Кроме этого, есть способ, работающий практически везде. Главный враг скорости запросов – это соединение данных (join) в разных таблицах. Например, у нас есть две таблицы: клиенты и заказы, обе таблицы соединяются через id клиента, имеющийся в обеих таблицах. Почему бы не делать это соединение данных периодически, например по ночам, и сохранять результат обратно в хранилище? Это называется материализованным представлением (materialized view). Тогда клиенты будут обращаться не к данным напрямую, а к их представлению. Скорость будет на порядок выше – это плюс. Минус такого решения – усложнение всей конструкции. Если что-то пойдет не так и таблицы-доноры будут иметь неверные данные, эти представления придется пересчитать. Это нужно будет всегда держать в голове.
Более радикальное решение – сменить технологию на ту, которая больше соответствует задачам. Я уже приводил пример, когда с Hadoop мы перевели пользователей на колоночную базу ClickHouse и получили ускорение в десятки и сотни раз.
Еще один дорогой способ ускорить работу аналитической системы – сделать апгрейд «железа сервера». В системах Hadoop и Clickhouse это легко сделать, просто добавив дополнительные машины. Такая операция называется горизонтальным партицированием (sharding) – данные разбиваются по записям и распределяются по серверам. Запросы к данным будут выполняться параллельно на нескольких серверах, а затем результаты объединяются. Такая схема теоретически может дать линейное ускорение: два сервера будут работать быстрее в два раза по сравнению с одним сервером и т. д.
Архивация данных и устаревание
Следующая часто возникающая проблема с хранилищами данных – большой рост данных, который приводит к нехватке свободного места. Этим нужно постоянно заниматься. Много данных не бывает, но бывает мало бюджета. Какие стратегии существуют?
Первая и самая простая стратегия – удаление устаревших данных. Это могут быть данные старше двух лет, старше пяти лет – все зависит от ваших задач. В Hadoop можно использовать другую стратегию: менять фактор репликации. По умолчанию он равен трем – это означает, что для хранения одного терабайта данных вам понадобится три терабайта на дисках. Это цена надежности, в случае выхода из строя двух дата-нод одновременно вы не потеряете данные. Фактор репликации можно устанавливать индивидуально для файлов: мы можем его уменьшить для более старых. Например, данные младше двух лет – фактор равен трем, от двух лет до четырех – двум, старше четырех лет – удаляются. Подобный подход используется в Facebook. Некоторые компании архивируют старые данные на какой-нибудь дешевый носитель, а если данные понадобятся, они переносятся обратно. Я не поддерживаю такую схему – это как прятать вещи в чулан: потом о них забываешь, а если вспомнишь, то искать их там лень.
Второй способ – использование кодеков сжатия (табл. 6.2) [43]. Это очень актуально и эффективно работает в Hadoop и Spark. Сжатие данных убивает двух зайцев – мы уменьшаем объем занимаемого места на дисках и ускоряем работу с данными: они в разы быстрее гоняются по сети между серверами кластера и быстрее читаются с диска. Чудес не бывает – чем сильнее жмет кодек, тем больше ему нужно ресурсов процессора для сжатия данных.
Мы на своем кластере используем кодеки: gzip, bzip2 и lzma. Lzma имеет самую высокую компрессию и используется для архивируемых данных. Gzip используется для всех остальных данных, поступающих в кластер. От конкретного кодека сжатия зависит возможность «разрезания» (split) файла для операции Map без его распаковки. Как уже писалось ранее, для операции Map данные «нарезаются» на блоки размером не больше заданного в настройках Hadoop (block size). Если сжатый файл больше этого размера, то в случае разделимого кодека (splittable codec) его можно разрезать и распаковать по частям на разных нодах кластера параллельно. В противном случае придется распаковывать этот огромный файл целиком – а это уже будет гораздо медленнее.
Таблица 6.2. Сравнение кодеков сжатия
Мониторинг хранилищ данных
Пользователи вашей аналитической системы могут принимать очень важные решения на основе данных, поэтому важно обеспечить их надежными данными.
Однажды у меня произошел нехороший случай: в пятницу вечером я произвел изменения в системе пополнения данных в хранилище в Ozon.ru. И ушел в отпуск. Конечно, на выходных все упало. Ребята-аналитики получили в понедельник письмо от генерального директора на английском, которое начиналось словами: «I’m fed up…» (Я сыт по горло…). Они, конечно, нашли причину проблемы и исправили. Как следовало мне поступить? Во-первых, не делать никаких изменений в пятницу, тем более перед отпуском. Если бы я это сделал хотя бы в четверг, то в пятницу утром изменения «сломали» бы систему и у меня было бы время все исправить. Во-вторых, если бы была полноценная система мониторинга, то разработчики первыми получили бы сообщения о проблеме. У них была бы возможность предупредить пользователей до того, как те ее сами заметят.
Когда я проверял задачи по анализу данных или делал их сам, то периодически мучился вопросом: «А все ли в порядке с данными?» Иногда эти сомнения оправданны, и проблема действительно существует. Это заставляет нас первым шагом делать проверку. Но она не всегда бывает простой и может занять приличное время. Есть второй путь – автоматизация проверок данных и мониторинг. Вот об этом и поговорим.
Есть два параметра, которые нужно проверить:
• доступность всех данных, которые есть в источнике;
• целостность.
Доступность данных проверяется легче всего. Во-первых, проверяется дата последнего обновления, например файла или таблицы. А еще лучше воспользоваться полем с датой/временем – например датой и временем заказа. Во-вторых, можно посчитать и сравнить количество записей в хранилище данных и в источнике. Если есть поле с датой и временем, можно сделать такое сравнение по дням. Конечно, в момент проверки данные источника и хранилища будут расходиться, потому что всегда есть дельта времени изменения данных в источнике и отражения этих изменений в хранилище. Если вы уверены, что с данными все в порядке, можно опытным путем найти допустимые пороговые значения относительной разницы в процентах. Для одних данных это может быть полпроцента, для других – все пять. Этот тип проверки закроет 80 % проблем с данными в хранилище. Это как раз те 20 % усилий по Парето, которые дают 80 % результата. Методика недорогая, доступна всем и нравится мне своей простотой.
Второй параметр – целостность данных – проверить сложнее. Под целостностью я подразумеваю наличие в одной таблице тех данных, которые есть во второй. Простейший пример – есть две таблицы, одна с клиентами, вторая с заказами. В таблице с заказами есть поле «клиент». Целостность таблицы с заказами будет означать, что все клиенты из этой таблицы должны быть также и в таблице – справочнике клиентов. Если это соответствие не соблюдается, то при соединении (join) двух таблиц либо возникнут «битые» данные (если соединение было сделано с учетом этой особенности – left join, right join, full outer join), либо эти данные просто исчезнут и эти продажи выпадут из отчетов (если соединение сделано через inner join). Оба этих результата потенциально могут привести к неверным решениям. Хорошо бы это контролировать через независимые тесты, например проверять относительный объем продаж клиентов, которых нет в таблице с клиентами.