English translation is not available yet. Showing Russian content.
Как вы проектируете dead letter queue для failed LLM инференс запросов?
Краткий тезис
Dead Letter Queue (DLQ) — это механизм отказоустойчивости, который собирает запросы к LLM, не обработанные после всех повторных попыток. Правильная архитектура DLQ для LLM-инференса включает несколько топиков с разными задержками повторной обработки (retry-1s, retry-10s, retry-1m), priority|приоритетный consumer, читающий из retry-топиков раньше, чем из основного input, и финальный dlq-топик для ручного разбора и алертов. Это позволяет не терять запросы, минимизировать влияние временных сбоев и анализировать корневые причины отказов.
1. Термин: Dead Letter Queue (DLQ)
Dead Letter Queue — это очередь (или топик в Kafka), в которую попадают сообщения, не обработанные после исчерпания всех попыток повторной обработки. В контексте LLM-инференса DLQ хранит запросы, которые не удалось выполнить из-за ошибок модели, таймаутов, превышения лимитов rate limiter’а или других неисправимых проблем.
Зачем DLQ для LLM
- Не терять запросы — пользовательский запрос не должен бесследно исчезать.
- Анализировать ошибки — можно изучать паттерны отказов и улучшать систему.
- Ручное восстановление — оператор может повторно отправить запрос после исправления проблемы.
2. Архитектура на Kafka с несколькими retry-топиками
Предлагается следующая схема на базе Apache Kafka (или совместимых брокеров, например, Schema Redpanda):
[Producer] → input → [Consumer] → успех (ответ)
↓ ошибка
retry-1s → [Consumer] → успех
↓ ошибка
retry-10s → [Consumer] → успех
↓ ошибка
retry-1m → [Consumer] → успех
↓ ошибка
dlq → [Manual reprocess / Alert]
Топики и их назначение
| Топик | Задержка перед повторной попыткой | Когда используется |
|---|---|---|
input | нет (немедленно) | Первичная обработка |
retry-1s | 1 секунда | Первая повторная попытка (например, после таймаута) |
retry-10s | 10 секунд | Вторая попытка (после временной недоступности) |
retry-1m | 1 минута | Третья попытка (после rate limit или перегрузки) |
dlq | ∞ | Финальная точка — запрос не обработан |
Как реализовать задержку
В Kafka нет встроенной задержки сообщений. Обычно consumer после неудачи отправляет сообщение в следующий retry-топик, а consumer этого топика спит указанное время перед чтением. Или используется Kafka Streams с punctuator для отложенной отправки. Более простой вариант — использовать RabbitMQ с x-delay или AWS SQS с DelaySeconds, но Kafka даёт больше контроля над порядком и масштабируемостью.
3. Consumer с приоритетами
Consumer должен читать сообщения из retry-топиков с более высоким приоритетом, чем из input. Это гарантирует, что повторные попытки не будут откладываться бесконечно из-за наплыва новых запросов.
Реализация приоритетов
- Используем один consumer group, который подписан на все топики.
- В цикле опрашиваем топики в порядке приоритета:
retry-1s→retry-10s→retry-1m→input. - Если в retry-топике есть сообщение, обрабатываем его, иначе переходим к следующему.
- Важно: не блокировать чтение из
input, если retry-топики пусты.
Пример псевдокода на Python (с использованием confluent_kafka):
import time
from confluent_kafka import Consumer, KafkaError
TOPICS_PRIORITY = ['retry-1s', 'retry-10s', 'retry-1m', 'input']
c = Consumer({
'bootstrap.servers': 'localhost:9092',
'group.id': 'llm-consumer',
'auto.offset.reset': 'earliest'
})
c.subscribe(TOPICS_PRIORITY)
def process_message(msg):
# вызов LLM, обработка
pass
while True:
# poll с таймаутом 100ms
msg = c.poll(0.1)
if msg is None:
continue
if msg.error():
if msg.error().code() == KafkaError._PARTITION_EOF:
continue
else:
print(msg.error())
break
topic = msg.topic()
try:
process_message(msg)
c.commit(msg) # успех — коммитим
except Exception as e:
# Определяем следующий retry-топик
next_topic = get_next_retry_topic(topic)
if next_topic:
# отправляем в следующий retry-топик
produce_to_topic(next_topic, msg.value())
else:
# отправляем в dlq
produce_to_topic('dlq', msg.value())
c.commit(msg) # коммитим исходное сообщение (оно уже не нужно)
Функция get_next_retry_topic
RETRY_CHAIN = {
'input': 'retry-1s',
'retry-1s': 'retry-10s',
'retry-10s': 'retry-1m',
'retry-1m': None # конец цепочки
}
def get_next_retry_topic(current_topic):
return RETRY_CHAIN.get(current_topic)
4. Обработка из DLQ: ручное восстановление и алерты
Когда запрос попадает в dlq, система должна:
- Отправить алерт — например, в Sentry или PagerDuty, чтобы дежурный инженер знал о проблеме.
- Сохранить запрос в долговременное хранилище (база данных, S3) для анализа.
- Предоставить интерфейс для ручного reprocess — админка, где можно просмотреть содержимое DLQ и повторно отправить запрос в
input(или в конкретный retry-топик) после исправления причины.
Пример админки (веб-интерфейс):
- Список сообщений из dlq с метаданными (время, ошибка, тело запроса).
- Кнопка «Reprocess» — отправляет сообщение обратно в
input. - Возможность редактировать запрос перед повторной отправкой.
5. Дополнительные соображения
Экспоненциальная задержка
Вместо фиксированных задержек (1s, 10s, 1m) можно использовать экспоненциальную задержку с джиттером: delay = base * 2^attempt + random(0, base). Это снижает нагрузку при массовых сбоях.
Максимальное количество retry
Ограничить число попыток (например, 3). После этого — только DLQ. Иначе можно зациклиться.
Обработка rate limiting
Если LLM возвращает 429 Too Many Requests, лучше не сразу retry, а подождать дольше (например, retry-1m). Можно добавить отдельный топик retry-rate-limit.
Fallback
Вместо DLQ можно попробовать fallback-модель (меньшая, более дешёвая) или кэшированный ответ. DLQ — последняя инстанция.
Идемпотентность
Повторные попытки могут привести к дублированию ответов. Клиент должен иметь идентификатор запроса (request_id) и игнорировать дубликаты.
6. Мониторинг и метрики
Для DLQ важно отслеживать:
- Lag (отставание) каждого топика — сколько сообщений ждут обработки.
- Количество сообщений в DLQ — если растёт, значит что-то сломалось.
- Частота ошибок по типам (таймаут, rate limit, ошибка модели).
- Время обработки — сколько запрос провёл в retry-цепочке.
Инструменты: Prometheus + Grafana, Kafka Lag Exporter.
7. Альтернативы Kafka
| Брокер | Встроенная задержка | Приоритеты | Простота |
|---|---|---|---|
| Kafka | Нет (нужен consumer sleep) | Ручная реализация | Средняя |
| RabbitMQ | Да (плагин x-delay) | Да (через x-priority) | Высокая |
| AWS SQS | Да (DelaySeconds) | Нет (FIFO с группами) | Очень высокая |
| Redis Streams | Нет (нужен XREADGROUP с BLOCK) | Частично | Низкая |
Выбор зависит от инфраструктуры: если уже есть Kafka — используем её; если стартап — проще RabbitMQ или SQS.
8. Потенциальные проблемы и их решение
- Дубликаты сообщений — при сбое consumer может отправить одно и то же сообщение в retry-топик дважды. Решение: идемпотентность на стороне producer (уникальный ключ сообщения) и дедупликация в consumer.
- Нарушение порядка — если важен порядок обработки запросов, используйте Kafka с одним partition на пользователя или FIFO-очередь (SQS FIFO).
- Бесконечный retry — всегда задавайте максимальное количество попыток и TTL для сообщения.
Пет-проект для закрепления
Задача Реализовать DLQ для LLM-инференса на Kafka с тремя retry-топиками, приоритетным consumer и веб-админкой для ручного reprocess.
Инструменты
- Python (confluent_kafka, FastAPI для админки)
- Kafka (локально через Docker)
- SQLite или PostgreSQL для хранения сообщений из DLQ
Шаги:
- Запустите Kafka в Docker:
docker-compose up -dс одним брокером. - Создайте топики:
input,retry-1s,retry-10s,retry-1m,dlq. - Напишите producer, который отправляет случайные запросы в
input. - Напишите consumer с приоритетным опросом (как в примере выше). При ошибке (имитация — случайный сбой) отправляйте в следующий retry-топик.
- После 3 неудач отправляйте в
dlq. - Реализуйте FastAPI-админку:
GET /dlq— список сообщений.POST /dlq/{id}/reprocess— отправляет сообщение обратно вinput.
- Добавьте алерт (логирование в Sentry или просто print).
- Запустите и проверьте, что сообщения проходят цепочку и попадают в DLQ.
Ожидаемый результат Работающая система, где сбойные запросы не теряются, а аккумулируются в DLQ с возможностью ручного восстановления.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 406 | Как вы обрабатываете ошибки LLM (timeout, rate limit)? |
| 408 | Как вы проектируете систему мониторинга для LLM-инференса? |
| 409 | Как вы реализуете circuit breaker для LLM? |
| 410 | Как вы управляете очередями запросов к LLM? |
| 411 | Как вы тестируете отказоустойчивость LLM-сервиса? |
| 412 | Как вы логируете запросы и ответы LLM? |
Навигация
- Предыдущий: 406
- Следующий: 408
- Индекс: 00. Индекс разборов