Вопрос 9 · Раздел 15

Какие гарантии доставки сообщений предоставляет Kafka

Kafka предоставляет три уровня гарантий доставки:

Версии по языкам: English Russian Ukrainian

Уровень Junior

Три уровня гарантий

Kafka предоставляет три уровня гарантий доставки:

1. At most once (не более одного раза):

Сообщение доставится максимум один раз
Может потеряться при ошибках

Как теряется: продюсер отправил и забыл (acks=0). Если сеть упала — сообщение потеряно. Консьюмер автокоммитит offset до обработки — если консьюмер упал после коммита но до обработки — сообщение потеряно.

2. At least once (как минимум один раз):

Сообщение доставится минимум один раз
Может продублироваться при retry

3. Exactly once (ровно один раз):

Сообщение доставится ровно один раз
Работает ТОЛЬКО для Kafka-to-Kafka сценариев. Для финансовых операций с записью в БД нужен Outbox pattern или idempotent writes.

Визуализация

At most once:
  Продюсер → Kafka → (возможно потеряно) → Консьюмер

At least once:
  Продюсер → Kafka → Консьюмер → (возможно дубликат)

Exactly once:
  Продюсер → Kafka → Консьюмер → ровно один раз

Простая настройка

// At most once
props.put("acks", "0");

// At least once (рекомендуется)
props.put("acks", "all");
props.put("enable.idempotence", "true");

// Exactly once
props.put("enable.idempotence", "true");
props.put("isolation.level", "read_committed");

Когда НЕ использовать каждую семантику

  • At-most-once — НЕ для платежей, заказов, уведомлений
  • At-least-once — НЕ когда дубликаты критичны (без идемпотентной обработки)
  • Exactly-once — НЕ для Kafka-to-Database, НЕ для Kafka-to-HTTP

Уровень Middle

Настройка гарантий

At most once:

props.put("acks", "0");
props.put("enable.idempotence", "false");
props.put("enable.auto.commit", "true");
// Быстро, но возможна потеря данных

At least once:

props.put("acks", "all");
props.put("enable.idempotence", "true");
props.put("retries", Integer.MAX_VALUE);
props.put("enable.auto.commit", "false");
// Надёжно, но возможны дубликаты

Exactly once:

// Producer
props.put("enable.idempotence", "true");
props.put("acks", "all");

// Consumer
props.put("isolation.level", "read_committed");
props.put("enable.auto.commit", "false");

Transaction API для Exactly Once

producer.initTransactions();

try {
    producer.beginTransaction();

    // Чтение
    ConsumerRecords<String, String> records = consumer.poll();

    // Обработка
    for (var record : records) {
        process(record);
    }

    // Запись результата
    producer.send(new ProducerRecord<>("output", result));

    // Коммит обоих действий
    producer.commitTransaction();

} catch (Exception e) {
    // Откат при ошибке
    producer.abortTransaction();
}

Типичные ошибки

  1. At least once без идемпотентности:
    Retry → дубликаты в топике → обработка дважды
    
  2. Коммит до обработки:
    consumer.commitSync();  // сначала коммит
    process(records);       // потом обработка
    // Если упал → данные потеряны
    
  3. Exactly once без transaction:
    Producer и consumer не в одной транзакции
    → Возможна потеря или дублирование
    

Уровень Senior

Internal Implementation

Idempotent Producer:

Каждый producer получает уникальный PID (Producer ID)
Каждое сообщение получает Sequence Number
Брокер отслеживает последовательность

Дубликат с тем же PID + Sequence → отклоняется

Transaction Coordinator:

Отдельный компонент координирует транзакции
Хранит state транзакций в __transaction_state topic
Обеспечивает atomic commit/abort

Exactly Once — ограничения

Exactly Once работает только для:
  Kafka → Kafka (read-process-write)

Не работает для:
  Kafka → Database (Kafka не управляет транзакцией БД)
  Kafka → HTTP API (нет guarantees на стороне API)

Решение для внешних систем:

