Очереди и гарантии доставки
Надёжность брокеров и семантика доставки — Kafka против RabbitMQ, партиции, оффсеты и consumer group, гарантии доставки, идемпотентные потребители, dead-letter queue, паттерны outbox и saga, упорядочивание.
15 вопросов
JuniorТеорияОчень частоЧто такое producer и consumer, и что делает брокер между ними?
Что такое producer и consumer, и что делает брокер между ними?
Producer публикует сообщения; consumer читает и обрабатывает их. Брокер стоит между ними — принимает, надёжно хранит и доставляет сообщения. Это развязывает стороны: ни одна не вызывает другую напрямую, и им не нужно быть онлайн одновременно.
Типичные ошибки
- ✗Считать, что брокер лишь маршрутизирует адреса и сам не хранит сообщения
- ✗Полагать, что producer и consumer должны быть онлайн одновременно, чтобы обменяться сообщением
- ✗Путать, какая сторона публикует, а какая подписывается
Уточняющие вопросы
- →Что даёт брокер такого, чего не может прямой сокет producer-consumer?
- →Как надёжное хранение в брокере помогает, когда consumer временно недоступен?
JuniorТеорияОчень частоЧто такое очередь и что такое topic, и как потребитель читает из каждого?
Что такое очередь и что такое topic, и как потребитель читает из каждого?
Очередь отдаёт каждое сообщение ровно одному потребителю — конкурирующие потребители делят нагрузку, и сообщение уходит после обработки. Topic рассылает каждое сообщение всем подписчикам, поэтому одно сообщение читают много потребителей, каждый со своей позиции чтения.
Типичные ошибки
- ✗Считать, что topic, как очередь, удаляет сообщение, когда его прочитал один потребитель
- ✗Думать, что лишние потребители на очереди умножают доставку, а не делят нагрузку
- ✗Считать, что все подписчики topic делят одну позицию чтения, а не ведут свою
Уточняющие вопросы
- →Когда вы выберете конкурирующих потребителей на очереди, а не broadcast через topic?
- →Как потребитель на topic возобновляет работу после перезапуска, не теряя своё место?
MiddleТеорияОчень частоКакие бывают гарантии доставки, и какую на деле даёт стриминговая платформа Kafka?
Какие бывают гарантии доставки, и какую на деле даёт стриминговая платформа Kafka?
Три: at-most-once (теряет сообщения при сбое), at-least-once (может дублировать при повторе), exactly-once (ни того, ни другого). Kafka даёт настоящий exactly-once только внутри своих транзакций; как только затрагивается внешняя система, остаётся at-least-once плюс идемпотентность.
Типичные ошибки
- ✗Считать exactly-once автоматическим, а не ограниченным собственными транзакциями брокера
- ✗Путать, какая гарантия теряет сообщения, а какая их дублирует
- ✗Полагать, что exactly-once распространяется на внешние побочные эффекты, и идемпотентность не нужна
Уточняющие вопросы
- →Почему внешний побочный эффект возвращает вас к at-least-once плюс идемпотентность?
- →Как at-least-once вместе с ключом дедупликации приближает exactly-once на практике?
MiddleДизайнЧастоВаши потребители отстают от producer на четыре часа — lag растёт, а данные downstream устаревают. Спроектируйте расследование и исправление. Опишите, что вы измерите, чтобы найти узкое место (пропускная способность потребителей против входящего темпа, lag по партициям, время обработки сообщения, rebalance, ошибки), какие рычаги дёрнете, чтобы закрыть разрыв (число партиций и экземпляров потребителей, размер батча, медленные downstream-вызовы, poison-сообщения в DLQ), и как отличите временный всплеск от потребителя, которому просто не хватает ресурсов под ровную нагрузку.
Ваши потребители отстают от producer на четыре часа — lag растёт, а данные downstream устаревают. Спроектируйте расследование и исправление. Опишите, что вы измерите, чтобы найти узкое место (пропускная способность потребителей против входящего темпа, lag по партициям, время обработки сообщения, rebalance, ошибки), какие рычаги дёрнете, чтобы закрыть разрыв (число партиций и экземпляров потребителей, размер батча, медленные downstream-вызовы, poison-сообщения в DLQ), и как отличите временный всплеск от потребителя, которому просто не хватает ресурсов под ровную нагрузку.
Измерьте, отстаёт ли пропускная способность от темпа и где уходит время — lag по партициям, время обработки. Добавьте потребителей до числа партиций, а партиции — если упёрлись, батчите I/O и отведите poison-сообщения. Устойчивый рост — нехватка ресурсов; всплеск рассосётся.
Типичные ошибки
- ✗Добавлять потребителей сверх числа партиций, ожидая пропускную способность за потолком параллелизма
- ✗Пропускать backlog сбросом offset на latest, молча теряя необработанные сообщения
- ✗Винить темп producer, не измерив время обработки сообщения или задержку downstream
Уточняющие вопросы
- →Почему добавление потребителей сверх числа партиций перестаёт помогать пропускной способности?
- →Как отличить временный всплеск трафика от устойчивой нехватки ресурсов?
MiddleДизайнЧастоПотребитель обрабатывает события заказов. Одно сообщение падает пять раз подряд — каждый повтор бросает ту же ошибку. Спроектируйте политику повторов и DLQ для этого потребителя. Укажите, сколько раз повторять и с каким backoff, когда сообщение уходит в dead-letter queue (отведённую очередь для необрабатываемых сообщений), какие метаданные едут с ним и кто следит за DLQ и действует по её содержимому. Опишите, как не блокировать партицию, пока повторяется одно poison-сообщение, и как оператор позже переобработает исправленное сообщение.
Потребитель обрабатывает события заказов. Одно сообщение падает пять раз подряд — каждый повтор бросает ту же ошибку. Спроектируйте политику повторов и DLQ для этого потребителя. Укажите, сколько раз повторять и с каким backoff, когда сообщение уходит в dead-letter queue (отведённую очередь для необрабатываемых сообщений), какие метаданные едут с ним и кто следит за DLQ и действует по её содержимому. Опишите, как не блокировать партицию, пока повторяется одно poison-сообщение, и как оператор позже переобработает исправленное сообщение.
Повторить с backoff и jitter для временных сбоев; после ограниченного числа попыток сообщение уходит в dead-letter queue, а не блокирует партицию. Прикладывают payload, ошибку и счётчик попыток. Команда получает алерт, устраняет причину и переигрывает.
Типичные ошибки
- ✗Повторять poison-сообщение вечно на месте, блокируя партицию за ним
- ✗Относиться к DLQ как к fire-and-forget без владельца, алерта и пути переигрывания
- ✗Отбрасывать payload и метаданные ошибки, из-за чего сбои невозможно диагностировать
Уточняющие вопросы
- →Как отличить временный сбой, который стоит повторить, от poison-сообщения?
- →Что защищает наивное переигрывание от повторного запуска той же ошибки по кругу?
MiddleТеорияЧастоЧто такое идемпотентный потребитель, и как реализовать дедупликацию при повторной доставке сообщений?
Что такое идемпотентный потребитель, и как реализовать дедупликацию при повторной доставке сообщений?
Идемпотентный потребитель даёт один итог при любом числе доставок, поэтому at-least-once безопасен. Дедупликация: дать сообщению стабильный id, хранить обработанные id, пропускать уже виденные — в идеале в транзакции с побочным эффектом.
Типичные ошибки
- ✗Считать, что флаг exactly-once у брокера снимает нужду в дедупликации на стороне потребителя
- ✗Дедуплицировать по времени прихода, а не по стабильному id сообщения или бизнес-id
- ✗Записывать обработанный id отдельно от побочного эффекта, из-за чего падение посередине всё равно дублирует
Уточняющие вопросы
- →Почему запись о дедупликации и побочный эффект должны быть в одной транзакции?
- →Как удержать хранилище обработанных id от неограниченного роста?
MiddleТеорияЧастоЧем брокеры сообщений Kafka и RabbitMQ различаются по модели, хранению, маршрутизации и упорядочиванию?
Чем брокеры сообщений Kafka и RabbitMQ различаются по модели, хранению, маршрутизации и упорядочиванию?
Kafka — партиционированный лог: pull, сообщения остаются после чтения, маршрутизация по ключу партиции, порядок в пределах партиции. RabbitMQ — push конкурентам, удаляет по ack, маршрутизирует через exchange, порядок лишь в очереди с одним потребителем.
Типичные ошибки
- ✗Считать, что Kafka удаляет сообщение, как только его прочитал потребитель, как классическая очередь
- ✗Полагать, что какой-то из брокеров гарантирует полный порядок по всем партициям или потребителям
- ✗Считать, что RabbitMQ маршрутизирует по ключу партиции, а не через exchange и binding
Уточняющие вопросы
- →Какая нагрузка склоняет вас к хранимому логу Kafka вместо delete-on-ack у RabbitMQ?
- →Как маршрутизация RabbitMQ через exchange и binding добавляет гибкость, которой нет у партиций Kafka?
MiddleТеорияЧастоЧто такое saga, и чем оркестрационная saga отличается от хореографической?
Что такое saga, и чем оркестрационная saga отличается от хореографической?
Saga держит данные согласованными без распределённой транзакции — цепочка локальных транзакций, у каждой компенсация, отменяющая шаг при сбое. Оркестрация ведёт шаги центральным координатором; в хореографии его нет, каждый сервис реагирует на предыдущее событие.
Типичные ошибки
- ✗Отождествлять saga с распределённым two-phase commit, а не с локальными транзакциями плюс компенсация
- ✗Путать оркестрацию (центральный координатор) и хореографию (событийная, без координатора)
- ✗Полагать, что провалившийся шаг откатывается через БД, а не явным компенсирующим действием
Уточняющие вопросы
- →Когда обзорность центрального оркестратора перевешивает более слабую связанность хореографии?
- →Почему компенсирующее действие приходится проектировать для эффектов, которые нельзя просто откатить?
MiddleТеорияЧастоЧто в стриминговой платформе Kafka такое партиция, offset и consumer group, и как они взаимодействуют?
Что в стриминговой платформе Kafka такое партиция, offset и consumer group, и как они взаимодействуют?
Партиция — упорядоченный, только-на-дозапись срез topic; параллелизм равен числу партиций. Offset — позиция сообщения внутри партиции, фиксируется (commit) на группу для учёта прогресса. Consumer group делит партиции между участниками — каждую партицию читает ровно один участник.
Типичные ошибки
- ✗Считать offset глобальным счётчиком, а не значением на партицию и на группу
- ✗Думать, что все участники consumer group получают каждое сообщение, как broadcast
- ✗Считать партиции репликами для надёжности, а не единицей параллелизма
Уточняющие вопросы
- →Почему число партиций ограничивает параллелизм одной consumer group?
- →Что происходит с распределением партиций, когда участник группы подключается или падает?
MiddleТеорияЧастоЧто такое паттерн transactional outbox, и какую проблему двойной записи (dual write) он решает?
Что такое паттерн transactional outbox, и какую проблему двойной записи (dual write) он решает?
Проблема двойной записи (dual write): сервис обновляет БД и публикует событие, но их нельзя закоммитить атомарно, и сбой между ними даёт рассинхронизацию. Outbox пишет событие в таблицу в той же транзакции, что и изменение состояния; relay позже читает её и публикует.
Типичные ошибки
- ✗Считать, что outbox требует распределённого two-phase commit, а не одной локальной транзакции
- ✗Публиковать событие до коммита в БД, из-за чего события выходят для откатанного состояния
- ✗Размещать outbox внутри брокера, а не в собственной БД сервиса
Уточняющие вопросы
- →Как relay, читающий таблицу outbox, избегает пропуска или дублирования событий?
- →Почему опрос таблицы outbox часто заменяют чтением журнала изменений БД?
MiddleТеорияИногдаПотребитель только что прочитал два сообщения в стриминговой платформе Kafka. Как тому же потребителю прочитать их снова?
Потребитель только что прочитал два сообщения в стриминговой платформе Kafka. Как тому же потребителю прочитать их снова?
Kafka хранит сообщения и отслеживает offset, не удаляя при чтении, — потребитель сдвигает offset назад: вызывает seek на ранний offset (или по времени) и делает poll. На сервере ничего не меняется: повторное чтение — клиентский сдвиг offset.
Типичные ошибки
- ✗Считать, что Kafka удаляет сообщение при чтении, поэтому для перечитывания нужен повторный publish от producer
- ✗Полагать, что offset может только продвигаться и клиент никогда не сдвигает его назад
- ✗Считать повторную доставку запросом к брокеру, а не клиентским
seek
Уточняющие вопросы
- →Как перечитать только сообщения после конкретной метки времени, а не с начала?
- →Какой риск для перечитывания создаёт commit offset до обработки?
SeniorДизайнИногдаСпроектируйте компенсирующие транзакции для saga бронирования из трёх шагов: забронировать место, списать с карты, выпустить билет. Третий шаг, выпуск билета, падает. Опишите компенсирующее действие для каждого уже завершённого шага и порядок их запуска, какое состояние каждый сервис должен хранить, чтобы корректно компенсировать, почему компенсация — не то же самое, что откат базы данных, и как дизайн остаётся корректным, если само компенсирующее действие падает или шаг повторяется.
Спроектируйте компенсирующие транзакции для saga бронирования из трёх шагов: забронировать место, списать с карты, выпустить билет. Третий шаг, выпуск билета, падает. Опишите компенсирующее действие для каждого уже завершённого шага и порядок их запуска, какое состояние каждый сервис должен хранить, чтобы корректно компенсировать, почему компенсация — не то же самое, что откат базы данных, и как дизайн остаётся корректным, если само компенсирующее действие падает или шаг повторяется.
Компенсируйте завершённые шаги в обратном порядке: вернуть деньги, затем освободить место. Каждая компенсация — прямая транзакция, отменяющая шаг, а не откат БД: локальные транзакции уже закоммичены. Храните id для отмены; компенсации должны быть идемпотентны и повторяемы.
Типичные ошибки
- ✗Ожидать, что откат БД отменит уже закоммиченные шаги вместо явных компенсаций
- ✗Запускать компенсации в прямом порядке, а не в обратном к завершённым шагам
- ✗Полагать, что компенсации выполняются один раз, и пропускать идемпотентность и повторяемость
Уточняющие вопросы
- →Почему компенсация должна быть идемпотентной, если координатор saga её повторяет?
- →Как обработать компенсацию, которая сама падает на полпути, например возврат средств с ошибкой?
SeniorДизайнИногдаПлатёжное событие доставлено дважды, и клиент списан дважды. Пройдите каждый слой, где это двойное списание можно было предотвратить — от семантики доставки брокера, через потребителя, вниз к платёжному побочному эффекту и внешнему платёжному провайдеру. Для каждого слоя скажите, какой механизм останавливает дубль или почему этот слой не может, и завершите единственным слоем, на который вы положитесь как на конечную защиту, и почему.
Платёжное событие доставлено дважды, и клиент списан дважды. Пройдите каждый слой, где это двойное списание можно было предотвратить — от семантики доставки брокера, через потребителя, вниз к платёжному побочному эффекту и внешнему платёжному провайдеру. Для каждого слоя скажите, какой механизм останавливает дубль или почему этот слой не может, и завершите единственным слоем, на который вы положитесь как на конечную защиту, и почему.
At-least-once делает дубли ожидаемыми; настройка брокера их не убирает. Брокер снижает повторную доставку, потребитель дедуплицирует по стабильному id, списание несёт ключ идемпотентности провайдера. Идемпотентность платёжного эффекта — последний рубеж: слои выше дублируют.
Типичные ошибки
- ✗Считать, что режим exactly-once у брокера покрывает внешний платёжный вызов
- ✗Дедуплицировать по времени прихода, а не по стабильному id события и ключу идемпотентности
- ✗Полагаться на ack для предотвращения повторной доставки, а не на идемпотентность у денежного эффекта
Уточняющие вопросы
- →Почему подтверждение брокера в одиночку не гарантирует, что списание случится один раз?
- →Откуда должен браться ключ идемпотентности, чтобы повтор переиспользовал тот же ключ?
SeniorДебаггингИногдаСервис коммитит в БД, затем падает до публикации события — БД и брокер рассинхронизированы. Найдите причину и исправьте.
Сервис коммитит в БД, затем падает до публикации события — БД и брокер рассинхронизированы. Найдите причину и исправьте.
Коммит в БД и публикация в брокер — независимые записи без общей транзакции; падение между ними сохранило заказ, но потеряло событие (dual write). Лечится transactional outbox: писать событие в таблицу в той же транзакции; relay публикует его.
Открыть задачу →Типичные ошибки
- ✗Считать, что повтор публикации при рестарте это чинит, хотя у сервиса нет записи, что событие не отправлено
- ✗Публиковать до коммита в БД, из-за чего выходят события для заказов, которые могут откатиться
- ✗Хвататься за распределённый two-phase commit вместо outbox в локальной транзакции
Уточняющие вопросы
- →Как relay, читающий outbox, избегает публикации события дважды?
- →Почему чтение журнала изменений БД часто предпочтительнее опроса таблицы outbox?
SeniorДизайнИногдаKafka гарантирует порядок только в пределах одной партиции, но ваш сервис обязан обрабатывать каждое событие данного клиента в точном порядке его появления, в topic из 12 партиций под высокой нагрузкой. Спроектируйте, как сохранить порядок по клиенту, всё ещё используя все 12 партиций ради пропускной способности. Объясните, как события попадают в нужную партицию, что происходит с глобальным порядком между разными клиентами, как смена числа партиций повлияет на существующие ключи и один режим отказа, который дизайн обязан выдержать — например, один горячий клиент.
Kafka гарантирует порядок только в пределах одной партиции, но ваш сервис обязан обрабатывать каждое событие данного клиента в точном порядке его появления, в topic из 12 партиций под высокой нагрузкой. Спроектируйте, как сохранить порядок по клиенту, всё ещё используя все 12 партиций ради пропускной способности. Объясните, как события попадают в нужную партицию, что происходит с глобальным порядком между разными клиентами, как смена числа партиций повлияет на существующие ключи и один режим отказа, который дизайн обязан выдержать — например, один горячий клиент.
Ключ партиционирования — customer id: его события — в одну упорядоченную партицию, а другие клиенты — по всем 12 ради пропускной способности. Порядок между клиентами теряется, держится лишь по ключу. Смена числа партиций перераспределяет ключи, горячий ключ перегружает партицию.
Типичные ошибки
- ✗Полагать, что Kafka сохраняет порядок между партициями, когда у сообщений общий ключ
- ✗Ожидать глобальный порядок между клиентами, а не только порядок по ключу
- ✗Игнорировать, что смена числа партиций перераспределяет ключи и рвёт непрерывность по ключу
Уточняющие вопросы
- →Как разгрузить одного горячего клиента, насыщающего свою единственную партицию?
- →Почему позднее увеличение числа партиций угрожает существующему порядку по ключу?