Ordering Guarantees
Границы гарантий порядка Kafka: partition, key, producer, consumer, DLQ и state machine.
Ordering Guarantees в Apache Kafka: порядок сообщений и его границы
Одно из ключевых свойств Apache Kafka, которое отличает её от многих других брокеров сообщений, — это гарантия порядка. Но эта гарантия не абсолютна. Понимание того, когда и для каких сообщений Kafka сохраняет порядок, критически важно для проектирования надежных потоковых систем.
Главное правило, которое нужно запомнить: Kafka гарантирует порядок сообщений только внутри одной партиции (partition). Между разными партициями одного топика порядка нет. Гарантии порядка между разными топиками также отсутствуют.
Как Kafka хранит сообщения: топики и партиции
В Kafka топик (topic) — это логическое понятие, которое физически разбито на партиции (partitions). Каждая партиция — это упорядоченная, неизменяемая (append-only) последовательность сообщений. Каждое сообщение внутри партиции получает уникальный последовательный номер — смещение (offset).
Топик "orders" (3 партиции):
Партиция 0:
offset 0: сообщение A (ключ: order_123)
offset 1: сообщение B (ключ: order_456)
offset 2: сообщение C (ключ: order_123)
Партиция 1:
offset 0: сообщение D (ключ: order_789)
offset 1: сообщение E (ключ: order_123)
Партиция 2:
offset 0: сообщение F (ключ: order_456)Важное следствие: Порядок сообщений гарантируется только внутри каждой партиции. Сообщение C в партиции 0 гарантированно находится после сообщения A и B. Но сообщение E в партиции 1 не имеет определенного порядка относительно сообщения C в партиции 0 — они могут быть прочитаны потребителем в любом порядке в зависимости от того, как потребитель читает из партиций.
Гарантии продюсера: как сообщения попадают в партиции
Продюсер решает, в какую партицию отправить сообщение. Это решение может быть:
1. Явное указание партиции. Продюсер сам знает номер партиции. Редко используется.
2. Через ключ сообщения (key). Стандартный способ. Продюсер вычисляет hash(key) % numberOfPartitions. Один и тот же ключ всегда попадает в одну и ту же партицию. Это гарантирует, что все сообщения с одинаковым ключом будут отправлены в одну партицию и, следовательно, будут упорядочены.
3. Round-robin (key = null). Если ключ не указан, продюсер по умолчанию распределяет сообщения по партициям по очереди (round-robin). Порядок между сообщениями с разными ключами не гарантируется, но даже внутри одного ключа его нет, потому что ключ отсутствует.
Важно о порядке внутри одной партиции. Если продюсер отправляет два сообщения в одну партицию (например, с одним ключом), они будут записаны в том порядке, в котором продюсер их отправил. Однако, если у продюсера включены ретраи (retries) и первое сообщение временно не записалось, а второе записалось, порядок может нарушиться.
Решение: Включить параметр max.in.flight.requests.per.connection = 1 (ограничивает количество одновременно передаваемых сообщений) и enable.idempotence = true (гарантирует порядок даже при ретраях). Это стандартная настройка для production.
Гарантии брокера: что происходит внутри
Когда брокер получает сообщение, он:
- Проверяет актуальность (если указано).
- Записывает сообщение на диск в указанную партицию.
- Присваивает смещение (offset) — порядковый номер в этой партиции.
- Реплицирует сообщение на реплики (если настроено).
Смещения всегда возрастают внутри партиции. Kafka никогда не меняет порядок сообщений внутри одной партиции после записи.
Гарантия: Если сообщение A было записано в партицию P0 раньше сообщения B, то смещение A будет меньше смещения B. Все потребители увидят A раньше B.
Гарантии потребителя: как читать сообщения, сохраняя порядок
Потребитель может читать из одной или нескольких партиций (в рамках одной consumer group). При чтении из нескольких партиций потребитель получает сообщения из каждой партиции в порядке возрастания смещений, но порядок между партициями не определен.
Пример: Потребитель читает из партиций 0 и 1.
Партиция 0: сообщения A (offset 0), C (offset 1), E (offset 2) Партиция 1: сообщения B (offset 0), D (offset 1)
Потребитель может получить сообщения в порядке: A, B, C, D, E (чередование). Или A, C, E, B, D (сначала все из партиции 0). Никаких гарантий.
Как сохранить порядок на стороне потребителя?
- Если порядок важен для всех сообщений без исключения — читайте из одной партиции (топик с 1 партицией). Это убивает масштабируемость.
- Если порядок важен только для сообщений с одним ключом — используйте ключ, и они все попадут в одну партицию. Внутри партиции порядок гарантирован.
- Если порядок важен для глобальной последовательности (например, все события системы), Kafka не подходит. Нужны другие системы (например, традиционная БД, Apache Pulsar с ordering на уровне топика).
Исключения и нарушения порядка
1. Изменение числа партиций. При увеличении количества партиций в топике отображение hash(key) % partitions меняется. Сообщения с тем же ключом, отправленные до и после изменения, попадут в разные партиции. Порядок между ними не гарантируется.
2. Ретраи без идемпотентности. Если у продюсера enable.idempotence = false и max.in.flight.requests.per.connection > 1, возможна ситуация: первое сообщение потерялось (временный сбой), второе записалось, затем первое переотправилось и записалось после второго. Порядок нарушен.
3. Dead Letter Queue (DLQ). Часто при обработке сообщений с ошибками их отправляют в DLQ. DLQ — отдельный топик. Сообщение из DLQ теряет связь с исходной последовательностью.
4. Перемещение данных между топиками (Kafka Streams, MirrorMaker). При копировании данных в другой топик порядок может быть нарушен, если используется больше одного потока.
Примеры сценариев и требования к порядку
Сценарий 1: Заказы в интернет-магазине.
События одного заказа должны обрабатываться в порядке: created → paid → shipped → delivered.
Решение: ключ = orderId. Допустим, 1000 заказов в минуту. 10 партиций. Каждый заказ попадает в свою партицию, порядок внутри одной партиции гарантирован. Никакая логика не нарушит порядок событий одного заказа.
Сценарий 2: Обновления профиля пользователя.
Последнее обновление должно быть применено после всех предыдущих. Порядок важен.
Решение: ключ = userId. Все обновления одного пользователя идут в одну партицию. Порядок сохранен.
Сценарий 3: Логи веб-сервера.
Порядок не важен. Каждый лог — независимое событие.
Решение: ключ = null (round-robin). Максимальная равномерность, порядок не нужен.
Сценарий 4: Банковские транзакции (дебет и кредит одного счета).
Если дебет (списание) придет раньше кредита (пополнения), логика может ошибочно заблокировать счет. Порядок критичен.
Решение: ключ = accountId. Транзакции одного счета в одну партицию.
Сценарий 5: Материализованные представления (Kafka Streams).
Kafka Streams гарантирует порядок обработки событий с одним ключом в пределах одной задачи (task). Но при изменении числа партиций входного топика или перебалансировке порядок может быть нарушен.
Порядок в Kafka Streams и ksqlDB
Kafka Streams разбивает обработку на задачи (tasks). Каждая задача обрабатывает одну или несколько партиций. Порядок гарантируется внутри одной задачи для сообщений с одинаковым ключом. При перебалансировке (добавление/удаление потока) обработка останавливается, и после перераспределения порядок сохраняется, потому что ключи перераспределяются вместе с партициями.
Важное ограничение: При использовании окон (windowing) и агрегаций, сообщения могут обрабатываться “не в порядке” (out-of-order), если они приходят с опозданием (late arrival). Kafka Streams позволяет настроить допустимое опоздание (allowedLateness).
Как проверить порядок: инструменты и метрики
- Логи продюсера: Включить отладочный лог для продюсера, чтобы видеть, в какую партицию ушло сообщение.
- Консольный потребитель:
kafka-console-consumer --property print.key=true --property print.partition=trueпокажет партицию и ключ. - Метрики потребителя:
records-lagпоказывает отставание, но не порядок. - Сквозные correlation ID: Добавлять в сообщения последовательный номер (sequence number) для проверки порядка на стороне потребителя.
Что делать, если порядок нарушен: диагностика
Если вы обнаружили, что сообщения обрабатываются не в том порядке, возможные причины:
- Разные ключи. Проверьте, что все сообщения, которые должны быть упорядочены, имеют одинаковый ключ.
- Изменение числа партиций. Если число партиций было увеличено, старые сообщения с одним ключом в старой партиции, новые — в новой. Порядок нарушен.
- Ретраи продюсера. Проверьте настройки
enable.idempotenceиmax.in.flight.requests.per.connection. - Клиент с несколькими потоками. Если продюсер отправляет сообщения из нескольких потоков без синхронизации, порядок может нарушиться даже для одного ключа.
- Потребитель с конкурентной обработкой. Если потребитель обрабатывает сообщения из одной партиции параллельно (например, пул потоков), порядок внутри партиции не гарантируется.
Резюме
Порядок сообщений в Kafka — это свойство, которое часто неправильно понимают. Запомните главное правило:
Kafka гарантирует порядок сообщений только внутри одной партиции. Между партициями порядка нет.
Как обеспечить порядок:
- Используйте ключ сообщения, чтобы все связанные сообщения попадали в одну партицию.
- Убедитесь, что число партиций не меняется (или готовы к нарушению порядка при изменении).
- Включите
enable.idempotence = trueиmax.in.flight.requests.per.connection = 5(или меньше) для сохранения порядка при ретраях. - Обрабатывайте сообщения в потребителе последовательно (без параллельной обработки внутри одной партиции).
Что не дает порядка:
- Отсутствие ключа (round-robin).
- Разные ключи для логически связанных сообщений.
- Изменение числа партиций.
- Ретраи без идемпотентности.
- Параллельная обработка сообщений из одной партиции.
Для аналитика:
При проектировании системы на Kafka всегда определяйте единицу порядка (order key). Это может быть orderId, userId, accountId, deviceId. Документируйте, какие сообщения должны попадать в одну партицию. Понимание границ гарантий порядка Kafka — это разница между системой, которая работает предсказуемо, и системой, которая “иногда” обрабатывает события в неверном порядке, создавая трудноуловимые баги. Kafka дает строгие гарантии, но только в рамках описанных границ. Не пытайтесь требовать от Kafka того, чего она не обещает.