Как приготовить DataVault и не испортить Greenplum

Каждый повар в поисках своего фирменного рецепта проходит несколько стадий. Подбор нужных ингредиентов методом проб и ошибок. Неизбежный провал и разочарования. Вторая попытка: замена неподходящих ингредиентов, выбор новых инструментов и, наконец, долгожданный успех. Затем фирменный рецепт балансируется до идеального вкуса. В IT всё то же самое. Разница в том, что повара скрывают успешные рецепты, а мы делимся своими находками.

Меня зовут Виталий Дудин, я руководитель направления Платформы больших данных в Х5 Digital. Вместе с командой мы строим аналитическую дата-платформу для экспресс-доставки. В этой статье поговорим про то, как она устроена и почему в какой-то момент мы решили всё переделать с нуля. Также немного расскажу про старое хранилище на PostgreSQL, про его преимущества и недостатки. Про то, как устроена новая аналитическая дата-платформа, модель DataVault на Greenplum и про то, как сделать так, чтобы всё работало, а не тормозило.

d9473a0ead97cf8c4e87abfe751b8b79.jpg

Во время взрывного роста рынка e-grocery все начали срочно масштабировать сервисы доставки. Некогда было даже как следует продумать модель хранения данных, приходилось идти по тонкой тропинке проб и ошибок. Мы тоже завязались на структуры источников данных, что в дальнейшем стало одной из проблем. Для удобства аналитики у нас была одна огромная витрина-монолит, но мы быстро достигли предела физического расширения сервера, и из-за технических ограничений начали выводить из эксплуатации DWH, основанный на PostgreSQL, и активно развивать DWH на базе GreenPlum. Из-за чего? Давайте подробнее посмотрим, как так получилось.

DWH на PostgreSQL

Главный вызов для нас — это быстрый рост нашего онлайн-бизнеса. Год к году число заказов значительно увеличивается, соответственно, растут объёмы хранимых данных. Так, один заказ генерирует большое число событий, которые нужно хранить во множестве таблиц. Рост ощутимый. С 75,1 миллионов в 2023 году до 54,4 миллионов заказов только в первом полугодие 2024.

dac61d8fb83287e3ec3365772ca03971.png

На старте проектов почти все выбирают PostgreSQL, и мы не исключение. Это крутая бесплатная СУБД в свободном доступе с активным развитием. Из-за того, что не было возможности долго раздумывать, завязались на структуры источников данных. Для удобства аналитики сделали одну огромную витрину-монолит, на которой построили очень много отчётности и ещё больше мелких витрин для небольших отчётов. Витрина-монолит весит больше 150 Гб и содержит в себе больше 300 атрибутов. Поэтому её очень тяжело поддерживать и развивать. Тем более, что в ней быстро скопилось много кода с расчётами, который нужно постоянно допиливать.

Архитектура старого хранилища на PostgreSQL довольно простая. У нас два вида источников данных. Первый — MongoDB, в которую пишет бэкенд. Для выборки изменений данных написан сервис, который читает оплог и применяет эти изменения в слой сырых данных. Второй тип источника — это внешние api-сервисы, из которых мы получаем данные и сохраняем их в слой сырых данных. Далее с помощью различных функций и вьюх получаем витрины данных, данные из которых отправляются в BI инструменты.

26ca4efce14875af2b933038c1bc23f3.png

В качестве CDC мы использовали самописный сервис, который требовал поддержки, но обладал рядом преимуществ:

  1. Мы получили возможность быстрого внедрения фичей и обработки особенностей данных, которых очень много;

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

  3. Он довольно просто конфигурируется и масштабируется;

  4. Он не требователен к ресурсам сервера;

  5. У нас есть большая экспертиза в Node.js внутри компании.

Хотя, конечно, не обошлось и без недостатков. У самописного CDC:

  1. Не было единой панели управления, так как это большое число докер-контейнеров;

  2. При внедрении фичей приходилось пересоздавать и перезапускать довольно много докер-контейнеров;

  3. Есть сложности с мониторингом;

  4. Не было отказоустойчивости, если происходил сбой, всё поднимали руками;

  5. Чтобы добавить атрибут в репликацию, дорабатывали конфигурационный файл и перезапускали сервис.

Да и в целом в хранилище со временем проявлялось всё больше недостатков, которые постепенно перевешивали его преимущества. Вот основные из них:

  1. Сервер невозможно удобно масштабировать, чтобы расширить железо, поэтому приходилось проводить много лишних действий;

  2. Появлялось всё больше и больше кода;

  3. Все дата-инженеры писали по-разному и ругали друг друга за кривой код;

  4. С каждым годом нужно было всё больше дата-инженеров;

  5. Возникало много дублированных метрик в разных витринах;

  6. Нарастали сложности с оптимизацией;

  7. Витрина-монолит росла и росла в ширину, что увеличивало сложность поддержки.

  8. Слабо структурированные данные увеличивали сложность работы с ними;

  9. Сложное подключение к расчетам новых источников заказов и прочих данных.

