Хранение ваших данных

Время рассеивается сияющим эфиром твердой угловатости фактов.

Ральф Вальдо Эмерсон, Эссе, 1841

Нашим друзьям, изучающим физику, повезло меньше. Термометр не может измерить температуру в непосредственно заданное время. Температура распространяется не мгновенно, поэтому все, что мы можем получить — среднюю температуру за какой-то период. И чем больше мы будем сужать этот период, тем дороже нам придется заплатить за оборудование. Так или иначе, это не застрахует нас от погрешностей и неисправностей.

Мы же с вами можем делать столько дискретных измерений, сколько пожелаем. Мы даже можем на ходу менять измерения, полагаясь на сложную логику. И, конечно, мы знаем, как работать с большими массивами данных. Так зачем же мы от них избавляемся? Вот, посмотрите на документацию одного популярного инструмента для анализа RRDtool:

Вы заметили, что в созданной нами базе мы использовали усреднение, зачем? Оказывается, если мы будем записывать данные каждую секунду, а потом захотим вывести среднее значение за год, то вычисления и сам процесс выборки из БД может потребовать большое количество ресурсов. Чтобы избежать этого, в RRDtool консолидация данных происходит при записи, а не во время считывания. Использование разных функций консолидации позволяет вам сохранять только те данные, которые вам нужны: максимальная нагрузка на канал LAN в течении одной минуты, минимальная температура вашего винного погреба, количество минут простоя.

RRDtool

Такой подход в корне неверен. И не только из-за пространственных суждений об оптимизации. Выбрасывать исходные данные и оставлять только собранную метрику во избежание захламления диска — очень непродуманно.

В замену сохраненному месту на диске вы впадаете в зависимость от потребности предугадывать результат. Такой подход сработает разве что если вам нужно посчитать «максимальную нагрузку на канал в течении одной минуты» и больше ничего. Если потом вам понадобится узнать, какая была средняя нагрузка или 95-й процентиль, или вам нужно будет сгруппировать данные по протоколу, исключить некоторые хосты, или что-либо еще, — вы не сможете.

Обрабатывать данные и выделять из них нужное — многомерная задача поисковиков. Но ведь мы – программисты. Мы знаем, как решать проблемы с поиском данных. Главное, чтобы была возможность свободно перемещаться между этими мерностями. Невозможно предугадать наперед, какие данные и параметры поиска нам понадобится использовать в будущем. Хранение всех исходных данных может и дорогостоящее удовольствие, но суммарное множество всех возможных вариантов метрик, которые мы сможем извлечь, намного ценней. С другой стороны, полезность исходных данных с каждым годом будет стремительно понижаться.

Есть и способ переосмысления этой задачи. Это то, что случается на пересечении двух сценариев использования. Новая информация должна быть всегда в «высокоэнергетическом» состоянии, очень жидкая и доступная. Напротив, старая информация может быть низкоэнергетической, собранной предварительно и иметь возможность оставаться в стабильном состоянии долгий период времени.

Таким образом, храните исходные данные в очень быстрых БД, но только в первые недели или месяцы использования. У вас всегда будет доступ к этим данным, чтобы провести нужный анализ. Затем вы можете запихнуть их в RRD или что-либо еще для хранения данных на длительной основе. На выходе мы имеем буфер со свежими и актуальными исходными данными для диагностики текущих проблем и предыдущие записи для сверки и сравнения.

Это приводит нас прямиком к системе Scuba, разработанной компанией Facebook. Когда Лиор Авраам и Дэвид Райс преподнесли нам эту идею, она показалась мне абсурдной. Хранить исходные данные в оперативной памяти? Без индексов? Каждый запрос как он есть? Чушь. Это были достаточно тяжелые времена, когда мы вынуждены были умещать все наши данные на дисках. О чем они вообще думали?

А думали они о будущем. Чопорность инструментов, с которыми ми имели дело в те времена, особенно зависимость от постоянного предугадывания, была нашей повседневной реальностью. Мы могли располагать только несколькими размерностями одновременно, и если проблема таилась где-то еще, мы просто застревали. Добавление других размерностей превращало работу в сплошную головную боль, которая, конечно же, не способствовала решению поставленной задачи. Исходные данные оказывались либо утерянными, либо доступ к ним был затруднен.

Хранение исходных образцов, всех метаданных и измерений в простой таблице в оперативной памяти позволяло делать запрос на любые данные одинаково просто. Это сократило время прохождения цепи обратной связи до секунд, что по-настоящему позволяло проводить исследования в реальном времени. Быстрое устаревание исходных данных стало преимуществом, так как ограничивало необходимое, постоянно иссякающее свободное место для хранения.

Ниже приведено начало README файла для системы анализа разработанной компанией Square, Inc.

Cube — это система сбора событий с присвоенными метками времени и последующим составлением метрики. Собирая события, а не уже готовую метрику, Cube позволяет составлять статистику постфактум. github.com/square/cube

Как же ее создать

Самым простым примером такого рода системы сбора информации может быть поисковая система, которая может составлять статистику. В ее задачи входит эффективное хранение огромного количества «документов» с помощью гибких логических структур, вероятнее всего размещенных на нескольких серверах одновременно. Она должна быстро найти подходящие для обработки данные и потом выдать их в виде количественного показателя, суммы, усредненных значений, процентного соотношения и т.п. В том числе, и разбивая по группам размерностей (например, среднее физическое время всех запросов в течение последних нескольких часов на участке в 5 минут).

Системы такого рода почти всегда гибридные: реальный поисковой движок дополняется статистикой, расширенными статистическими движками или хранилищами с добавлением сверху слоя сбора аналитики. Не думаю, что можно сойтись на одном названии, но, если бы спрашивали меня, то я, скорее всего, назвал бы их «аналитическими движками».

