Григорий Добряков

Howto · разбор

Разбор 09 YouTube @IT-Head · «Шины событий VS шины данных» 3 600 просмотров

Почему отправка сообщения в Kafka — это двухфазный коммит, а не fire-and-forget

Команды думают: отправил сообщение — работа сделана. Но система становится консистентной только когда получатель обработал и подтвердил. До этого момента она в рассогласованном состоянии — и это может длиться секунды или часы.

CTO Architect Tech Lead Head of AI

Проблема

Разработчик отправил сообщение в Kafka — и считает, что задача выполнена. Но получатель ещё не обработал. У него своя база, своя бизнес-логика, он может отклонить изменение или упасть на середине. Отправитель об этом не знает. Система в этот момент рассогласована — и это не аномалия, а нормальное состояние асинхронной архитектуры, которое нужно проектировать осознанно.

Методика

Две фазы распределённого коммита — это не DB и брокер, а отправитель и получатель.

  1. 1. Что такое две фазы на самом деле. Фаза 1 — сервис А сохранил изменение и отправил сообщение в брокер. Фаза 2 — сервис Б получил, обработал и зафиксировал у себя. Система событийно консистентна только после фазы 2. Всё время между ними — окно рассогласования, которое может длиться секунды или часы.
  2. 2. Получатель — не тупой relay. У сервиса Б своя бизнес-логика и валидация. Он может отклонить изменение — недостаточно прав, нарушение правил, несовместимый формат. Сервис А об этом не знает в момент отправки. Это архитектурное свойство хореографии, не баг реализации.
  3. 3. Два архитектурных выбора — и оба требуют разговора с бизнесом. Либо А отправляет и доверяет брокеру и Б — fire-and-forget с явным принятием eventual consistency. Либо А ждёт подтверждения от Б — система синхронна по бизнес-факту, но сложнее по реализации. Какой бы вариант ни выбрали, бизнес-заказчик должен понимать: между отправкой и подтверждением система находится в рассогласованном состоянии. Это может длиться секунды или часы — и это нормально, если принято осознанно. Если не проговорить заранее, это всплывёт как «баг» в проде.
  4. 4. Инфраструктурный prerequisite: надёжная фаза 1. Прежде чем думать о фазе 2, нужно чтобы сообщение вообще дошло до брокера. Две системы (БД и брокер) не коммитятся атомарно — нужен outbox в той же транзакции.

    Как выглядят обе фазы вместе:

    # Фаза 1: сервис А фиксирует изменение и отправляет
    with db.transaction():           # атомарно — или оба, или ничего
        order.status = "placed"
        order_repo.save(order)
        outbox.publish("orders.created", order)
    # ← А завершил; система рассогласована до завершения фазы 2
    
    # Фаза 2: сервис Б обрабатывает асинхронно
    def handle_order_created(event):
        if not inventory.reserve(event.items):
            outbox.publish("orders.rejected", event.order_id)
            return                   # Б отклонил — А об этом пока не знает
        fulfillment_repo.create(event)
        outbox.publish("orders.confirmed", event.order_id)
    
    # Фаза 1 завершается: А получает подтверждение от Б
    def handle_order_confirmed(event):
        order_repo.update(event.order_id, status="confirmed")
    # ← только теперь оба узла согласованы по бизнес-факту
    
    def handle_order_rejected(event):
        order_repo.update(event.order_id, status="rejected")
    # ← или оба знают об отказе

Два паттерна, которые делают этот цикл управляемым на практике: Correlation ID — уникальный идентификатор, который А присваивает запросу при отправке и передаёт через все события; Б возвращает его в confirmation, А матчит ответ обратно к исходному запросу. Без него А не знает, какое именно подтверждение пришло. Saga — управление цепочкой из нескольких шагов с компенсирующими транзакциями: если шаг N упал, сага откатывает шаги 1..N−1 через явные compensating events. Актуально когда в цикл вовлечены три сервиса и больше.

Артефакт

Видео «Почему отправка сообщения в Кафку — это двухфазный коммит» на канале @IT-Head. Две фазы здесь — это отправитель и получатель: система событийно консистентна только тогда, когда сервис А знает, что сервис Б успешно обработал сообщение. До этого момента система в состоянии рассогласования — и это может длиться секунды или часы. Howto разбирает уровень ниже: как не потерять сообщение по дороге от БД до брокера.

Подпись серии

Где ломается

Для кого и почему

Если вы строите event-driven системы — здесь про класс инцидентов, который возникает не из-за багов реализации, а из-за неверной ментальной модели. Окно eventual consistency и ответственность получателя нужно проектировать явно, а не обнаруживать в проде.

Есть проблемы с рассогласованием в event-driven системе?

Аудит event-driven интеграций: где окно eventual consistency не проговорено с бизнесом, где получатель может отклонить молча, где dual write без outbox — на основе реального опыта с 40+ сервисами на Kafka/RabbitMQ.

Написать на почту

Другие разборы

Серия инженерных разборов: реальная задача → методика → работающий артефакт → честный разбор, где он ломается.

К серии →