Мы накопили довольно много данных. Смотрите сами, вот немного цифр:

c491ca54ff251e456c6d0760a378c5e1.jpg

У рецепта получились вот такие ингредиенты. Он оказался не таким уж «вкусным», как мы рассчитывали! Пришлось искать им более удобную замену, чтобы избежать костылей, которые начинали постепенно появляться, и выйти на новый уровень.

DWH GreenPlum

На самом деле, вариантов, которые бы нам подходили, оказалось не так уж много. Мы сразу отбросили те хранилища, которые не вписывалось в наши критерии:

  • Бесплатность;

  • Открытость;

  • Лёгкая масштабируемость;

  • Поддержка колоночного хранения данных;

  • Ориентированность на аналитику.

После отбора остались Vertica и GreenPlum, но у Vertica быстро выявился существенный недостаток. Её полноценная бесплатная версия предназначена для работы с данными объёмом до 1Тб и максимум тремя серверами в кластере. На фоне санкций мы решили не рисковать и не наступать второй раз на грабли отсутствия полноценного масштабирования, поэтому всё-таки остановились на GreenPlum.

У этого СУБД много плюсов. Он опенсорс, и его используют крупные компании. Выполнен на основе PostgreSQL. У него колоночное хранение данных. Он легко горизонтально масштабируется и заточен под аналитику. Что немаловажно — в команде уже была экспертиза по этому хранилищу.

Конечно, не обошлось без минусов. На тот момент в основе Greenplum был PostgreSQL версии 9.4, хотя уже была доступна версия 15. Это было обусловлено довольно медленным развитием продукта, но мы решили, что справимся с этим. Более существенным недостатком оказались проблемы с OLTP нагрузкой — с этим ещё предстояло разобраться. Но после того, как взвесили все плюсы и минусы, плюсы всё-таки перевесили, поэтому мы взялись за дело.

В новом хранилище мы изменили архитектуру. Кое-какие ингредиенты старого рецепта сохранили из-за явных преимуществ, но другие требовалось заменить. В качестве CDC мы снова использовали самописный сервис, который читает oplog MongoDB и запрашивает целиком изменяемые документы. Это вешает какой-то overhead на Mongo, но незначительный. Только теперь мы сделали сервис универсальным, поэтому он требует гораздо меньше усилий на поддержку в отличие от CDC-сервиса старого DWH.

Далее данные уходят на хранение в Kafka. Оттуда мы читаем их с помощью NiFi и складываем в слой сырых данных в Greenplum. Потом происходит какая-то магия (спойлер — DBT magic) и они попадают в витрины данных. Также у нас есть airflow для управления запуском трансформаций данных. Трансформацию внутри DWH мы делаем с помощью DBT, а в качестве модели хранения данных выбрали DataVault с хранением полной истории изменения данных.

Получилась такая схема:

70927c7fe128767c337258c22790d598.png

Главная часть фирменного рецепта хранилища — его детальный слой. Если кратко пробежаться по цифрам, то сейчас DDS включает в себя:

0a9e71442ae8e3e96aae018d5499078c.png

Сейчас многие используют DBT в проде и почти все хоть что-то о нём слышали. Можно сказать, что это модный ингредиент, но мы используем его не потому, что это тренд, а потому что приложение легковесное, отлично гитится, подходит для CI/CD и соответствует принципу DRY за счёт макросов и шаблонов Jinja.

Это очень важно при методологии DataVault, так как объектов в нём по природе много. DBT следует концепции «Всё есть SELECT». Это абстрагирует весь код, который отвечает за материализацию запросов в Хранилище. То есть все команды CREATE, INSERT, UPDATE, DELETE, по сути, заменяются одним SELECT-запросом. При этом логика преобразований может быть многоуровневой. Это освобождает инженеров от DDL-операций, так как всё происходит под капотом на уровне приложения. Ну и встроенная автодокументация с Web-сервером, Data Lineage и тесты позволяют сохранить ещё больше ресурсов для концентрации на моделировании и настройке ELT. Причём, эти сервисы вполне нормальные, и в то же время позволяют, при необходимости, перейти на другие продукты.

Но, естественно, не всё так просто и радужно.

fcb5c6ea45d8f559f4b5cd064ff66ebf.png

Чтобы включить DBT в наш стек Data Platform, пришлось решить три основных вопроса:

  1. У команды не было практически никаких компетенций в продукте, чтобы можно было с уверенностью сказать, что он подойдёт для реализации задуманной модели и витрин;

  2. У DBT нет встроенной поддержки GP;

  3. Для работы с DV разумно было использовать специализированный пакет DBT Vault.