Я вряд ли бы порекомендовал вам для серьезной работы делать свой собственный подобный; в мире уже и так предостаточно подходящих для использования библиотек. Но понимание их внутреннего содержимого через создание своей игрушечной версии поможет понять преимущества систем, построенных вокруг сбора образцов исходных данных.

Самое сложное — это масштабирование таких систем, особенно с функцией статистики, на несколько связанных между собой машин. Мы не будем рассматривать такие варианты. Это позволит нам создать прототип системы меньшей кровью, используя стандартные реляционные БД. Функций статистики часто не хватает, но мы заменим их в полном объеме умным SQL и последующей обработкой данных. Мы также обойдем стороной некоторые принципы построения гибких логических структур во избежание лишней головной боли во время работы со структурами реляционных БД. В одном временном пространстве у нас будет только один индекс. Мы редко когда будем удалять колонки, и не будем выполнять объединения таблиц.

sqlite> .tables
derp     derp_cache     derp_db

sqlite> .schema derp
CREATE TABLE derp (
  timestamp     int,
  script_path   string,
  server_name   string,
  datacenter    string,
  ab_tests      string,
  walltime      int,
  cpu_time      int,
  ...

Каждый из трех режимов в нашем примере может быть представлен постой таблицей БД. Мы можем отправлять таблицам запросы и получать данные анализа реального времени разного рода запросов на сервере, скажем, за последний час. Вот почему определение слова метрика в предыдущей главе было так схоже с описанием таблицы БД.

sqlite> select script_path, round(avg(walltime)/1000)
  from derp
  where timestamp >= 1366800000
  and timestamp < 1366800000+3600
  group by script_path;

/account  | 1532.0
/health   | 2.0
/home     | 1554.0
/pay      | 653.0
/share    | 229.0
/signup   | 109.0
/watch    | 459.0

Как только пользователь входит в приложение под своим профилем, оно проделывает намного больше работы и обрабатывает дополнительные данные, такие как настройки пользователя и просмотр его истории. Таким образом, состояние пользователя играет важную роль в том, какие данные будет обрабатывать приложение. Анализ данных только от небольшого количества анонимных пользователей может помешать обнаружить некоторые проблемные участки кода.

sqlite> select user_state, sum(sample_rate), round(avg(walltime)/1000), round(avg(db_count))
  from derp
  where timestamp ...
  and script_path = '/watch'
  group by user_state;

logged-in |  2540 | 1502.0 | 2.3
anon      | 11870 |  619.0 | 0.9

Включение разной информации о среде в логи DERP позволит анализировать производительность большего числа компонентов системы. Загружаются ли сервера равномерно? А как насчет разных дата-центров, в которых они могут находиться? Как только этот набор данных собран, в поле sample_rate будет храниться сумма всех произведенных запросов.

sqlite> select server_name, sum(sample_rate), round(avg(walltime)/1000)
  from derp
  where timestamp ...
  group by server_name;

web1 | 19520 | 1016.0
web2 | 19820 | 1094.0
...
sqlite> select datacenter, sum(sample_rate), round(avg(walltime)/1000)
  from derp
  where timestamp ...
  group by datacenter;

east   | 342250 | 1054.0
west   | 330310 | 1049.0

Группируя запросы во временной размерности по 300 секунд, мы сможем увидеть, что заход на страницу /health, цель которой показывать нам, что сервер работоспособен и отвечает на запросы, показывает вполне стабильную производительность, что от него и ожидается.

sqlite> select group_concat(round(avg(walltime)/1000, 1), ', ')
  from derp
  where timestamp ...
  and script_path = '/health'
  group by timestamp/(60*5);

1.5, 2.1, 2.2, 1.3, 1.3, 1.3, 1.4, 1.1, ...

Разве это не прекрасно? Все эти и тысячи других измерений мы можем проводить, исследуя одну и ту же таблицу с исходными данными. Простота запросов тоже очень кстати. Мы можем с легкостью превратить все эти цифры в диаграмму или график. Что вскоре и сделаем.

Вкратце о моделировании данных

Давнее противостояние между приверженцами реляционных моделей данных и пространственным моделированием сходится по числу требуемых объединений таблиц для завершения процедуры анализа. Другими словами, это спор на счет производительности.

Что ж, мы уже в курсе, что делать с проблемами производительности. Принцип «определи проблему и проведи анализ» одинаково применим ко всем системам, построенным для измерений. Простая, описанная выше структура таблиц очень редко разменивает место на время: она не использует объединения таблиц, так как все необходимые метаданные уже присоединены к образцу. Выглядит хорошо, но внешность иногда бывает крайне обманчивой.

Как только ваша система измерений начнет расти и развиваться, вы поймете, что вас вряд ли устроит структура, накапливающая тонны таких лишних метаданных как тип чипсета, модель, номер статива и так далее в том же духе. Вместо этого вы, скорее всего, с удовольствием соедините несколько таблиц с нужными данными в одну. Смысл в том, что нужно усложнять свою систему там, где это требуется, а не там, где это якобы положено делать. Тут главное не перемудрить.

Профессиональный фестиваль "Российские интернет-технологии" — весь спектр индустрии веб-разработки от системного администрирования до управления проектами и высоконагруженных систем, а также клиентское программирование, базы данных и системы хранения, тестирование и качество. Всё это вокруг бесплатной технологической выставки.

Флагманская конференция, самое крупное профессиональное мероприятие для разработчиков Интернет-проектов в России и одно из крупнейших во всём мире! Программа охватывает такие аспекты веб-разработок, как архитектуры крупных проектов, базы данных и системы хранения, системное администрирование, нагрузочное тестирование, эксплуатация крупных проектов и другие направления, связанные с высоконагруженными системами.

Хотите узнать больше о наших конференциях?