Добавить в корзинуПозвонить
Найти в Дзене
Мир Вокруг

📌 Dead Letter Queue в Kafka: как 15M профилей перестали терять события при деградации внешнего API

Опубликовано 9 июня 2026 на Хабре. Кейс продакшн-сервиса: 300–400 RPS, обогащение из БД и отправка во внешний REST, который регулярно «отваливается». Разобрано, почему ручной commit в Kafka убивает пропускную способность и как auto-commit + asyncio + DLQ-топик + FSM удерживают и скорость, и гарантию доставки. 💡 Главные тезисы: • На ручном commit потолок одного консьюмера — около 10 сообщений/сек при RTT ~100 мс. 15M профилей дают 300–400 RPS, и синхронная модель сразу упирается в сетевую задержку внешнего сервиса. Масштабирование через consumer group обходится дорого. • Решение — auto-commit, отделение commit'а от обработки и пул асинхронных задач под asyncio.Semaphore. Скорость больше не привязана к RTT, но появляется новая ответственность: задачи в asyncio держатся через слабые ссылки и могут быть собраны GC посреди HTTP-вызова, поэтому нужен явный registry тасок с add_done_callback и логированием исключений. • DLQ здесь — не «кладбище», а временное хранилище недоставленных событий

📌 Dead Letter Queue в Kafka: как 15M профилей перестали терять события при деградации внешнего API

Опубликовано 9 июня 2026 на Хабре. Кейс продакшн-сервиса: 300–400 RPS, обогащение из БД и отправка во внешний REST, который регулярно «отваливается». Разобрано, почему ручной commit в Kafka убивает пропускную способность и как auto-commit + asyncio + DLQ-топик + FSM удерживают и скорость, и гарантию доставки.

💡 Главные тезисы:

• На ручном commit потолок одного консьюмера — около 10 сообщений/сек при RTT ~100 мс. 15M профилей дают 300–400 RPS, и синхронная модель сразу упирается в сетевую задержку внешнего сервиса. Масштабирование через consumer group обходится дорого.

• Решение — auto-commit, отделение commit'а от обработки и пул асинхронных задач под asyncio.Semaphore. Скорость больше не привязана к RTT, но появляется новая ответственность: задачи в asyncio держатся через слабые ссылки и могут быть собраны GC посреди HTTP-вызова, поэтому нужен явный registry тасок с add_done_callback и логированием исключений.

• DLQ здесь — не «кладбище», а временное хранилище недоставленных событий. Поды синхронизирует маркер в Redis с TTL: пока ключ жив — consumer паузит партиции основного топика через consumer.pause(...), после истечения TTL переключается на чтение DLQ, а основной топик стоит на паузе. Дополнительный семафор на HTTP не даёт восстановившемуся API принять весь бэклог залпом.

• Режимами управляет FSM на уровне партиций одного AIOKafkaConsumer: NORMAL, PAUSED, DRAIN DLQ. Логика переключения — match/case по паре (api_unavailable, dlq_has_unread). Отдельных воркеров для DLQ нет, что сохраняет единый механизм коммита оффсетов.

• Цикл «основной топик → DLQ → ретрай → DLQ» рвут envelope-обёрткой: dlq_retries инкрементируется на стороне продюсера и растёт монотонно даже при ребалансировке. При превышении MAX_RETRIES событие логируется как dlq_dropped или уезжает в долгосрочное хранилище.

• Для событий с TTL добавлен first_dlq_timestamp + DEADLINE_INTERVAL и сверка с источником истины: MATCH`/`MISMATCH`/`EXPIRED`/`QUERY_FAILED. Дедлайн защищает и от деградации API, и от сбоев самого валидатора.

🛠 Чеклист внедрения DLQ для Kafka-консьюмера с внешним REST API:

• decode() и базовую валидацию (pydantic/msgspec) делать до создания asyncio-таски — детерминированные ошибки не должны расходовать бюджет ретраев.

• Выбор между счётчиком и дедлайном — по природе события: для webhook'ов хватает dlq_retries, для бизнес-событий — first_dlq_timestamp плюс сверка с источником истины.

• MAX_RETRIES и DEADLINE_INTERVAL подбирать эмпирически по метрикам dlq_dropped и dlq_retry_rate на staging.

🗣 Цитата: «Сам по себе DLQ — это всего лишь топик. Всё сложное находится вокруг него: маркер состояния, конечный автомат обработки, а также вспомогательные ограничения вроде счётчика ретраев и дедлайна.»

🔍 Наш комментарий:

Разделение ответственности собрано аккуратно: Redis — флаг деградации, Kafka pause/resume — управление потоком, envelope — жизненный цикл сообщения. Главный риск — единственный Redis как точка отказа: если он ляжет, маркер потеряется и поды возобновят долбёжку. Стоит держать в голове fallback — локальный circuit breaker.

Habr / Dead Letter Queue в Kafka на практике

#kafka #python #backend