Масштабирование коннектора: Правда о tasks.max
Один из самых распространенных мифов в мире Debezium: “Чтобы увеличить пропускную способность PostgreSQL коннектора, установите tasks.max=4”. Это неправда, и попытки так сделать не дадут никакого эффекта.
В этом уроке мы разберем, почему PostgreSQL коннектор принципиально ограничен одной задачей, и изучим реальные стратегии масштабирования CDC pipeline.
Миф: tasks.max увеличивает throughput
Myth: “Мой PostgreSQL коннектор медленный. Установлю
tasks.max=4для параллелизма.”
Reality: PostgreSQL коннектор Debezium ВСЕГДА использует только 1 task, независимо от значения
tasks.max. Это не баг — это архитектурное ограничение.
Доказательство
Создайте коннектор с tasks.max: "4" и проверьте количество tasks:
# Конфигурация с tasks.max=4
curl -X POST http://localhost:8083/connectors \
-H "Content-Type: application/json" \
-d '{
"name": "inventory-connector",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres",
"database.port": "5432",
"database.user": "postgres",
"database.password": "postgres",
"database.dbname": "inventory",
"topic.prefix": "inventory",
"tasks.max": "4"
}
}'
# Проверить количество tasks
curl http://localhost:8083/connectors/inventory-connector/status | jq '.tasks | length'
# Результат: 1 (не 4!)
Почему PostgreSQL коннектор single-task?
Причина кроется в архитектуре PostgreSQL WAL (Write-Ahead Log).
WAL — последовательный журнал
WAL — это последовательный журнал транзакций. Logical decoding читает его строго по порядку от позиции restart_lsn. Невозможно распараллелить чтение, потому что:
- Порядок имеет значение: События должны обрабатываться в порядке их записи в WAL
- Одна позиция: Replication slot отслеживает одну позицию, не несколько
- Транзакционная целостность: Все изменения одной транзакции должны обрабатываться атомарно
Сравнение с другими коннекторами
| Коннектор | Поддержка tasks.max | Причина |
|---|---|---|
| PostgreSQL | Нет (только 1) | WAL последовательный, одна точка чтения |
| MySQL | Нет (только 1) | Binlog последовательный |
| MongoDB | Да (множество) | Каждый shard = отдельный поток |
| SQL Server | Да (несколько) | Каждая БД = отдельный capture instance |
| Oracle | Нет (только 1) | LogMiner читает последовательно |
Архитектурная причина
Проверка знанийПочему PostgreSQL коннектор Debezium принципиально ограничен одной task, даже если установить tasks.max = 10?
Реальные стратегии масштабирования
Раз tasks.max не работает, что делать? Есть три основных подхода.
Стратегия 1: Множественные коннекторы
Когда использовать: Разные наборы таблиц имеют независимых потребителей.
Разделение таблиц по доменам с независимыми коннекторами
- Независимые replication slots
- Изоляция failures
- Per-domain monitoring
- Больше WAL retention (N slots)
- Больше ресурсов Connect
- Сложнее управлять
Конфигурация:
// orders-connector
{
"name": "orders-connector",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres",
"database.dbname": "inventory",
"topic.prefix": "orders",
"table.include.list": "public.orders,public.order_items",
"slot.name": "debezium_orders"
}
}
// inventory-connector
{
"name": "inventory-connector",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres",
"database.dbname": "inventory",
"topic.prefix": "inventory",
"table.include.list": "public.products,public.inventory",
"slot.name": "debezium_inventory"
}
}
Преимущества:
- Независимые replication slots
- Независимые offsets
- Изоляция failures
Недостатки:
- Каждый коннектор создает отдельный slot (больше WAL retention)
- Больше ресурсов Kafka Connect
- Сложнее управлять
Стратегия 2: Downstream параллелизация
Когда использовать: Один коннектор, но нужна параллельная обработка.
Параллелизм на стороне consumer через Kafka partitions
- Один коннектор, один slot
- Параллелизм на consumer
- Ordering per-key гарантирован
- Debezium остается single-threaded
- Bottleneck в WAL reader
- Требует partition-aware consumers
Как работает:
- Debezium пишет события в Kafka topic
- Topic имеет N партиций (key = primary key таблицы)
- N consumer instances читают параллельно
- События одного ключа всегда в одной партиции (ordering гарантирован)
Конфигурация Debezium:
{
"name": "inventory-connector",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"topic.prefix": "inventory",
"transforms": "route",
"transforms.route.type": "org.apache.kafka.connect.transforms.RegexRouter",
"transforms.route.regex": ".*",
"transforms.route.replacement": "inventory.cdc.events"
}
}
Создание топика с партициями:
kafka-topics --create \
--bootstrap-server kafka:9092 \
--topic inventory.cdc.events \
--partitions 8 \
--replication-factor 1
Преимущества:
- Один коннектор, один slot
- Параллелизм на стороне consumer
- Ordering гарантирован per-key
Недостатки:
- Debezium все еще single-threaded
- Bottleneck в WAL reader
Стратегия 3: Performance Tuning
Когда использовать: Максимизировать throughput одного коннектора.
{
"name": "high-throughput-connector",
"config": {
"connector.class": "io.debezium.connector.postgresql.PostgresConnector",
"database.hostname": "postgres",
"database.dbname": "inventory",
"topic.prefix": "inventory",
"max.queue.size": "16384",
"max.batch.size": "4096",
"poll.interval.ms": "500",
"snapshot.fetch.size": "10240",
"producer.override.batch.size": "131072",
"producer.override.linger.ms": "10",
"producer.override.compression.type": "lz4"
}
}
Параметры tuning:
| Параметр | Default | Tuned | Эффект |
|---|---|---|---|
max.queue.size | 8192 | 16384 | Больше буфер между WAL и Kafka |
max.batch.size | 2048 | 4096 | Больше events per commit |
poll.interval.ms | 1000 | 500 | Чаще poll (меньше latency) |
producer.override.batch.size | 16384 | 131072 | Больше batch к Kafka |
producer.override.linger.ms | 0 | 10 | Подождать для batch |
producer.override.compression.type | none | lz4 | Сжатие (меньше I/O) |
Performance Ceiling
Максимальная пропускная способность одного PostgreSQL коннектора:
~7,000 events/second (под оптимальными условиями)
Это примерное значение, зависящее от:
- Размера events (больше = медленнее)
- Network latency между Connect и Kafka
- Disk I/O на PostgreSQL
- Transforms complexity
Если нужно больше 7K events/sec:
- Используйте множественные коннекторы
- Архивируйте старые данные
- Рассмотрите партиционирование PostgreSQL
Decision Framework: Выбор стратегии
Проверка знанийКакое преимущество дает downstream параллелизация (Kafka partitions + consumer group) по сравнению с созданием дополнительных коннекторов?
Anti-Patterns
Anti-Pattern 1: tasks.max больше 1
{
"tasks.max": "4" // БЕСПОЛЕЗНО для PostgreSQL!
}
Проблема: Игнорируется. Вы думаете, что масштабировали, но ничего не изменилось.
Решение: Не тратьте время. Используйте реальные стратегии.
Anti-Pattern 2: Все таблицы в одном коннекторе
{
"table.include.list": "public.table1,public.table2,...,public.table100"
}
Проблема:
- Один slot для всех таблиц
- Failure одной таблицы блокирует все
- Трудно мониторить per-table lag
Решение: Группируйте таблицы по домену/SLO.
Anti-Pattern 3: Слишком маленький max.queue.size
{
"max.queue.size": "1024" // Слишком мало!
}
Проблема: Частые flush к Kafka, высокий overhead.
Решение: Минимум 8192, лучше 16384 для high-throughput.
Мониторинг throughput
Ключевые метрики
# Events per second (rate over 5 minutes)
curl -s http://localhost:9404/metrics | \
grep debezium_metrics_TotalNumberOfEventsSeen
# Queue utilization
curl -s http://localhost:9404/metrics | \
grep -E "QueueRemainingCapacity|QueueTotalCapacity"
PromQL запросы:
# Events per second
rate(debezium_metrics_TotalNumberOfEventsSeen{connector="inventory-connector"}[5m])
# Queue utilization %
100 * (1 - (
debezium_metrics_QueueRemainingCapacity /
debezium_metrics_QueueTotalCapacity
))
Когда беспокоиться
| Метрика | Warning | Critical | Действие |
|---|---|---|---|
| Events/sec | Падение более 20% | Падение более 50% | Проверить WAL reader |
| Queue util | Более 80% | Более 95% | Kafka bottleneck |
| Lag | Более 5s | Более 30s | Capacity review |
Lab: Measure Connector Throughput
Цель
Измерить текущий throughput и определить bottleneck.
Шаги
1. Создать нагрузку на БД:
docker exec -it postgres psql -U postgres -d inventory -c "
-- Создать таблицу для теста
CREATE TABLE IF NOT EXISTS load_test (
id SERIAL PRIMARY KEY,
data TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- Генерировать 10000 записей
INSERT INTO load_test (data)
SELECT md5(random()::text)
FROM generate_series(1, 10000);
"
2. Наблюдать throughput в Prometheus:
# Открыть Prometheus UI
open http://localhost:9090
# Query: rate of events
rate(debezium_metrics_TotalNumberOfEventsSeen[1m])
3. Проверить queue utilization:
100 * (1 - (
debezium_metrics_QueueRemainingCapacity /
debezium_metrics_QueueTotalCapacity
))
4. Определить bottleneck:
- Queue util высокий (более 80%): Kafka write bottleneck
- Queue util низкий, но throughput низкий: WAL read bottleneck
Ожидаемый результат
- Видите пик throughput при INSERT
- Queue utilization временно растет
- После INSERT throughput падает к baseline
Ключевые выводы
-
tasks.max НЕ работает для PostgreSQL — коннектор всегда использует 1 task
-
Причина: WAL — последовательный журнал, один reader
-
Реальные стратегии:
- Множественные коннекторы (разные наборы таблиц)
- Downstream параллелизация (Kafka partitions + consumers)
- Performance tuning (queue size, batch size, compression)
-
Performance ceiling: ~7,000 events/sec per connector
-
Monitoring: rate(TotalNumberOfEventsSeen), Queue utilization, Lag
Production Insight: Не теряйте время на tasks.max. Если throughput критичен — разделяйте на множественные коннекторы и используйте downstream parallelization. Один коннектор имеет жесткий потолок.
Check Your Understanding
Finished the lesson?
Mark it as complete to track your progress