Performance Tuning с числовыми целями
Самый распространённый совет по оптимизации Kafka: “увеличьте batch.size”. Это бесполезно без конкретных цифр. Этот урок устроен иначе: каждая рекомендация сопровождается конкретным значением, конкретной целью и конкретным trade-off.
Два базовых режима: throughput-optimized и latency-optimized. Они конфликтуют — нельзя максимизировать оба одновременно. Выбор определяется use case.
Два режима: throughput vs latency
Producer throughput optimization: конкретные числа
Baseline конфигурация для throughput-оптимизированного producer:
# Throughput-optimized producer
batch.size=1048576 # 1 MB (default: 16384 = 16 KB)
linger.ms=50 # ждать 50ms для заполнения batch (default: 0)
compression.type=lz4 # быстрое сжатие (default: none)
buffer.memory=67108864 # 64 MB буфер (default: 33554432 = 32 MB)
max.in.flight.requests.per.connection=5 # параллельных запросов (default: 5)
acks=all # максимальная durability
Цель: 100K+ records/s при avg msg size 500 bytes = 50 MB/s
producer.send()
producer.send() -- запись поступает в RecordAccumulator. Данный момент: начало отсчёта linger.ms.Брокер (ACK)
Сжатый batch отправляется брокеру. max.in.flight.requests.per.connection=5 позволяет держать 5 незавершённых batch в полёте -- конвейеризация повышает throughput. ACK получен -- batch из буфера удаляется.Почему batch.size=1MB, а не 16KB (default)?
При 16KB batch и 100K records/s по 500 bytes:
- Throughput в байтах: 100,000 * 500 = 50 MB/s
- Размер batch: 16 KB
- Количество запросов к брокеру: 50 MB / 16 KB = ~3,200 запросов/с
- Каждый запрос: round trip к брокеру (~1-5ms), CPU overhead на обе стороны
При 1MB batch и той же нагрузке:
- Количество запросов к брокеру: 50 MB / 1 MB = 50 запросов/с
- В 64 раза меньше запросов — соответственно ниже overhead
Наблюдаемые результаты:
- batch.size=1MB + linger.ms=50 + compression=lz4: 200K-500K records/s (зависит от msg size)
- batch.size=16KB + linger.ms=0: 20K-50K records/s
Producer latency optimization: конкретные числа
# Latency-optimized producer
acks=1 # только лидер-ACK (не ждём ISR)
batch.size=16384 # 16 KB (default) — меньше ждать заполнения
linger.ms=0 # отправлять немедленно, не ждать
compression.type=none # нет сжатия = нет CPU-задержки
buffer.memory=33554432 # 32 MB (default) достаточно
max.block.ms=5000 # 5 сек блокировки при полном буфере
Цель: менее 5ms p99 produce latency (в пределах одного датацентра)
Consumer throughput optimization: конкретные числа
# Throughput-optimized consumer
fetch.min.bytes=1048576 # ждать 1MB данных перед возвратом fetch (default: 1)
fetch.max.wait.ms=500 # максимум 500ms ждать fetch.min.bytes (default: 500)
max.poll.records=1000 # возвращать до 1000 записей за poll() (default: 500)
fetch.max.bytes=52428800 # максимум 50MB в ответе на fetch (default: 52428800)
max.partition.fetch.bytes=1048576 # 1MB per partition per fetch (default: 1048576)
Цель: consumer удерживает lag менее 1000 записей при input rate = 50K records/s
Почему fetch.min.bytes=1MB важно?
При fetch.min.bytes=1 (default): брокер отвечает на каждый fetch-запрос немедленно, даже если данных нет. При интенсивном потреблении это создаёт тысячи пустых fetch-запросов в секунду.
При fetch.min.bytes=1MB: брокер ждёт накопления 1MB данных (или истечения fetch.max.wait.ms=500ms). Количество fetch-запросов резко падает, снижается overhead на обе стороны.
Масштабирование consumer group:
Максимальный параллелизм = число партиций топика. Нельзя назначить двух consumers на одну партицию в рамках одной группы.
# Пример: топик orders, 12 партиций
# Consumer group с 6 инстансами: каждый обрабатывает 2 партиции
# Consumer group с 12 инстансами: каждый обрабатывает 1 партицию (максимальный параллелизм)
# Consumer group с 13 инстансами: один инстанс будет idle (лишний)
Broker-side tuning: параметры I/O
# Broker thread configuration
num.io.threads=8 # I/O потоки (default: 8, увеличить если RequestHandlerAvgIdlePercent < 0.3)
num.network.threads=3 # сетевые потоки (default: 3, увеличить при высоком connection count)
num.replica.fetchers=1 # потоки для репликации от фолловеров (default: 1, увеличить если follower lag растёт)
# Socket buffers
socket.send.buffer.bytes=102400 # TCP send buffer (100 KB)
socket.receive.buffer.bytes=102400 # TCP receive buffer (100 KB)
socket.request.max.bytes=104857600 # Максимальный размер запроса (100 MB)
Когда увеличивать num.io.threads:
RequestHandlerAvgIdlePercent ниже 0.30 в течение 10 минут → попробовать увеличить num.io.threads с 8 до 12 или 16. Каждый I/O поток обрабатывает сетевые запросы: Produce, Fetch, Metadata. При высокой нагрузке очередь заполняется.
Когда увеличивать num.replica.fetchers:
kafka.server:type=ReplicaFetcherManager,name=MaxLag растёт → фолловеры не успевают реплицировать. Увеличение num.replica.fetchers с 1 до 2-4 позволяет нескольким потокам параллельно реплицировать от разных лидеров.
Сравнение алгоритмов сжатия: числа
End-to-end latency: анатомия задержки
consumer.poll()
consumer.poll() вернул запись. Суммарная latency: при linger.ms=0, acks=1, активном потоке данных = 1-5ms p99. При linger.ms=50, acks=all = 60-150ms p99.Реальные числа end-to-end latency (в пределах одного датацентра):
| Конфигурация | p50 | p99 | Сценарий |
|---|---|---|---|
| linger.ms=0, acks=1 | 1ms | 5ms | Latency-критичные системы |
| linger.ms=0, acks=all, RF=3 | 3ms | 20ms | Дюрабельность + низкая задержка |
| linger.ms=50, acks=all, RF=3 | 55ms | 100ms | Throughput-оптимизированный |
| linger.ms=50, acks=all, сжатие lz4 | 57ms | 110ms | Throughput + сжатие |
log.flush.interval.messages и log.flush.interval.ms — НЕ настраивайте в production. Kafka намеренно использует OS page cache для записи и периодически делает fsync (управляемый операционной системой). Принудительный flush после каждого сообщения (log.flush.interval.messages=1) снижает throughput в 10-100 раз. Durability обеспечивается через replication (acks=all, min.insync.replicas=2), не через fsync. Это фундаментальное архитектурное решение Kafka. Не нарушайте его.
batch.size=1048576 (1 MB) + linger.ms=50 + compression.type=lz4 — это baseline для throughput-оптимизированного producer. Начните с этих значений и корректируйте на основе JMX метрик: batch-size-avg (заполняется ли batch?) и record-queue-time-avg (сколько записей ждут в буфере?).