Ключ партиционирования и порядок внутри партиции

Урок про то, как выбор ключа сообщения управляет и порядком, и равномерностью нагрузки.

Ключ партиционирования — поле сообщения, по хешу которого Kafka выбирает партицию; все события с одним ключом попадают в одну партицию и потому строго упорядочены между собой.

Зачем это нужно

Порядок в Kafka гарантирован только внутри партиции. Значит, чтобы события одной сущности (одного заказа, одного пользователя) шли по порядку, они должны попадать в одну партицию. Этого добиваются ключом: key = order_id — и вся история заказа лежит в одной партиции, в правильной последовательности.

Как ключ выбирает партицию

  partition = hash(key) mod число_партиций

  key="ord-7"  -> hash -> P2
  key="ord-7"  -> hash -> P2   (тот же ключ -- та же партиция)
  key="ord-9"  -> hash -> P0
  key=null     -> по кругу (round-robin) по партициям

Одинаковый ключ всегда даёт одну и ту же партицию (пока не менялось их число). Если ключ не задан, Kafka раскидывает события равномерно по кругу — порядок между ними не гарантируется.

Порядок для связанных событий

Рассмотрим заказ с событиями created -> paid -> shipped. Если ключ = order_id, все три попадут в одну партицию и консьюмер увидит их строго в этом порядке. Без ключа они могли бы разъехаться по партициям, и shipped прочитался бы раньше paid.

{"key": "ord-7", "event": "created", "ts": "12:00:01"}
{"key": "ord-7", "event": "paid",    "ts": "12:00:05"}
{"key": "ord-7", "event": "shipped", "ts": "12:10:00"}

Перекос ключей (hot partition)

Обратная сторона: если один ключ встречается чрезмерно часто, его партиция перегружается, а остальные простаивают. Классика — ключ = country, и 80% трафика из одной страны валится в одну партицию.

  Перекос по ключу "country":

  P0 (RU): [#########################]  перегружена
  P1 (KZ): [##]
  P2 (BY): [#]
  -> параллелизм потерян, P0 -- бутылочное горло

Как работает под капотом

Партиция вычисляется на стороне продюсера: он берёт байты ключа, считает хеш (по умолчанию murmur2), берёт остаток от деления на число партиций — и пишет в найденную партицию. Брокер не перевыбирает партицию. Поэтому при увеличении числа партиций тот же ключ может начать попадать в другую партицию: mod изменился. Это важный нюанс — менять число партиций у топика с порядком по ключу нужно осознанно, иначе «хвост» истории сущности окажется в новой партиции.

Частые ошибки

  • Не задать ключ там, где нужен порядок. События сущности разъедутся по партициям и перемешаются.
  • Выбрать ключ с низкой кардинальностью. Мало уникальных значений (страна, статус) -- перекос и hot partition.
  • Менять число партиций, полагаясь на порядок по ключу. Раскладка ключей сместится, история сущности разорвётся.

Итоги

  • Ключ определяет партицию: одинаковый ключ — одна партиция — гарантированный порядок событий.
  • Без ключа события раскидываются по кругу, и порядок между ними не гарантируется.
  • Ключ с низкой кардинальностью даёт перекос (hot partition) и теряет параллелизм.
Проверьте себя
1. Зачем задавать ключ = order_id для событий заказа?
AЧтобы ускорить запись
BЧтобы все события заказа попали в одну партицию и шли строго по порядку
CЧтобы сжать данные
DЧтобы удалить дубликаты
2. Что вызывает hot partition (перекос)?
AСлишком длинные сообщения
BКлюч с низкой кардинальностью — мало значений, одно доминирует
CОтсутствие реплик
DБольшое число консьюмеров
3. Где вычисляется партиция по ключу?
AНа брокере при записи
BНа стороне продюсера: hash(key) mod число_партиций
CВ ZooKeeper
DНа консьюмере при чтении