Skip to content
Learning Platform
Intermediate
25 minutes
mysql postgresql binlog wal cdc comparison

Prerequisites:

  • module-8/04-mysql-connector-configuration
  • module-2/01-logical-decoding-deep-dive

Binlog vs WAL: Архитектурное Сравнение

В Модуле 2 вы освоили PostgreSQL CDC через WAL и logical decoding. Теперь, изучив MySQL binlog, вы можете увидеть, что эти системы решают одну задачу (CDC) совершенно разными способами.

Понимание архитектурных различий критически важно: если вы переносите знания PostgreSQL на MySQL напрямую, вы сделаете дорогие ошибки в production. В этом уроке мы систематизируем различия и построим ментальную модель для работы с обеими СУБД.

Зачем сравнивать?

Проблема: Многие инженеры изучают PostgreSQL CDC первым (он популярен в стартапах и data engineering) и ожидают, что MySQL работает аналогично.

Реальность:

  • MySQL не имеет replication slots — позицию хранит клиент (Kafka Connect offsets)
  • PostgreSQL не требует schema history topic — схема встроена в каждое сообщение
  • GTID (MySQL) работает иначе, чем LSN (PostgreSQL)
  • Failover сценарии принципиально различаются

Если вы изучали PostgreSQL в Модуле 2: Этот урок предотвратит распространенные заблуждения. Если нет — поймете, чем MySQL уникален.

Архитектурное сравнение: Таблица ключевых отличий

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

АспектPostgreSQL WALMySQL Binlog
Изначальное назначениеCrash recovery (durability)Replication (data distribution)
Формат данныхФизический (блоки страниц)Логический (row changes)
CDC механизмLogical decoding (pgoutput/wal2json)Прямое чтение (binlog уже логический)
Position trackingServer-side (replication slots)Client-side (Kafka Connect offsets)
Position форматLSN (hexadecimal, 0/16B374D8)GTID (uuid:sequence) или file:offset
DDL trackingImplicit (в WAL messages)Explicit (schema.history.internal.kafka.topic)
Schema в событияхВстроена (структура полей)Ссылка на table_id (требует reconstruction)
Failover handlingSlot мигрирует на репликуGTID автоматически релоцирует
Server-side stateReplication slot держит WALНет server-side state (binlog expire независимо)
Disk growth riskAbandoned slot → disk fullConnector lag → невозможность recovery

Ключевой вывод: PostgreSQL архитектура — “сервер помнит позицию и держит WAL”. MySQL архитектура — “клиент помнит позицию, сервер чистит binlog независимо”.

Position Tracking: Server-Side vs Client-Side

Это самое фундаментальное различие, которое влияет на все остальное.

PostgreSQL: Server-Side Position (Replication Slot)

PostgreSQL: Server-Side TrackingRecommended
Replication Slotrestart_lsn, confirmed_flush_lsn
+WAL сохраняется до подтверждения
+Автоматическое возобновление
!Abandoned slot → disk full
MySQL: Client-Side Tracking
Kafka Connect OffsetsGTID или file:position
+Нет влияния на сервер
+Failover проще с GTID
!Lag > retention → resnapshot

Преимущества server-side tracking:

  • Сервер гарантирует сохранение WAL до подтверждения клиента
  • Нет риска потерять события при crash Debezium
  • Переподключение возобновляет с точной позиции

Недостатки:

  • Abandoned slot → диск переполняется (WAL накапливается бесконечно)
  • Требует мониторинг pg_replication_slots и алерты на active=false
  • Cleanup слотов требует ручного вмешательства

MySQL: Client-Side Position (Kafka Connect Offsets)

PostgreSQL: LSN Position
Position Format0/16B374D8
+Монотонно возрастает
+Scope: single server
+Server-side хранение
Schema: встроена в событие
MySQL: GTID Position
Position Formatuuid:1-150
+Глобально уникален в кластере
+Failover прозрачен
!Client-side хранение
Schema: требует history topic

Преимущества client-side tracking:

  • Нет server-side state — чище для сервера
  • Abandoned connector не влияет на MySQL (binlog чистится по расписанию)
  • Failover проще: GTID глобально уникален, можно переключиться на любую реплику

Недостатки:

  • Клиент ОБЯЗАН надежно хранить offset (Kafka Connect offsets topic)
  • Если connector down дольше binlog_expire_logs_seconds → невозможно восстановить
  • Требует мониторинг зазора между gtid_executed и connector offset
