Урок по треку Data Engineering 7 мин

CDC и инкрементальная загрузка

Полная и инкрементальная загрузка, водяной знак по updated_at, удаления, log-based CDC, Debezium, outbox.

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

Полная загрузка

Выгрузить таблицу целиком и заменить копию в хранилище.

  • Плюсы: просто, видны удаления (строки просто исчезают), нет накопленных ошибок.
  • Минусы: долго и тяжело для источника на больших объёмах.

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

Инкремент по водяному знаку

Таблица источника имеет колонку updated_at, которая меняется при каждом изменении строки. Загрузка запоминает водяной знак (watermark): до какого updated_at данные уже забраны, и в следующий раз берёт updated_at > watermark.

Подводные камни:

  • Граница: несколько строк с одинаковым updated_at, часть из них была вставлена уже после выгрузки, но с тем же временем. Строгое > их потеряет. Безопаснее брать >= или окно с перекрытием и дедуплицировать по ключу при загрузке (upsert).
  • Долгие транзакции: строка получила updated_at = 10:00, но транзакция закоммитилась в 10:05, после того как выгрузка в 10:02 сдвинула водяной знак. Строка потеряна. Лечится перекрытием окна (брать с запасом в несколько минут или часов).
  • Удаления не видны: удалённой строки в источнике просто нет, по updated_at её не найти. Решения: мягкое удаление (deleted_at) в источнике, периодическая сверка ключей, log-based CDC.
  • Колонку обновляют не все: если какой-то код меняет строку без обновления updated_at, изменение потеряется. Надёжнее триггер в базе.
  • Нужен индекс по updated_at, иначе инкремент сканирует всю таблицу.

Log-based CDC

CDC (change data capture) захватывает изменения из журнала транзакций базы: WAL в PostgreSQL (логическая репликация), binlog в MySQL. Самый популярный инструмент: Debezium, работает как коннектор Kafka Connect и пишет события изменений в Kafka.

Событие Debezium содержит:

  • op: c (create), u (update), d (delete), r (read: строка из начального снимка);
  • before и after: состояние строки до и после (для d пустое after);
  • source: метаданные: таблица, позиция в журнале (LSN), время транзакции.

Плюсы log-based CDC:

  • Видны все изменения, включая удаления и промежуточные состояния.
  • Минимальная нагрузка на источник: читается журнал, а не таблицы.
  • Задержка секунды.
  • Не нужна колонка updated_at и дисциплина разработчиков.

Минусы и что учитывать:

  • Нужен доступ к журналу и настройка базы (в PostgreSQL wal_level = logical, слот репликации, публикация).
  • Слот репликации держит WAL, пока коннектор не прочитал: если коннектор встал надолго, диск источника переполнится. Это обязательно мониторят.
  • Начальная загрузка: снимок существующих данных (события r), затем поток изменений.
  • Изменения схемы источника нужно обрабатывать.
  • Порядок гарантирован в пределах ключа (события строки идут в одну партицию Kafka по первичному ключу).

Применение изменений в хранилище

События нужно применить к копии таблицы:

  • Последнее состояние: по каждому ключу берём событие с самой поздней позицией в журнале; если это удаление, строку удаляем (или помечаем). Делается через MERGE или в ClickHouse через ReplacingMergeTree с флагом удаления.
  • История: все события сохраняются как журнал изменений, из которого можно построить SCD 2.
  • Идемпотентность: повторная доставка того же события не должна ломать результат. Порядок определяется позицией в журнале (LSN), а не временем получения.

Outbox

Сервису нужно и записать изменение в свою базу, и отправить событие в Kafka. Если делать это двумя операциями, одна может не пройти: заказ сохранён, а событие потеряно (или наоборот). Паттерн transactional outbox: в той же транзакции, что и бизнес-изменение, сервис пишет событие в таблицу outbox. Отдельный процесс (часто тот же Debezium) читает outbox и публикует события. Атомарность обеспечивает транзакция базы.

Частые ошибки

  • Строгое updated_at > watermark без перекрытия: потери на границе и из-за долгих транзакций.
  • Инкремент без обработки удалений.
  • Слот репликации без мониторинга: переполненный диск боевой базы.
  • Применение событий CDC в порядке получения, а не в порядке журнала.
  • Двойная запись «в базу и в Kafka» без outbox.

Что спросят на собесе

  • Полная загрузка против инкрементальной, когда что.
  • Как работает загрузка по updated_at и где она теряет данные.
  • Что такое log-based CDC, Debezium и формат его событий.
  • Как применить поток изменений к таблице в хранилище с учётом удалений.
  • Паттерн outbox и зачем он нужен.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.