Для начала провели ресерч и оказалось, что минимальный опыт работы с продуктом в команде уже есть и его можно использовать. Дальше повезло ещё больше! Как раз во время проведения ресёрча появилась первая версия коннектора. Оставалось только убедиться, что его функционала хватает для наших потребностей. Это оказалось так! Плюс ко всему, коннектор продолжает развиваться. Его совершенствует наш соотечественник.

А вот с пакетом DBT Vault пришлось немного повозиться. Из коробки он тоже не работал с GP, поэтому мы немного допилили его синтаксис в части хэширования и оконных функций. «Тестирование нового рецепта» нас так затянуло, что захотелось сделать и другие доработки.

Разработка ELT — инструменты и методология

Методология DV 2.0

В процессе ELT есть спорные грабли, связанные с методологией DV 2.0, по которой ключ строится на основе хэш-функции. В нашем случае — это MD5 c 32-мя символами. Это необходимо для возможности параллельной загрузки в саты, линки и хабы.

Это может быть полезно, если в архитектуре присутствует Data Lake и в нём тоже формируются какие-то данные. В таком случае можно напрямую джойниться. Но на больших объёмах особенно заметно, что джойны медленнее, чем на основе интов, и занимают больше памяти как на диске, так и у ОЗУ.

Если в перспективе у вас не ожидаются объекты с 1B записей или есть возможность масштабироваться горизонтально, то с этим можно не заморачиваться.

Автоматизировали рутинную работу с кастомными скриптами

Также мы автоматизировали рутинную работу с кастомными скриптами. Например, такие вещи, как автонарезка партиций и фреймворк для подготовки миграций.

787a002c5673e4dc5801e4f38ef4b33b.png

На схеме важно выделить S2t-файл и парсер к нему. S2t-файл — это, по сути, Excel с метаданными об источниках, этапах трансформации и структуре объектов. Парсер-приложение на Python обрабатывает файл и создаёт DBT-модели.

Эти модели запускаются Airflow.

Функционал DBT

Что касается откровенных граблей, то это функционал DBT expand_column_types, который для нас оказался удивительным и вредным. Он по умолчанию включен в DBT, и явно его отключить в нашей версии было невозможно.

9b85de6030f6682943a113d8bd4bdfde.png

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

В нашем случае это было из-за операции union all varchar (20) и null. Результатом стал varing без явного ограничения длины, но в DBT в одной из функций захаркодено, что в таком случае должен быть varing (256). Почему так — не понятно, поэтому мы выпилили этот функционал и забыли.

С основными доработками вроде разобрались. Добились того, чего хотели и перешли непосредственно к обкатке и проверкам. То есть начали доводить наш фирменный рецепт до «идеального вкуса».

Трансформация

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

Первый этап — взятие инкремента из сырых данных

Сырые данные — это секционированные по месяцам таблицы, хранящие в себе CDC-логи коллекций MongoDb. Некоторые из коллекций активно изменяются, а значит генерируют много записей. Например, наша основная коллекция заказов к концу месяца достигает 150 Gb, поэтому взятие инкремента внутри партиций длилось 45 и более минут.

Для улучшения перформанса мы выбрали вариант дополнительного индексирования атрибута, по которому берётся часовой инкремент, но этого оказалось недостаточно. Индекс начал нормально работать только после принудительного отключения GPORCA и последовательного сканирования.

914c239918fb0451cc5010f3b68f3307.png

Раскладка неструктурированных данных

Так как основной источник — это кластеры Mongo (их 10), мы выделили раскладку неструктурированных данных в плоский вид в отдельный этап. Тут тоже пришлось перепробовать несколько методов, чтобы получить необходимый результат.

Первым подходом было разовое разложение абсолютно всех атрибутов каждой коллекции, но от него пришлось быстро отказаться. Потому что некоторые коллекции содержат в себе около 500 уникальных атрибутов, из которых в DDS раскладывается всего, например, 30, а это дополнительные затраты.

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

d01aab3af9d40219ce450a6759480313.png

Подготовка финального стейджа

На данном этапе готовится финальный стейдж, в котором:

  • хэшируются атрибуты для PK и хэшдифа сателлитов;

  • переименовываются атрибуты в соответствии с принятым неймингом;

  • кастятся типы данных.

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

Этот подход подтвердил Yon Lew.

Вставка в DDS

И на финальном этапе происходит вставка в DDS.

d178642d380f3491ae078a24528a9de6.png

Здесь также привлекают внимание большие объекты, поскольку особенности архитектуры DV каждый раз требуют последовательного сканирования всего объекта для правильной вставки данных. Это дорогостоящая операция даже с учётом распределённой системы. Нашим решением стал выбор правильного атрибута секционирования и сканирования только тех партиций, в которые попадают данные из обрабатываемых батчей. При этой операции особое внимание стоит обратить на выбор атрибута секционирования. Если сделать неверный выбор, будут образовываться дубли. Один и тот же ключ может прийти в разные месяцы, система его пропустит и получится дубль.

