deшка
Урок 16.2

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

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

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

Топик делится на партиции

Топик объединяет записи одного потока, например record-stream. Партиция представляет отдельный упорядоченный журнал внутри топика. У топика с тремя партициями есть журналы 0, 1 и 2. Каждый можно читать независимо, поэтому разные процессы способны работать параллельно.

Брокеры хранят партиции на диске. Один брокер обычно обслуживает много партиций разных топиков. Число партиций и число брокеров обозначают разные вещи: три партиции могут находиться на одном брокере, а каждая партиция может иметь копии на нескольких брокерах. Копии подробно разберём в главе о репликации.

Новые записи добавляют в конец выбранной партиции. Внутри неё Kafka присваивает offset, числовую позицию записи. В двух партициях могут одновременно существовать записи с offset 0. Их адрес различается: (topic, partition, offset). Для поиска конкретной доставки нужны все три части.

Ключ связывает изменения одной сущности

Запись содержит key и value как байты, timestamp и необязательные headers. Клиент сериализует объект в байты, а читатель десериализует обратно. Kafka не определяет по имени поля key_id, как маршрутизировать JSON. Producer должен отдельно передать ключ записи.

Producer выбирает key по требованию к порядку: связанные записи получают одинаковый ключ. В обычной политике маршрутизации он вычисляет партицию из этого ключа. При неизменных числе партиций, сериализации ключа и политике одинаковый ключ попадает в одну партицию.

Учебная маршрутизация при трёх партициях
key=17  value=a    → partition 0
key=28  value=x    → partition 1
key=17  value=b    → partition 0
key=39  value=z    → partition 2
key=17  value=c    → partition 0
key=28  value=y    → partition 1

Ключи 17, 28 и 39 направляют сообщения в отдельные партиции с собственными offsets

Это заданный пример распределения, а не вычисленный результат конкретного клиентского хеша. Для ключа 17 все три записи лежат в партиции 0. Читатель видит значения a, b, c в порядке их добавления в этот журнал. У ключей 28 и 39 свои последовательности; общего порядка между партициями здесь нет.

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

Записи без ключа распределяет выбранный partitioner клиента. Современные клиенты могут придерживаться одной партиции для заполнения пачки, а затем менять её. Правило «каждое следующее сообщение обязательно идёт в следующую партицию» слишком грубое. Явно заданный номер партиции также может обходить маршрутизацию по ключу.

Порядок есть в журнале, а не во всём топике

В event_log ниже партиция 0 содержит offsets 0, 1, 2, партиция 1 — 0, 1, партиция 2 — 0. Сортировка только по offset перемешает независимые журналы. Посмотреть последовательность записей с ключом 17 можно так:

Просмотр одного журнала в учебном снимке
SELECT partition_id, offset_id, payload
FROM event_log
WHERE key_id = 17
ORDER BY partition_id, offset_id;

Результат: 0 | 0 | a, 0 | 1 | b, 0 | 2 | c. Порядок отражает добавление записей в партицию. Он может отличаться от времени бизнес-события: источник задержался и отправил старый факт позже нового. Для разрешения таких конфликтов контракт может содержать version сущности или отдельное время события.

Даже внутри одной партиции consumer может нарушить порядок эффектов, если запускает запись результатов в несколько потоков без координации. Получить записи по порядку и применить их по порядку — две обязанности. Producer также должен учитывать настройки повторов; этот механизм разберём в следующем уроке.

Партиции дают параллелизм и создают цену

Если обычная consumer group читает три партиции, три её участника могут получить по одному журналу. Четвёртый участник не получит дополнительную долю этого топика. Пока ограничимся этим примером; распределение между группами изучим отдельно.

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

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

Практика

SQL-песочница содержит обычные таблицы PostgreSQL. event_log — шесть строк из показанного журнала: поля partition_id, offset_id, key_id, payload. routing_audit — отдельная проверочная выборка с намеренным дефектом: для ключа 28 встретились партиции 1 и 2 при фиксированной топологии. Найди нарушение предположения о единой маршрутизации; данные не являются результатом запуска Kafka.

Три задачи проверяют чтение порядка одного ключа, распределение нагрузки и поиск ключей в нескольких партициях. В конце объясни, почему добавление брокера и добавление партиции решают разные задачи.

postgresql · песочницарешено 0 / 3

Верни partition_id, offset_id, payload из event_log для key_id = 17. Сортируй по partition_id, затем offset_id.

Ctrl+Enter
Войти, чтобы отмечатьдальше →
16.3 →Producer: пачки, подтверждения и повторы