Проверка знаний
В чём ключевое различие между server-side (PostgreSQL) и client-side (MySQL) position tracking для CDC?
Ответ
PostgreSQL хранит позицию на сервере через replication slot, гарантируя сохранение WAL до подтверждения клиента. MySQL хранит позицию на стороне клиента (Kafka Connect offsets), и сервер чистит binlog независимо. Abandoned PostgreSQL slot переполняет диск, а abandoned MySQL connector теряет возможность восстановления после purge.

Position Format: LSN vs GTID

PostgreSQL LSN (Log Sequence Number)

-- LSN — 64-битное число, представленное как hexadecimal
SELECT pg_current_wal_lsn();
-- Пример: 0/16B374D8

-- LSN состоит из двух частей: timeline / offset
-- 0/16B374D8 = timeline 0, offset 381101272 bytes

Характеристики LSN:

  • Монотонно возрастает
  • Уникален в пределах одного PostgreSQL сервера
  • При failover timeline может измениться (1/…, 2/…)
  • Debezium отслеживает LSN через replication slot

Сравнение LSN:

-- Вычислить разницу между LSN (в байтах)
SELECT pg_wal_lsn_diff('0/16B374D8', '0/16000000');
-- Результат: 11777240 (bytes)

MySQL GTID (Global Transaction ID)

-- GTID — UUID:sequence_number
SHOW GLOBAL VARIABLES LIKE 'gtid_executed';
-- Пример: 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-150

-- UUID = уникальный идентификатор MySQL сервера
-- 1-150 = диапазон transaction IDs (выполнено 150 транзакций)

Характеристики GTID:

  • Глобально уникален в MySQL cluster (включая реплики)
  • Не зависит от файлов binlog (в отличие от file:offset)
  • При failover GTID продолжает нумерацию (no reset)
  • Debezium сохраняет GTID в Kafka Connect offsets

Ranges и Set операции:

-- Посмотреть очищенные GTIDs (недоступные для репликации)
SHOW GLOBAL VARIABLES LIKE 'gtid_purged';
-- Пример: 3E11FA47-71CA-11E1-9E33-C80AA9429562:1-100

-- Это значит: транзакции 1-100 удалены, доступны только 101+

Сравнение форматов

ХарактеристикаPostgreSQL LSNMySQL GTID
ТипByte offsetTransaction ID
ScopeSingle serverEntire cluster
FailoverTimeline изменяется, LSN может сброситьсяGTID глобально уникален, failover прозрачен
FormatHexadecimal (0/16B374D8)UUID:range (uuid:1-150)
Хранение на сервереReplication slot (restart_lsn)Нет (только executed/purged)
Хранение у клиентаНе требуется (slot помнит)Kafka Connect offsets topic

Практическое значение: GTID делает MySQL failover проще для CDC. PostgreSQL требует внимания к timeline changes при switchover.

Schema Evolution: Implicit vs Explicit Tracking

PostgreSQL: Schema Embedded in Messages

PostgreSQL logical decoding встраивает полную схему в каждое сообщение WAL:

// PostgreSQL WAL message (pgoutput format)
{
  "schema": {
    "type": "struct",
    "fields": [
      {"field": "id", "type": "int32"},
      {"field": "name", "type": "string"},
      {"field": "email", "type": "string"}
    ]
  },
  "payload": {
    "before": null,
    "after": {
      "id": 1,
      "name": "Alice",
      "email": "[email protected]"
    }
  }
}

Почему это работает:

  • Logical decoding plugin (pgoutput) знает текущую схему таблицы
  • Каждое событие самодостаточно
  • Нет необходимости в отдельном хранилище DDL истории

MySQL: Schema History Topic Required

MySQL binlog НЕ содержит полную схему в каждом событии. Вместо этого используется TABLE_MAP_EVENT:

// Binlog events последовательность
TABLE_MAP_EVENT: table_id=108, database='inventory', table='customers'
  Columns: [id INT, name VARCHAR, email VARCHAR]

WRITE_ROWS_EVENT: table_id=108
  Row: {id: 1, name: 'Alice', email: '[email protected]'}

Проблема: table_id — это runtime идентификатор, который сбрасывается при рестарте MySQL. Debezium не может полагаться только на него.

Решение: Debezium записывает ВСЕ DDL изменения в отдельный Kafka topic:

