Команда Go for Devs подготовила перевод статьи инженеров Datadog о том, как из разрозненных пайплайнов вырастить надёжную мультитенантную платформу репликации данных. В материале — практический опыт масштабирования PostgreSQL, переход к асинхронной репликации, автоматизация через Temporal, работа с CDC и Kafka, а также реальные компромиссы между согласованностью, надёжн��стью и задержками.


Оглавление:


В Datadog мы эксплуатируем тысячи сервисов, которым необходим быстрый и согласованный доступ к данным. Перемещение данных между разнородными системами — оперативно и надёжно — критически важно, но при этом весьма сложно. У каждого приложения свои требования к актуальности данных, согласованности и задержкам, поэтому разрозненные решения быстро становятся хрупкими и плохо масштабируются по мере роста компании.

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

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

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

Закладываем фундамент: масштабирование поиска

Наш технический путь начался с общей базы данных Postgres, которая обслуживала ключевые продуктовые страницы в Datadog. (В отдельной статье нашего блога мы рассказываем, как постепенно уходим от общего монолитного хранилища.) Для небольшой нагрузки общая база Postgres была правильным выбором: простота использования, ACID-гарантии и экономичность делали её оптимальным решением для низкого и среднего трафика. На первых этапах прямые запросы обеспечивали быстрый отклик — большинство загрузок страниц укладывалось менее чем в 100 мс.

По мере роста объёмов данных в Datadog мы начали сталкиваться с хорошо известными ограничениями масштабирования Postgres. Время выполнения запросов, которое раньше измерялось миллисекундами, стало постепенно увеличиваться до секунд — особенно для сложных join-ов и агрегаций по нескольким таблицам.

Характерным примером стала страница Metrics Summary. Для одной организации нам требовалось при каждом запросе соединять таблицу из 82 000 активных метрик с 817 000 конфигураций метрик. При таких масштабах p90-задержка страницы приближалась к 7 секундам, а каждое изменение фасета запускало очередной дорогостоящий запрос, что приводило к узким местам по I/O и росту нагрузки на CPU, особенно в пиковые периоды.

Мы рассматривали изменение порядка join-ов, добавление составных индексов и использование эвристик запросов на основе размера таблиц. Однако разрастание данных и индексов приводило к замедлению вставок и обновлений, операции VACUUM и ANALYZE создавали постоянную операционную нагрузку, а давление на память вызывало рост вр��мени ожидания I/O. Мы фиксировали увеличение числа ошибок и падение пропускной способности по мере того, как база данных переставала справляться с конкурентной нагрузкой, что напрямую влияло на пользовательский опыт. Мониторинг с помощью Datadog APM подтвердил, что эти запросы начали потреблять несоразмерно большую долю системных ресурсов и времени, особенно по мере роста числа нормализованных строк на запрос.

Эти наблюдения отражают общеизвестные ограничения Postgres в индустрии: сложные аналитические запросы резко деградируют по мере роста размеров таблиц и индексов; вертикальное масштабирование быстро упирается в убывающую отдачу; а операционные задачи — такие как бэкапы, failover и репликация — становятся всё более рискованными на масштабах в несколько терабайт. К тому моменту, когда наши таблицы превысили порог в 50 000 метрик на организацию сразу для нескольких организаций, накопилось достаточно тревожных сигналов — медленная загрузка страниц, нестабильная работа фасетных фильтров и возросшая операционная нагрузка.

Понимая, что поиск в реальном времени и фасетная фильтрация по своей природе являются принципиально иной нагрузкой, чем OLTP, мы переработали архитектуру системы. Мы перенаправили поисковые запросы и агрегации на специализированную поисковую платформу, а данные начали динамически денормализовываться в процессе репликации из Postgres, как показано ниже. Такой подход «расплющивал» реляционные структуры для эффективной индексации документов и сразу устранил узкие места, связанные с поиском, существенно сократив задержки запросов и восста��овив качество пользовательского опыта.

Реплицируя данные из PostgreSQL в нашу поисковую платформу и обогащая их в рамках пайплайна, мы сократили время загрузки страниц до 97% — примерно с 30 секунд до 1 секунды — при этом удерживая лаг репликации на уровне около 500 мс.

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

Автоматизация репликации: масштабирование провижининга с помощью Temporal

По мере роста платформы управление сложностью провижининга пайплайнов репликации — от Postgres до нашей поисковой платформы — стало серьёзной проблемой. Мы использовали такие технологии, как Debezium, Elasticsearch и Kafka, однако сборка всех компонентов в единую платформу оказалась нетривиальной задачей.

Каждый пайплайн требовал выполнения последовательности точных и воспроизводимых шагов:

  • Включение логической репликации в Postgres путём установки wal_level в значение logical

  • Создание и настройка пользователей Postgres с корректными правами доступа

  • Создание объектов репликации, таких как publishers и replication slots

  • Развёртывание экземпляров Debezium для захвата изменений и стриминга данных в Kafka

  • Создание топиков Kafka и корректное сопоставление каждого экземпляра Debezium

  • Настройка heartbeat-таблиц для контроля удержания WAL (Write-Ahead Log) и мониторинга

  • Конфигурация sink-коннекторов для передачи данных из Kafka в поисковую платформу

При тиражировании этого процесса на множество пайплайнов и дата-центров операционная нагрузка росла экспоненциально, как показано на диаграмме ниже.

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

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

Надёжность важнее согласованности: асинхронная схема репликации

При проектировании платформы репликации данных перед нами встал базовый архитектурный выбор: синхронная или асинхронная репликация.

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

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

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

