English translation is not available yet. Showing Russian content.
Как вы проектируете dead letter queue для failed LLM инференс запросов?
Краткий тезис
Dead letter queue (DLQ) для failed LLM инференс запросов — это механизм изоляции и последующей обработки запросов, которые не удалось выполнить после всех повторных попыток. Типичная архитектура строится на Kafka (или аналогичной очереди) с тремя топиками: main (входящие запросы), retry (повторные попытки с задержкой) и DLQ (финальные ошибки). Из DLQ запросы либо репроцессируются вручную после анализа, либо автоматически после исправления причины, либо генерируют алерт в Sentry / PagerDuty. Ключевые аспекты: стратегия повторных попыток (backoff|exponential backoff, jitter), мониторинг глубины DLQ, идемпотентность обработчиков и дедупликация.
1. Термин: Dead Letter Queue (DLQ)
Dead letter queue — это очередь (или топик), в которую попадают сообщения, которые не удалось обработать после всех разрешённых повторных попыток. В контексте LLM инференса это означает, что запрос к модели (например, вызов OpenAI API или локальной модели) завершился ошибкой, и система исчерпала лимит retry.
Зачем нужна DLQ для LLM
- Изоляция ошибок: сбойные запросы не блокируют обработку нормальных.
- Анализ первопричин: можно изучать паттерны ошибок (таймауты, rate limits, битые промпты).
- Репроцессинг: после исправления ошибки (например, увеличения таймаута) можно повторно отправить запросы из DLQ.
- Алертинг: при росте числа сообщений в DLQ — сигнал для инженера.
2. Причины failed LLM инференс запросов
Прежде чем проектировать DLQ, нужно понимать, какие ошибки могут возникать:
| Тип ошибки | Пример | Стратегия retry |
|---|---|---|
| Таймаут (timeout) | LLM не ответила за 30 с | Exponential backoff, увеличить таймаут |
| Rate limit (превышение лимита) | 429 Too Many Requests | Retry с заголовком Retry-After |
| Ошибка модели (model error) | 500 Internal Server Error, перегрузка | Retry с задержкой, переключение на fallback |
| Битый промпт (invalid prompt) | Слишком длинный контекст, невалидный JSON | Не retry — сразу в DLQ |
| Ошибка авторизации | Истёк API-ключ | Не retry — алерт |
| Ошибка парсинга ответа | LLM вернула неструктурированный текст | Retry с другим prompt template |
Вывод: не все ошибки стоит повторять. DLQ должна получать только те запросы, которые не удалось обработать после разумного числа retry, а также те, которые заведомо не могут быть ретраированы (например, битый промпт).
3. Архитектура с Kafka: main → retry → DLQ
Типичная схема на Apache Kafka:
[Producer] → main_topic → [Consumer] → успех? → (да) завершено
↓ (нет)
retry_topic (с задержкой)
↓
[Consumer] → успех? → завершено
↓ (нет, исчерпаны retry)
dlq_topic
Компоненты:
- main_topic: все входящие запросы на инференс.
- retry_topic: запросы, которые нужно повторить. Каждое сообщение содержит метаданные: количество попыток, время следующей попытки, исходная ошибка.
- DLQ_topic: финальные неудачные запросы.
Реализация задержки:
- В Kafka нет встроенной задержки, поэтому используют Kafka Streams или отдельный retry consumer, который спит перед повторной отправкой.
- Альтернатива: использовать RabbitMQ с dead letter exchange и TTL, или AWS SQS с redrive policy.
Пример конфигурации топиков (в YAML для Kafka):
topics:
- name: llm_requests_main
partitions: 10
replication: 3
- name: llm_requests_retry
partitions: 5
replication: 3
config:
cleanup.policy: delete
retention.ms: 86400000 # 1 день
- name: llm_requests_dlq
partitions: 3
replication: 3
config:
cleanup.policy: compact # храним последнюю версию для анализа
4. Стратегия повторных попыток (Retry Policy)
Exponential backoff с jitter — стандартный подход:
import random
import time
def next_retry_delay(attempt: int, base_delay: float = 1.0, max_delay: float = 60.0) -> float:
delay = min(base_delay * (2 ** attempt), max_delay)
jitter = random.uniform(0, delay * 0.1) # 10% jitter
return delay + jitter
Параметры:
- max_retries: обычно 3–5 для LLM (больше — слишком долго).
- base_delay: 1–2 секунды.
- max_delay: 30–60 секунд.
- retryable errors: список кодов ошибок, которые стоит повторять (429, 500, 503, таймауты).
Пример обработчика на Python с Kafka:
from kafka import KafkaConsumer, KafkaProducer
import json
consumer = KafkaConsumer('llm_requests_main', bootstrap_servers='localhost:9092')
producer = KafkaProducer(bootstrap_servers='localhost:9092')
MAX_RETRIES = 3
for msg in consumer:
request = json.loads(msg.value)
try:
response = call_llm(request['prompt'])
# успех — сохраняем результат
except RetryableError as e:
request['retry_count'] = request.get('retry_count', 0) + 1
if request['retry_count'] <= MAX_RETRIES:
delay = next_retry_delay(request['retry_count'])
request['next_retry_at'] = time.time() + delay
producer.send('llm_requests_retry', json.dumps(request).encode())
else:
producer.send('llm_requests_dlq', json.dumps(request).encode())
except NonRetryableError as e:
producer.send('llm_requests_dlq', json.dumps(request).encode())
5. Обработка сообщений из DLQ
После попадания в DLQ возможны три сценария:
5.1 Ручной репроцесс (manual reprocess)
- Инженер анализирует ошибку через дашборд (Grafana, Kibana).
- Исправляет причину (например, обновляет API-ключ).
- Вручную отправляет сообщение обратно в main_topic через UI или скрипт.
5.2 Автоматический репроцесс после фикса (auto-reprocess)
- Если ошибка связана с временным сбоем (например, перезагрузка модели), можно настроить scheduler, который периодически (раз в час) переотправляет сообщения из DLQ в retry_topic.
- Важно: добавить TTL (time-to-live) для сообщений в DLQ, чтобы старые запросы не репроцессились бесконечно.
5.3 Алертинг (alerting)
- При появлении нового сообщения в DLQ отправляется алерт в Sentry, PagerDuty или Slack.
- Алерт должен содержать: ID запроса, тип ошибки, время, количество попыток.
Пример алерта через Sentry:
import sentry_sdk
sentry_sdk.capture_message(
f"LLM request failed after retries: {request_id}, error: {error_type}",
level='error'
)
6. Мониторинг и метрики
Для эффективного управления DLQ нужно отслеживать:
| Метрика | Описание | Инструмент |
|---|---|---|
| DLQ depth | Количество сообщений в DLQ | Prometheus + Grafana |
| DLQ age | Время нахождения сообщения в DLQ | Kafka consumer lag |
| Retry rate | Доля запросов, ушедших на retry | Счётчик в приложении |
| Error distribution | Распределение по типам ошибок | Sentry / ELK |
| Reprocess success rate | Доля успешных повторных обработок из DLQ | Логи |
Пороги для алертов:
- DLQ depth > 100 за 5 минут → warning.
- DLQ age > 1 час → critical (возможно, проблема не решается автоматически).
7. Альтернативы Kafka
Не всегда нужен Kafka. Для небольших систем подойдут:
| Очередь | Встроенная DLQ | Задержка | Сложность |
|---|---|---|---|
| RabbitMQ | Dead letter exchange + TTL | Да (TTL) | Средняя |
| AWS SQS | Redrive policy (maxReceiveCount) | Да (visibility timeout) | Низкая |
| Redis Streams | Нет встроенной, реализуется через группы | Нет (нужен consumer) | Высокая |
| Google Pub/Sub | Dead letter topic | Нет (через subscription) | Средняя |
Выбор: если у вас уже есть Kafka для других микросервисов — используйте его. Если LLM-сервис изолирован — SQS или RabbitMQ проще.
8. Best practices
- Идемпотентность: обработчик должен безопасно повторять один и тот же запрос (например, если LLM возвращает тот же результат для того же промпта).
- Дедупликация: используйте уникальный request_id в каждом сообщении. При повторной отправке проверяйте, не был ли запрос уже обработан (через Redis или БД).
- TTL для DLQ: сообщения в DLQ не должны храниться вечно. Установите retention (например, 7 дней), после чего они автоматически удаляются.
- Партиционирование: для Kafka используйте ключ партиции на основе request_id, чтобы гарантировать порядок обработки одного запроса.
- Разделение ошибок: разные типы ошибок могут направляться в разные DLQ (например, dlq_timeout, dlq_rate_limit) для упрощения анализа.
9. Пример полной реализации (схематично)
# consumer.py
from kafka import KafkaConsumer, KafkaProducer
import json, time, random
consumer = KafkaConsumer('llm_requests_main', group_id='llm_worker')
producer = KafkaProducer()
def process_request(request):
# вызов LLM
response = call_llm(request['prompt'], timeout=30)
return response
for msg in consumer:
request = json.loads(msg.value)
try:
result = process_request(request)
# сохраняем результат
except RetryableError as e:
retry_count = request.get('retry_count', 0) + 1
if retry_count <= 3:
delay = min(2 ** retry_count, 60) + random.uniform(0, 2)
request['retry_count'] = retry_count
request['next_retry_at'] = time.time() + delay
producer.send('llm_requests_retry', json.dumps(request).encode())
else:
request['final_error'] = str(e)
producer.send('llm_requests_dlq', json.dumps(request).encode())
except NonRetryableError as e:
request['final_error'] = str(e)
producer.send('llm_requests_dlq', json.dumps(request).encode())
Пет-проект для закрепления
Задача: Реализовать DLQ для LLM-сервиса, который вызывает OpenAI API, с обработкой rate limit и таймаутов.
Инструменты: Python, Kafka (или Redpanda), Docker, Prometheus + Grafana (опционально).
Шаги:
- Разверните Kafka в Docker (один брокер, три топика: main, retry, dlq).
- Напишите producer, который отправляет 1000 запросов с разными промптами (часть из них — заведомо битые, часть — нормальные).
- Напишите consumer, который:
- Настройте consumer для retry_topic, который проверяет
next_retry_atи спит до нужного момента. - Настройте consumer для DLQ, который логирует ошибки и отправляет алерт в консоль (имитация Sentry).
- (Опционально) Добавьте метрики через Prometheus client и визуализируйте глубину DLQ в Grafana.
Ожидаемый результат:
- Все успешные запросы обработаны.
- Rate limit запросы повторены и в итоге выполнены.
- Таймаутные запросы после 3 попыток попали в DLQ.
- Битые промпты сразу в DLQ.
- В логах видно, какие запросы и почему ушли в DLQ.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 238 | Как реализовать retry стратегии для LLM вызовов? |
| 239 | Как обрабатывать ошибки LLM в агентных системах? |
| 241 | Как мониторить и логировать работу AI-агентов? |
| 242 | Как обеспечить отказоустойчивость агентного пайплайна? |
| 243 | Как проектировать fallback для LLM? |
| 245 | Как тестировать устойчивость агентной системы к сбоям? |
Навигация
- Предыдущий: 239
- Следующий: 241
- Индекс: 00. Индекс разборов