// Содержимое schema.history.internal.kafka.topic
{
  "source": {
    "server": "mysql-server",
    "gtid": "3E11FA47-71CA-11E1-9E33-C80AA9429562:47"
  },
  "ddl": "CREATE TABLE customers (id INT PRIMARY KEY, name VARCHAR(255), email VARCHAR(255))",
  "ts_ms": 1706788800000
}

Почему это необходимо:

  • При рестарте connector Debezium читает schema history topic
  • Восстанавливает mapping: table_id → schema для каждого GTID
  • Корректно декодирует старые binlog события при recovery
PostgreSQL WAL Architecture
PostgreSQL Server
Записывает изменения
WAL Segments
Гарантирует сохранность
Replication Slot
pgoutput plugin
Logical Decoder
Структурированные события
Debezium CDC
MySQL Binlog Architecture
MySQL Server
Записывает ROW events
Binlog Files
binlog_expire_logs_seconds
Retention Policy
GTID tracking
Client-Side Offset
+ Schema History Topic
Debezium CDC

Критическое требование для MySQL:

// Обязательная конфигурация MySQL connector
{
  "schema.history.internal.kafka.topic": "schema-changes.mysql-server",
  "schema.history.internal.kafka.bootstrap.servers": "kafka:9092"
}

Topic retention MUST be infinite:

kafka-topics --bootstrap-server kafka:9092 \
  --create \
  --topic schema-changes.mysql-server \
  --config retention.ms=-1 \
  --config retention.bytes=-1

Если schema history topic удален или corrupted: Connector не сможет стартовать. Потребуется полный resnapshot базы данных.

Connector Configuration: Сравнение свойств

Давайте сравним конфигурации коннекторов для одной задачи: capture изменений таблиц customers и orders.

PostgreSQL Connector

{
  "name": "postgres-inventory-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",

    // Connection
    "database.hostname": "postgres",
    "database.port": "5432",
    "database.user": "debezium",
    "database.password": "dbz",
    "database.dbname": "inventory",

    // PostgreSQL-specific: Logical decoding configuration
    "plugin.name": "pgoutput",
    "slot.name": "debezium_inventory",
    "publication.name": "dbz_publication",

    // Table filtering
    "table.include.list": "public.customers,public.orders",

    // Topic naming
    "topic.prefix": "postgres-server"
  }
}

MySQL Connector

{
  "name": "mysql-inventory-connector",
  "config": {
    "connector.class": "io.debezium.connector.mysql.MySqlConnector",

    // Connection (identical pattern)
    "database.hostname": "mysql",
    "database.port": "3306",
    "database.user": "debezium",
    "database.password": "dbz",

    // MySQL-specific: Cluster integration
    "database.server.id": "184054",
    "database.server.name": "mysql-server",

    // MySQL-specific: Schema history storage
    "schema.history.internal.kafka.topic": "schema-changes.mysql-server",
    "schema.history.internal.kafka.bootstrap.servers": "kafka:9092",

    // Table filtering (identical pattern)
    "table.include.list": "inventory.customers,inventory.orders",

    // Topic naming (identical pattern)
    "topic.prefix": "mysql-server"
  }
}

Свойства без эквивалента

PostgreSQL OnlyMySQL Only
plugin.name (pgoutput/wal2json)database.server.id (cluster member ID)
slot.name (replication slot)schema.history.internal.* (DDL tracking)
publication.name (table filter)gtid.source.includes (GTID filtering)
slot.drop.on.stop (cleanup control)binlog.buffer.size (event buffering)

Общие свойства:

  • database.hostname, database.port, database.user, database.password
  • table.include.list / table.exclude.list
  • topic.prefix
  • snapshot.mode
  • heartbeat.interval.ms / heartbeat.topics.prefix

Заблуждение: “Я настроил PostgreSQL connector, MySQL будет аналогично”. Реальность: MySQL требует уникальные свойства (database.server.id, schema.history.*), без которых connector не работает.

Monitoring Metrics: Что измерять

PostgreSQL Metrics

-- Ключевые метрики для PostgreSQL CDC
SELECT
    slot_name,
    active,
    -- Position lag (bytes)
    pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS lag_bytes,
    pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) AS lag_pretty,
    -- WAL disk retention
    wal_status,
    safe_wal_size,
    -- Replication lag
    pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) AS replication_lag_bytes
FROM pg_replication_slots
WHERE slot_type = 'logical';