После того, как данные в детальном слое наполнились, можно переходить к следующему этапу.

Сбор витрин

Это самый важный этап с бизнес-ценностями. Здесь главное — спроектировать промежуточные Intermediate-таблицы. Их нужно правильно дистрибуцировать, чтобы финальный джойн был максимально локальным. Плюс нужно позаботиться о том, чтобы эти промежуточные таблицы можно было переиспользовать для разных витрин. Это позволит снизить нагрузку на кластер.

35489001714e3de3e1aef9d17e9140f0.png

Также важно правильно выбирать перезаполнение витрин. Некоторые из них можно полностью перечитывать, если это позволяет бизнес и сами расчёты сравнительно лёгкие. А для некоторых необходимо делать загрузку инкрементальной. Потому что аналитикам и руководству важно понимать, что отчёт, который они смотрели в октябре за сентябрь, будет таким же, если просматривать его в мае. Данные должны быть одни и те же! Логика не должна меняться! Ну и, конечно, если витрина очень сложная и расчёт тяжелый — без инкремента не обойтись.

После того, как мы разобрались с нашими трансформациями, пора было переходить к другим процессам.

Доставка кода на прод

Коллективная работа с объектами Greenplum ведётся в двух проектах GitLab.

  1. С помощью библиотеки Liquibase происходит всё управление Greenplum за исключением объектов в слое DDS, DM. То есть создаются схемы, служебные объекты, настраиваются права, а процесс выкатки автоматизирован по кнопке и включает тесты изменений и сам накат в базу.

  2. Для DBT выделены отдельные схемы, в которых он полностью управляет объектами. В нём также автоматизирован процесс выкатки, который докерезуют DBT-проект и пушит образ в приватный Registry.

Для запусков расчётов мы используем ssh-оператор Airflow, который через определённый entrypoint запускает тот или иной функционал проекта. Например, entrypoint run-and-upload запускает часовую обработку сырых данных и выгружает артефакты результата работы в специальную служебную таблицу.

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

Внесение хитрых DDL-изменений и кастомных пересчётов достигается за счёт функционала миграции, который позволяет с помощью Python, DBT-команд и нативного SQL написать практически любую логику изменений. Модуль миграции также включен в DBT-проект и запускается через отдельный ручной даг.

Это всё, что касается доставки кода в прод, но не стоит забывать про отслеживание изменений. В наше время сложно обойтись без качественного мониторинга.

Мониторинг и алертинг

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

Первым делом надо исключить неважные алерты, иначе со временем на них перестанут обращать внимание и будут пропускать важные. После этого нужно выбрать критерии для этих процессов. Мы организовали мониторинг каждого из ELT-этапов:

  • чтение данных из oplog и отправка сообщений в топики Kafka;

  • лаг вычитки из топиков;

  • обработка сообщений в NIFI;

  • вставка данных в GP;

  • результаты работы DBT, в том числе его тестов;

  • ну и факты запусков Airflow DAG, время обработки тасков.

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

Итоги

Финальную точку в нашем фирменном рецепте ставить пока рано. Конечно, нужные ингредиенты на данном этапе мы подобрали, первичные дегустации уже состоялись и «вкус» получился достойным, но его балансировка всё ещё продолжается. Хотя мы уже:

  1. Ускорили разработку витрин данных;

  2. Разработкой моделей данных для витрин теперь занимаются даже аналитики, потому что не требуются какие-то специфичные знания;

  3. Для поддержки работоспособности и развития дата-платформы нужно меньше дата-инженеров;

  4. Избавились от узких мест в доставке данных, теперь все компоненты развернуты в отказоустойчивом виде;

  5. Навели порядок и наконец ушли от дублирования одинаковых метрик бизнеса;

  6. Получили возможность инкрементально обновлять витрины данных;

  7. Легко масштабируем сервер БД без даунтайма;

  8. Движемся в сторону Data Mesh, так как Real-time аналитику мы всё же оставили на PostgreSQL;

  9. Храним всю историю изменения данных.

Кое-какие моменты пока ещё не доделаны:

  • огромный объём данных в сыром слое;

  • сложность в распределении ресурсов сервера между читающими пользователями;

  • много объектов в БД.

Но мы продолжаем стремиться к идеальному рецепту и уже планируем дальнейшие доработки. Например, хотим:

  • охлаждать сырые исторические данные в S3 хранилище, чтобы не раздувать кластер Greenplum старыми данными;

  • внедрить дополнительную СУБД для чтения витрин данных многими пользователями (уже даже выбрали для этого ClickHouse);

  • расширить число источников данных.

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

© Habrahabr.ru