Real-Time Backend
Change Data Capture
Интернет-магазин обновил цену товара в PostgreSQL. Через сколько миллисекунд об этом должны узнать Elasticsearch (поиск), Redis (кеш), аналитика и партнёрский API? Ответ Shopify - менее 500. Секрет: они читают не БД, а её журнал.
- Netflix синхронизирует каталог фильмов между PostgreSQL и Elasticsearch через Debezium - изменения метаданных фильма появляются в поиске менее чем через секунду без единого `SELECT`
- Airbnb строит near-real-time аналитику бронирований через PostgreSQL WAL -> Kafka -> Hive: задержка от OLTP-записи до аналитического дашборда - менее 1 минуты вместо ночного ETL
- Shopify использует CDC для синхронизации inventory между складской БД и системами управления заказами: каждое изменение остатка немедленно блокирует overselling
- LinkedIn разработал Databus (2012) - одну из первых CDC-систем на базе MySQL binlog - для синхронизации профилей пользователей между сервисами; идеи Databus легли в основу современного Debezium
Что такое CDC
**Change Data Capture (CDC)** - это подход, при котором система отслеживает каждое изменение в базе данных (INSERT / UPDATE / DELETE) и публикует его как событие в поток. Вместо того чтобы периодически делать `SELECT` и сравнивать снимки, CDC читает внутренний журнал транзакций - источник абсолютной истины, который БД ведёт и так.
Классический polling выглядит так: сервис каждые 5 секунд спрашивает `WHERE updated_at > last_check`. Проблема - удалённые строки исчезают бесследно, а `updated_at` нужно добавлять руками в каждую таблицу. CDC не требует ни того ни другого: он видит DELETE-запись прямо в лог-файле.
Три источника данных для CDC по возрастанию надёжности: **триггеры БД** (медленно, нагружают БД), **временные метки** (не ловят DELETE), **журнал транзакций** (WAL/binlog - промышленный стандарт).
- **Log-based CDC** читает WAL (PostgreSQL) или binlog (MySQL) - без доп. нагрузки на таблицы
- **Событие CDC** содержит: тип операции, таблицу, старые и новые значения полей, LSN/позицию в журнале
- **At-least-once delivery** - стандарт; потребитель должен уметь обрабатывать дублирующиеся события идемпотентно
- **Initial snapshot** - перед стримингом нужно выгрузить текущее состояние таблицы; Debezium делает это автоматически
Polling с `updated_at` не замечает одну категорию изменений. Какую?
Debezium
**Debezium** - опенсорсный CDC-движок от Red Hat, де-факто стандарт в экосистеме Kafka. Он работает как набор Kafka Connect коннекторов: читает журнал транзакций каждой БД и публикует события в Kafka-топики. Netflix использует Debezium для синхронизации 10+ микросервисов; LinkedIn - для репликации пользовательского профиля между дата-центрами.
Debezium поддерживает PostgreSQL (через logical replication slots), MySQL/MariaDB (binlog), MongoDB (oplog), Oracle (LogMiner), SQL Server (CDC feature). Один коннектор = одна БД-инстанция.
Shopify использует Debezium для CDC своей inventory-базы: каждое изменение остатков на складе немедленно попадает в Kafka и оттуда - в системы управления заказами, аналитику и партнёрские API. Задержка от записи в БД до обработки downstream - менее 500 мс.
- **Replication slot** - PostgreSQL хранит WAL до тех пор, пока Debezium его не прочитал; при долгом простое слот может заблокировать очистку WAL и переполнить диск
- **Offset storage** - Debezium сохраняет последний прочитанный LSN в Kafka (топик `connect-offsets`); при рестарте продолжает с этой точки
- **Schema registry** - события Debezium используют Avro/JSON Schema; Confluent Schema Registry или Apicurio хранят эволюцию схем
Debezium-коннектор был остановлен на 48 часов из-за инцидента. Какой побочный эффект наиболее вероятен в PostgreSQL?
PostgreSQL Logical Replication
**Logical replication** в PostgreSQL работает поверх WAL (Write-Ahead Log) - журнала, в который PG записывает каждое изменение перед применением. Физическая репликация копирует байты страниц напрямую; логическая декодирует WAL в читаемые SQL-операции и отдаёт их подписчику через replication slot.
Плагин **pgoutput** - встроен в PostgreSQL 10+, не требует установки. Альтернатива **wal2json** выдаёт изменения в JSON прямо из PG - удобно для скриптов, но менее производительно.
Airbnb строит свою data-платформу на PostgreSQL logical replication: события о новых бронированиях, изменениях цен и обновлениях листингов через WAL попадают в Kafka, откуда расходятся в аналитическое хранилище (Hive/Presto), ML-пайплайны и системы fraud detection. Задержка от записи в OLTP до аналитики - менее 1 минуты.
- Поставить `wal_level = logical` в `postgresql.conf` и перезапустить PG
- Создать пользователя с ролью REPLICATION: `CREATE ROLE debezium REPLICATION LOGIN PASSWORD '...'`
- Выдать права на таблицы: `GRANT SELECT ON TABLE orders TO debezium`
- Создать PUBLICATION для нужных таблиц
- Запустить Debezium-коннектор - он сам создаст replication slot
В PostgreSQL для logical replication требуется параметр `wal_level`. Какое значение нужно установить?
CDC Patterns и применение
CDC используется в нескольких устойчивых паттернах. **Outbox Pattern** решает проблему двойной записи: сервис записывает бизнес-событие в таблицу `outbox` в той же транзакции, что и основное изменение, а CDC доставляет его в Kafka. Гарантия: либо оба действия произошли, либо ни одно.
**CQRS + CDC**: команды пишут в OLTP (PostgreSQL), CDC доставляет изменения в read-модели (Elasticsearch, Redis, ClickHouse). Каждая read-модель оптимизирована под свой паттерн запросов - без дублирования логики синхронизации.
- **Event Sourcing via CDC** - MySQL binlog как неизменяемый лог событий; LinkedIn использует этот паттерн для Databus (предшественник Kafka)
- **Cache Invalidation** - Redis-кеш сбрасывается точечно по CDC-событию, а не по TTL; снижает staleness без лишних запросов к БД
- **Search Index Sync** - Elasticsearch индекс обновляется через CDC; Netflix так синхронизирует каталог фильмов между PostgreSQL и поиском
- **Analytics ETL** - ClickHouse принимает CDC-поток и строит аналитические агрегаты в near-real-time вместо ночных батч-джобов
Критический момент: CDC гарантирует **at-least-once** доставку. Потребитель может получить одно событие дважды (при рестарте коннектора). Решение - идемпотентная обработка: использовать `id` события или LSN как ключ при вставке в целевую систему (`INSERT ... ON CONFLICT DO NOTHING`).
CDC - это просто ещё один способ делать ETL, только быстрее
CDC меняет парадигму: вместо периодического копирования состояния система получает непрерывный поток изменений. Это позволяет строить event-driven архитектуры, где downstream-системы реагируют на факты, а не опрашивают источник.
ETL работает со снимками данных и теряет промежуточные состояния (если запись была создана и удалена между двумя запусками ETL - ETL этого не увидит). CDC видит каждое изменение, что принципиально для аудита, event sourcing и near-real-time аналитики.
Сервис должен атомарно обновить запись в БД и опубликовать событие в Kafka. Без Outbox Pattern это проблема, потому что...
Итоги
- **Log-based CDC** читает WAL (PostgreSQL) или binlog (MySQL) и превращает каждую транзакцию в событие - без polling, без дополнительной нагрузки на таблицы, с захватом DELETE
- **Debezium + Kafka** - промышленный стандарт: коннектор читает replication slot, публикует события в топики, сохраняет позицию (LSN) - при рестарте продолжает с той же точки
- **Outbox Pattern** через CDC решает проблему двойной записи атомарно: обновление БД и публикация события в одной транзакции, CDC доставляет событие асинхронно
- **At-least-once delivery** требует идемпотентной обработки на стороне потребителя - использовать LSN или event ID как ключ дедупликации
Связанные темы
CDC связан с несколькими фундаментальными темами распределённых систем:
- Kafka и event streaming — CDC-события публикуются в Kafka-топики; Kafka обеспечивает дурабельность и replay для downstream-потребителей
- Outbox Pattern — CDC - транспортный уровень для Outbox: атомарная запись в БД + асинхронная доставка события без двухфазного коммита
- CQRS — CDC синхронизирует write-модель (OLTP) с read-моделями (Elasticsearch, Redis, ClickHouse) - основа CQRS в распределённых системах
- WAL и транзакционный журнал — CDC стоит поверх WAL - понимание структуры журнала помогает диагностировать задержки и проблемы с replication slots
Вопросы для размышления
- Replication slot в PostgreSQL накапливает WAL, пока потребитель не прочитает. Как организовать мониторинг, чтобы не допустить переполнения диска при падении Debezium?
- Downstream-сервис получил CDC-событие UPDATE дважды из-за рестарта коннектора. Какие конкретные механизмы идемпотентности применимы в зависимости от целевой системы (PostgreSQL, Elasticsearch, Redis)?
- В каких сценариях CDC избыточен и проще использовать polling? Где граница между разумным polling и необходимостью CDC?