Суть Читай реальные сниппеты продюсера и consumer'а, предскажи поведение порядка и выбери фикс с наибольшим рычагом, который senior сделает первым.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min
Баги порядка живут в конфиге продюсера и обработчике consumer’а, а не в брокере. Читай код, предсказывай, где порядок ломается, и выбирай фикс, к которому senior потянется первым.
Цель
Отработай цикл, который запускаешь на каждом инциденте порядка: читай partition key и конфиг продюсера, найди, где два сообщения могут погнаться или поменяться местами, и тянись к структурному фиксу — выбор ключа, идемпотентный продюсер, version guard — прежде чем винить брокер.
Сниппет 1 — partition key
// События одного аккаунта должны оставаться упорядоченными друг относительно друга.ProducerRecord<String, Event> record = new ProducerRecord<>("account-events", UUID.randomUUID().toString(), event);producer.send(record);
Викторина
Completed
Требование — порядок по аккаунту. Что на самом деле делает этот выбор ключа и каков фикс?
Heads-up Kafka сохраняет порядок только внутри partition. Случайный ключ растаскивает события аккаунта по partition, поэтому нет partition, держащей их по порядку. Ключ должен быть границей консистентности.
Heads-up Топик на аккаунт не масштабируется и не нужен. Один топик, партиционированный по accountId, даёт порядок по аккаунту и параллелизм между аккаунтами. Дефект — случайный ключ.
Heads-up acks управляет надёжностью, а не тем, на какую partition попадёт ключ. Случайный ключ всё равно растаскивает события аккаунта; порядок чинит только ключевание по accountId.
Ключ верный, и всё на одной partition. Почему два сообщения всё равно могут поменяться местами и каков фикс в одну строку?
Heads-up retries=0 меняет переупорядочивание на потерю сообщений при временных сбоях — хуже. Реальный фикс — идемпотентный продюсер, сохраняющий порядок через retry за счёт per-partition sequence number.
Heads-up acks=all — надёжная, верная настройка и не вызывает переупорядочивания. Переупорядочивание идёт от неидемпотентных retry с несколькими in-flight.
Heads-up Одна partition гарантирует порядок, только если продюсер не может дать переотправленному батчу обогнать более поздний — а именно это и позволяют неидемпотентные multi-in-flight retry.
Сниппет 3 — обработчик consumer’а
def handle(msg): user = db.get(msg.user_id) user.name = msg.new_name # last write wins на том, что пришло последним db.save(user)
Викторина
Completed
Consumer'ы конкурентны на at-least-once очереди. Два обновления одного пользователя могут прийти не по порядку или дважды. Каков самый сильный фикс?
Heads-up Транзакция делает одно обновление атомарным, но не упорядочивает два независимых сообщения. Более старое обновление всё равно может закоммититься после более нового и перезаписать его; нужен version guard, а не только атомарность.
Heads-up Дедуп не даёт применить одно сообщение дважды, но два разных обновления одного пользователя всё равно могут примениться не по порядку. Нужны дедуп плюс проверка версии.
Heads-up Это сериализует несвязанных пользователей ради задачи на уровне пользователя, схлопывая пропускную способность. Партиционируй по user_id и version-guard'ь.
Сниппет 4 — DLQ replay
# Ночная задача: дренировать dead-letter queue обратно в основной топикfor failed in dead_letter_queue.drain_all(): main_topic.produce(failed.key, failed.value) # на полной скорости, без троттлинга
Викторина
Completed
Consumer идемпотентен и version-guard'нут. Что всё равно делает этот replay с живым порядком и как запускать его безопасно?
Heads-up Тот же ключ сохраняет, на какую partition они попадут, но они всё равно приземляются после более новых событий — переупорядочивание. Безопасно применять только из-за version guard, и replay всё равно троттлят ради живой пропускной способности.
Heads-up DLQ replay — нормальный инструмент восстановления. С идемпотентными, version-guard'нутыми consumer'ами беспорядок безвреден при применении; фикс — rate-limit на replay, а не запрет.
Heads-up Сброс ключа разбрасывает переигранные сообщения по partition, делая хуже. Сохраняй ключ, опирайся на version guard и троттли replay.
Итог
Порядок читается в продюсере и consumer’е, а не в брокере: случайный partition key разбрасывает события сущности и должен быть границей консистентности; неидемпотентный продюсер с несколькими in-flight переупорядочивает у источника, чинится через enable.idempotence=true; конкурентным consumer’ам на at-least-once очереди нужен per-entity version guard, а не только транзакция или дедуп; DLQ replay переупорядочивает против живого трафика, безопасен при version guard, но только с rate-limit. Сначала чини структуру, потом проверяй, что порядок держится под нагрузкой.