В основе этой архитектуры лежат проверенные open source-технологии — Debezium и Kafka Connect. Debezium обеспечивает захват изменений данных (CDC) из различных баз данных, а Kafka Connect служит фундаментом для масштабируемого и отказоустойчивого перемещения данных между системами. Вместе они образуют ядро нашей управляемой платформы репликации, обеспечивая гибкий, надёжный и расширяемый обмен данными внутри Datadog.

Обеспечение устойчивости пайплайнов за счёт совместимости схем

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

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

В рамках первой части мы разработали внутреннюю автоматизированную систему валидации управления схемами. Она анализирует SQL-миграции схемы до их применения к базе данных и выявляет потенциально опасные изменения. Например, мы блокируем такие изменения, как ALTER TABLE ... ALTER COLUMN ... SET NOT NULL, поскольку не все сообщения в пайплайне гарантированно заполняют этот столбец. Если потребитель получит сообщение, в котором поле окажется null, репликация может сломаться. Эти проверки позволяют автоматически одобрять большинство изменений без ручного вмешательства. В случае ломающих изменений мы напрямую работаем с командой, чтобы согласовать безопасный rollout.

Вторая часть решения опирается на мультитенантный Kafka Schema Registry, интегрированный с нашими source- и sink-коннекторами. Мы настроили его на обратную совместимость (backward compatibility), что означает: новые схемы должны позволять старым потребителям читать данные без ошибок. На практике это ограничивает изменения схемы безопасными операциями — например, добавлением необязательных полей или удалением существующих.

Когда происходит миграция схемы, Debezium фиксирует обновлённую схему, сериализует данные в формате Avro и публикует как сами данные, так и обновление схемы в Kafka-топик и Schema Registry. Обновлённая схема сравнивается с версией, сохранённой в Schema Registry, и либо принимается, либо отклоняется в зависимости от режима совместимости — в нашем случае backward. Поскольку пользователи также могут создавать собственные Kafka-консьюмеры для прямого чтения топиков, поддержание совместимости схем имеет критическое значение: нам важно, чтобы все потребители — как внутренние, так и внешние — продолжали работать без сбоев.

Ориентация на потребности пользователей в пайплайнах данных

В Datadog создание инфраструктуры, способной поддерживать широкий спектр сценариев использования, является базовым требованием. По мере того как всё больше внутренних команд начинали загружать и реплицировать данные в downstream-системы, стало ясно, что универсальный пайплайн «на все случаи жизни» плохо масштабируется при росте требований. Одним командам были нужны отфильтрованные или денормализованные данные, другим — кастомная логика обогащения, а третьим требовалось изменять форму или структуру данных перед сохранением.

Чтобы поддержать это разнообразие потребностей, мы спроектировали пайплайн как модульную, адаптируемую и настраиваемую систему, сделав упор на два ключевых механизма: трансформации Kafka Sink Connector и стандартизированный API обогащения.

Kafka Connect служит основой нашего фреймворка загрузки данных, как показано на диаграмме ниже. Чтобы дать командам контроль над тем, как данные проходят путь от источника к приёмнику, мы активно использовали single message transforms в Kafka Connect. Они позволили реализовать широкий спектр кастомизаций — динамическое изменение имён топиков, преобразование типов столбцов, генерацию составных первичных ключей путём конкатенации полей, а также добавление или удаление столбцов на уровне отдельных сообщений. Такая гибкость дала командам возможность формировать данные под свои конкретные ��адачи без необходимости вносить изменения в источник.

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

Для команд, которым требовалось добавлять к записям дополнительные вычисляемые поля или метаданные, мы внедрили кастомный API обогащения. Этот сервис располагается поверх нашей поисковой платформы и предоставляет стандартизированный способ запрашивать обогащение данных во время загрузки или после неё. Централизовав логику обогащения, мы избежали её дублирования в отдельных пайплайнах. В результате команды смогли сохранять согласованность своих data-продуктов, одновременно снижая сложность потоков загрузки данных.

От точечного решения к мультитенантной платформе

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

К решению вот такого уровня:

Изначальный сценарий использования репликации данных был предельно простым: передавать обновления из SQL-базы данных в downstream-поисковую платформу. Однако в процессе построения первого пайплайна мы увидели возможность создать нечто гораздо большее — обобщённую мультитенантную платформу, рассчитанную на масштабы Datadog.

С тех пор мы запустили целый ряд новых приложений на базе CDC по всей компании:

  • Репликация Postgres-to-Postgres для постепенного отказа от большой общей базы данных и обеспечения надёжных бэкапов Orgstore

  • Пайплайны Postgres-to-Iceberg для масштабируемой событийно-ориентированной аналитики

  • Репликация Cassandra для расширения источников данных за пределы традиционных SQL-баз

  • Межрегиональная репликация Kafka для повышения локальности данных и устойчивости Datadog On-Call

Компромиссы и извлечённые уроки

Чтобы выйти на такие масштабы, мы приняли ряд ключевых архитектурных решений, находя баланс между производительностью, масштабируемостью и операционным качеством:

  • Вынесли поисковую нагрузку за пределы Postgres, избавившись от запросов с большим количеством join-ов и сократив задержки до 87%

  • Выбрали асинхронную репликацию, сделав ставку на доступность и пропускную способность вместо жёстких гарантий согласованности

  • Автоматизировали провижининг пайплайнов с помощью Temporal, обеспечив надёжные и модульные workflow на больших масштабах

  • Гарантировали совместимость схем за счёт валидационных проверок и мультитенантного Kafka Schema Registry

  • Дали возможность кастомизации пайплайнов с помощью трансформаций и API обогащения, чтобы поддержать требования разных продуктовых команд

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

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

Русскоязычное Go сообщество

Друзья! Эту статью подготовила команда «Go for Devs» — сообщества, где мы делимся практическими кейсами, инструментами для разработчиков и свежими новостями из мира Go. Подписывайтесь, чтобы быть в курсе и ничего не упустить!