1. Idempotent writes в БД (unique constraints)
2. Two-phase commit (XA — не рекомендуется)
3. Outbox pattern
4. Debezium CDC

End-to-End Exactly Once

Сценарий: Kafka → Processing → PostgreSQL

Решение: Outbox Pattern
1. Записать в outbox таблицу (в той же транзакции)
2. Debezium CDC читает outbox
3. Отправляет в Kafka
4. Consumer обрабатывает и пишет в target table
5. Idempotent upsert по business key

Failure Scenarios

1. Producer failure:

Отправил → не получил ack → retry
Idempotent producer → дубликат отклонён

2. Broker failure:

Записал в Leader → не успел реплицировать → Leader упал
acks=all + min.insync.replicas=2 → данные сохранены

3. Consumer failure:

Прочитал → не обработал → не закоммитил
При рестарте → прочитает снова (at least once)
Idempotent processing → дубликат обработан корректно

Performance Trade-offs

Гарантия Latency Throughput Data Safety
At most once Минимальная Максимальная Низкая
At least once Средняя Высокая Высокая
Exactly once Высокая Средняя Максимальная

Best Practices

✅ At least once по умолчанию
✅ Exactly once когда критично (финансы, биллинг)
✅ Идемпотентная обработка на консьюмере
✅ Idempotent producer (enable.idempotence=true)
✅ Transactional read-process-write для Kafka-to-Kafka
✅ Outbox pattern для внешних систем

❌ At most once для важных данных
❌ Без обработки дубликатов
❌ Exactly once без понимания ограничений
❌ Коммит до обработки

Архитектурные решения

  1. At least once + idempotent consumer — оптимальный баланс
  2. Exactly only for Kafka-to-Kafka — не работает с внешними системами
  3. Outbox pattern — для end-to-end guarantees
  4. Idempotent writes — универсальное решение для внешних систем

Резюме для Senior

  • Exactly Once работает только в сценарии Kafka-to-Kafka
  • Для внешних систем используйте Outbox pattern или idempotent writes
  • At least once + idempotent consumer — оптимальный подход
  • Transaction API обеспечивает atomic read-process-write
  • Понимание ограничений критично для правильной архитектуры

🎯 Шпаргалка для интервью

Обязательно знать:

  • Три гарантии: at-most-once (0-1 раз), at-least-once (1+ раз), exactly-once (ровно 1)
  • At-most-once: acks=0, быстро но возможна потеря данных
  • At-least-once: acks=all + enable.idempotence=true, надёжно но возможны дубликаты
  • Exactly-once: Transaction API, работает ТОЛЬКО для Kafka-to-Kafka сценариев
  • Для внешних систем (БД, HTTP) — Outbox pattern или idempotent writes
  • Idempotent producer: PID + Sequence Number, брокер отклоняет дубликаты
  • Коммит ДО обработки = потеря данных; коммит ПОСЛЕ = at-least-once

Частые уточняющие вопросы:

  • Почему exactly-once не работает для Kafka → БД? — Kafka не управляет транзакцией БД.
  • Как обеспечить end-to-end гарантию? — Outbox pattern: запись в outbox_table + CDC (Debezium).
  • Что делает idempotent producer? — Уникальный PID + sequence number, брокер отклоняет дубликаты при retry.
  • Какую гарантию выбрать по умолчанию? — At-least-once + идемпотентная обработка консьюмера.

Красные флаги (НЕ говорить):

  • «Exactly-once работает для любых систем» — только Kafka-to-Kafka
  • «At-most-once подходит для платежей» — потеря данных недопустима
  • «Коммит перед обработкой — нормально» — при crash данные потеряны
  • «Idempotent producer не нужен» — без него retry = дубликаты

Связанные темы:

  • [[10. В чём разница между at-most-once, at-least-once и exactly-once]]
  • [[11. Как настроить exactly-once семантику]]
  • [[23. Что такое idempotent producer]]
  • [[20. Что такое producer acknowledgment и какие режимы существуют (acks=0,1,all)]]