Топик, партиция, ключ и порядок

О лекции

Топик в Kafka — распределённый лог, разрезанный на партиции. Порядок гарантирован внутри партиции, а не по всему топику. Ключ продюсера выбирает партицию и тем самым порядок событий одной сущности.

Kafka kafka собеседование

Kafka — это лог, который можно перечитать. Продюсер дописывает запись в конец. Брокер выдаёт номер — offset. Консьюмер читает лог. После чтения сообщение само не исчезает.

Сообщение живёт, пока действует retention: по времени или по размеру. Потом сегмент удаляют. Это не очередь задач. В очереди после ack сообщение пропадает. Здесь вы можете прокрутить лог назад, пока его не стёрли.

Топик — логическое имя потока, например orders. Физически топик разрезан на партиции. У каждой партиции свой непрерывный лог и свой лидер на брокере.

Пример. Топик orders из трёх партиций. Событие попадает ровно в одну. Порядок «сначала created, потом paid» обещан только внутри этой партиции, не по всему топику.

Партиции и параллелизм

Партиция — последовательность записей с растущим offset. В одной consumer group партицию читает не больше одного консьюмера. Так сохраняется порядок внутри неё.

Параллелизм чтения в группе ограничен числом партиций. Двенадцать партиций — максимум двенадцать занятых консьюмеров. Двадцать консьюмеров на двенадцать партиций: восемь будут без работы.

Добавить партиций — увеличить потенциальный параллелизм. Делать это легкомысленно нельзя: меняется отображение ключа на партицию, растёт metadata, дольше rebalance.

Retention и compaction — про жизнь лога, не про порядок. Compacted топик оставляет последнее значение ключа. Это changelog, не очередь задач. Сообщение с ключом не «удалится после ack».

Пример. Топик user-profile с compaction. Пользователь сменил имя пять раз. В логе со временем останется последняя запись по этому user_id. Старые версии сожмутся, но не в момент чтения.

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

Если у сообщения есть ключ, продюсер считает hash и кладёт все события этого ключа в одну партицию. События одного user_id или order_id идут по порядку — пока вы не сменили число партиций.

Без ключа работает round-robin или sticky. События одной сущности разъезжаются по партициям. Тогда «сначала created, потом paid» между ними больше не обещано.

Смена числа партиций ломает mapping. Тот же ключ после rehash может попасть в другую партицию. Старые записи остаются там, где были. Новые пишутся в новое место. Порядок сущности на этой границе вы не восстановите без своей склейки при чтении.

err := producer.Send(ctx, kafka.Message{
	Topic: "orders",
	Key:   []byte(order.UserID),
	Value: payload,
})
if err != nil {
	return err
}
// топик orders, 12 партиций: до 12 членов группы читают параллельно
// 20 консьюмеров в той же группе: 8 будут без партиции

Ключ user_id держит события одного пользователя в одной партиции. Двенадцать партиций — до двенадцати параллельных консьюмеров в группе.

Пример. Пользователь 42 шлёт created, paid, shipped. Ключ — user_id: все три в одной партиции и в таком порядке. Ключа нет: search может увидеть paid раньше created.

Продюсерские acks=0/1/all меняют durability: успели ли реплики принять запись. На порядок внутри партиции это не влияет. Порядок — про партицию и ключ. Идемпотентный продюсер тоже не про порядок между ключами: он про дубли ретрая на одной партиции.

Типичная ошибка

Сказать «Kafka сохраняет порядок» без границы: порядок внутри партиции, не по топику. Одна партиция «чтобы был глобальный порядок» на высоком TPS — узкое горло. Без ключа ждать, что события одного заказа не перегонят друг друга.

На собеседовании

Топик — лог из партиций. Порядок — внутри партиции. Ключ держит события одной сущности вместе. Параллелизм группы не больше числа партиций. Смена этого числа ломает hash ключа.