Критичные алерты:

  • active = false AND lag_bytes > 1GB → Abandoned slot warning
  • wal_status = 'lost' → WAL deleted, resnapshot required
  • lag_bytes > safe_wal_size → Approaching disk full

MySQL Metrics

-- Ключевые метрики для MySQL CDC
-- 1. Current binlog position
SHOW MASTER STATUS;

-- 2. GTID tracking
SHOW GLOBAL VARIABLES LIKE 'gtid_executed';
SHOW GLOBAL VARIABLES LIKE 'gtid_purged';

-- 3. Binlog retention
SHOW VARIABLES LIKE 'binlog_expire_logs_seconds';
SHOW BINARY LOGS;

-- 4. Schema history topic health (через Kafka APIs)
-- kafka-console-consumer --topic schema-changes.mysql-server --from-beginning

Критичные алерты:

  • gtid_purged близок к connector offset → Risk of data loss
  • Binlog file count резко падает → Purge occurred, check connector lag
  • Schema history topic lag > 1 hour → DDL not being tracked

Сравнительная таблица метрик

МетрикаPostgreSQLMySQL
Position lagpg_wal_lsn_diff(current, restart_lsn)gtid_executed - connector offset
Data retentionwal_status, safe_wal_sizebinlog_expire_logs_seconds, gtid_purged
Connection healthactive status in pg_replication_slotsBinlog reader thread status (JMX)
Schema syncN/A (schema in events)Schema history topic lag
Slot/state healthSlot exists and activeN/A (client-side state)

Failover Behavior: Slot Migration vs GTID Relocation

PostgreSQL Failover (With Replication Slots)

PostgreSQL Replication Modes
Physical Replication
+ Побайтовая копия
+ Только PG → PG
+ Вся база целиком
- Не для CDC
Logical Replication
+ Структурированные события
+ Выборочные таблицы
+ Кросс-версионность
+ CDC через Debezium
MySQL Replication Modes
Traditional Replication
+ Простая настройка
+ Single-master
+ Binlog уже логический
+ CDC через Debezium
Group Replication
+ Multi-master
+ Automatic failover
+ GTID обязателен
+ CDC через GTID

PostgreSQL failover сложности:

  • Replication slots по умолчанию НЕ реплицируются на standby
  • Требуется настройка hot_standby_feedback = on и primary_slot_name на replica
  • Если slot не мигрировал → нужно пересоздать, возможна потеря LSN tracking

MySQL Failover (With GTID Mode)

PostgreSQL CDC Requirements
Обязательные параметры для CDC
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10
После изменения параметров требуется restart PostgreSQL
MySQL CDC Requirements
Обязательные параметры для CDC
binlog_format = ROW
binlog_row_image = FULL
gtid_mode = ON
Aurora MySQL: настраивается через Parameter Groups

MySQL failover преимущества (GTID mode):

  • GTID глобально уникален — одинаковый на primary и replicas
  • Debezium просто переподключается к новому primary с последним GTID
  • Нет server-side state для миграции
  • Автоматическое восстановление без manual intervention

Без GTID (file:offset mode):

  • Требуется вычислить новый (binlog_file, offset) на replica
  • Высокий риск потери данных или дубликатов
  • Не рекомендуется для production CDC
Проверка знаний
Почему MySQL connector требует schema history topic, а PostgreSQL connector — нет?
Ответ
PostgreSQL logical decoding встраивает полную информацию о схеме таблиц в каждое WAL-событие, делая их самодостаточными. MySQL binlog содержит только TABLE_MAP с table_id, поэтому Debezium должен хранить DDL-историю в отдельном Kafka topic для восстановления схем при перезапуске.

Common Misconceptions: Распространенные заблуждения

Эти заблуждения возникают у 90% инженеров, переходящих с PostgreSQL на MySQL или наоборот.

ЗаблуждениеРеальность
”MySQL has replication slots”Нет. MySQL использует client-side position tracking (Kafka Connect offsets). Нет server-side объекта, аналогичного PostgreSQL replication slot.
”PostgreSQL needs schema history topic”Нет. PostgreSQL logical decoding включает schema в каждое WAL сообщение. Schema history topic — MySQL-специфичная необходимость.
”Binlog is physical like WAL”Нет. Binlog (в ROW format) — логический формат, записывает row changes. PostgreSQL WAL — физический, записывает page blocks (требует logical decoding).
”Both use same offset format”Нет. PostgreSQL использует LSN (hexadecimal byte offset). MySQL использует GTID (UUID:sequence) или file:offset.
”Abandoned connector is safe in both”Нет. PostgreSQL abandoned slot → диск переполняется WAL. MySQL abandoned connector → binlog purge продолжается, но recovery невозможен после purge.
”Failover работает одинаково”Нет. PostgreSQL требует slot replication. MySQL (GTID mode) — автоматическое failover без server-side state.
”Schema changes tracked same way”Нет. PostgreSQL — implicit (schema в WAL). MySQL — explicit (schema.history.internal.kafka.topic обязателен).

When to Choose Which: Практические рекомендации

Этот курс покрывает обе СУБД, но когда выбирать какую для новых проектов?

PostgreSQL для CDC (предпочтительно)

Когда использовать:

  • Greenfield проекты (новые системы с нуля)
  • Приложения с частыми schema changes (logical decoding гибче)
  • Когда нужна JSONB support в CDC (pgoutput захватывает JSONB корректно)
  • Транзакции с высоким уровнем изоляции (SERIALIZABLE)
  • Open-source ориентированная организация

Почему:

  • Logical decoding — более современный подход
  • Schema embedded in messages — меньше зависимостей
  • Managed services (AWS RDS, Azure PostgreSQL) полностью поддерживают logical replication
  • Активное community и регулярные улучшения

MySQL для CDC (часто унаследованное)

Когда использовать:

  • Legacy системы уже на MySQL (миграция дороже)
  • AWS Aurora MySQL (managed MySQL с CDC support)
  • Когда binlog уже используется для репликации (infrastructure готова)
  • Высоконагруженные OLTP с simple schema (binlog эффективнее для простых структур)

Почему:

  • Binlog — проверенная технология (20+ лет в production)
  • GTID mode — надежное failover без сложной настройки
  • Меньше server-side overhead (нет slot state)
  • Хорошая поддержка в managed services (RDS, Aurora)

Для AWS Aurora MySQL специфически:

  • Aurora MySQL binlog поддерживается из коробки
  • Параметр binlog_format=ROW настраивается через parameter groups
  • Automatic failover с GTID работает seamlessly

Практический совет: Если вы начинаете новый проект — рассмотрите PostgreSQL. Если вы поддерживаете существующую MySQL систему — используйте binlog CDC (не мигрируйте ради миграции).

Key Takeaways: Ключевые выводы

  1. Position tracking: PostgreSQL — server-side (replication slots), MySQL — client-side (Kafka offsets)

  2. Position format: PostgreSQL LSN (hexadecimal byte offset), MySQL GTID (UUID:sequence)

  3. Schema evolution: PostgreSQL — implicit (schema in WAL), MySQL — explicit (schema history topic mandatory)

  4. Failover: PostgreSQL требует slot replication, MySQL GTID — automatic relocation

  5. Disk growth risk: PostgreSQL abandoned slot → WAL накапливается. MySQL lag → binlog purge делает recovery невозможным

  6. Configuration: MySQL требует database.server.id и schema.history.internal.*, которых нет в PostgreSQL

  7. Monitoring: PostgreSQL — pg_replication_slots, MySQL — gtid_executed vs gtid_purged

  8. CDC complexity: PostgreSQL требует logical decoding setup. MySQL — прямое чтение binlog (проще для старта)

  9. Schema history topic: MySQL ОБЯЗАТЕЛЬНО, PostgreSQL НЕ нужен

  10. Replication slots: PostgreSQL центральная концепция, MySQL НЕ имеет эквивалента

What’s Next: Что дальше?

Мы систематизировали архитектурные различия между MySQL binlog и PostgreSQL WAL. Теперь вы знаете, почему MySQL connector настраивается иначе и какие подводные камни специфичны для каждой СУБД.

В следующем уроке мы углубимся в MySQL schema history topic — самый критичный MySQL-специфичный компонент. Вы узнаете:

  • Как Debezium восстанавливает schema при restart
  • Почему schema history topic MUST have infinite retention
  • Как debugging schema history corruption
  • Операционные процедуры для schema history topic maintenance

Это знание критично для production MySQL CDC — schema history corruption — одна из самых частых причин connector failures.

Check Your Understanding

Score: 0 of 0
Conceptual
Question 1 of 4. В чём ключевое архитектурное различие между PostgreSQL WAL и MySQL binlog как источниками данных для CDC?

Finished the lesson?

Mark it as complete to track your progress