Files
2nd/10_Wiki/Topics/Domain_Programming/Coding/Messaging_DLQ_Patterns.md
T
Antigravity Agent c24165b8bc refactor(topics): 멀티 에이전트용 지식 재편 — _Common(공통 기본기) + Domain_* 구조
에이전트 8종(대화형/프로그래머 C·S/디자이너/설계자/기획자/QA/PD/PM)에게
[공통 기본 능력 + 롤별 Specialty] 2층으로 지식을 주입하기 위한 재분류.
문서 내용·포맷은 무수정, 폴더 이동만 (6,372개 문서 수 보존 확인).

- Topic_Programming → Domain_Programming (내부 구조 보존)
- Topic_Graphic → Domain_Design
- Topic_Business → Domain_Product
- Topic_General → Domain_General
- _Common 신설: Math(구 Topic_Math_Specialty), Reasoning(구 General/From_Thinking & Reasoning),
  Reasoning_Creativity(구 General/From_창의성), Communication(Poetic_Blog_Writing + From_writing)
- 타 도메인의 From_* 폴더는 유지 (출처 표기일 뿐, 이미 도메인에 맞게 분류된 문서)
- 빈 폴더 정리 (memory/procedures)
- 에이전트→폴더 매핑은 workspace의 .astra/agent-knowledge-map.json (9개 에이전트)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 11:05:56 +09:00

5.1 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
messaging-dlq-patterns Dead Letter Queue — 실패 메시지 처리 Coding draft B conceptual 2026-05-09 2026-05-09
messaging
dlq
error-handling
vibe-coding
language applicable_to
TS / SQS / RabbitMQ / Kafka
Backend
DLQ
dead-letter
poison message
redrive
retry topic

Dead Letter Queue

N번 재시도 후 실패한 메시지 = DLQ 로 보냄. 재시도 무한 / 메시지 손실 둘 다 방지. SQS / RabbitMQ DLX / Kafka retry topics.

📖 핵심 개념

  • Poison message: 처리 불가 — 영원 재시도 시 큐 막힘.
  • Max retries 후 DLQ 로 이동.
  • DLQ 모니터링 + 알람 + redrive (재처리).

💻 코드 패턴

SQS — built-in DLQ

resource "aws_sqs_queue" "main" {
  name                       = "orders"
  visibility_timeout_seconds = 60
  message_retention_seconds  = 4 * 24 * 3600

  redrive_policy = jsonencode({
    deadLetterTargetArn = aws_sqs_queue.dlq.arn
    maxReceiveCount     = 5  # 5번 후 DLQ
  })
}

resource "aws_sqs_queue" "dlq" {
  name                      = "orders-dlq"
  message_retention_seconds = 14 * 24 * 3600
}

CloudWatch alarm: ApproximateNumberOfMessagesVisible > 0 in DLQ.

Redrive (DLQ → main)

# AWS console: Start redrive
# 또는 CLI
aws sqs start-message-move-task \
  --source-arn $DLQ_ARN \
  --destination-arn $MAIN_ARN

RabbitMQ — DLX

await ch.assertExchange('orders.dlx', 'direct', { durable: true });
await ch.assertQueue('orders.dlq', { durable: true });
await ch.bindQueue('orders.dlq', 'orders.dlx', 'orders');

await ch.assertQueue('orders', {
  durable: true,
  arguments: {
    'x-dead-letter-exchange': 'orders.dlx',
    'x-dead-letter-routing-key': 'orders',
    'x-message-ttl': 60_000,
  },
});

ch.consume('orders', async (msg) => {
  if (!msg) return;
  const retries = msg.properties.headers?.['x-retries'] ?? 0;
  if (retries >= 5) {
    return ch.nack(msg, false, false); // DLX 로
  }
  try {
    await handle(msg);
    ch.ack(msg);
  } catch {
    // 다시 publish with x-retries+1
    ch.publish('', 'orders', msg.content, {
      headers: { ...msg.properties.headers, 'x-retries': retries + 1 },
    });
    ch.ack(msg);
  }
});

Kafka — retry topics 패턴

orders            (메인)
orders.retry.5s
orders.retry.30s
orders.retry.5m
orders.dlq
async function handleWithRetry(msg: KafkaMessage) {
  try {
    await handle(msg);
  } catch (e) {
    const retry = Number(msg.headers!['x-retry'] ?? 0);
    const next = ['orders.retry.5s', 'orders.retry.30s', 'orders.retry.5m'];
    const target = retry < next.length ? next[retry] : 'orders.dlq';
    await producer.send({
      topic: target,
      messages: [{
        key: msg.key, value: msg.value,
        headers: { ...msg.headers, 'x-retry': String(retry + 1), 'x-error': String(e) },
      }],
    });
  }
}

각 retry topic 의 consumer 가 delay 후 main 으로 이동.

DLQ 검사 + 재처리

// CLI 도구
async function inspectDlq() {
  const r = await sqs.send(new ReceiveMessageCommand({ QueueUrl: dlqUrl, MaxNumberOfMessages: 10 }));
  for (const m of r.Messages ?? []) {
    console.log(m.MessageId, m.Body);
    console.log('Error:', m.MessageAttributes?.ErrorMessage?.StringValue);
  }
}

async function redriveOne(msgId: string, fixedBody: string) {
  // DLQ → main 재발행
  await sqs.send(new SendMessageCommand({ QueueUrl: mainUrl, MessageBody: fixedBody }));
  // DLQ 에서 삭제
  await sqs.send(new DeleteMessageCommand({ QueueUrl: dlqUrl, ReceiptHandle: ... }));
}

Error meta 첨부

await producer.send({
  topic: 'orders.dlq',
  messages: [{
    key, value,
    headers: {
      ...origHeaders,
      'x-error-type': e.name,
      'x-error-message': e.message,
      'x-error-stack': e.stack?.slice(0, 1000),
      'x-failed-at': new Date().toISOString(),
      'x-original-topic': 'orders',
    },
  }],
});

Alarm

# Prometheus
- alert: DLQGrowing
  expr: rate(sqs_messages_visible{queue="orders-dlq"}[5m]) > 0
  for: 10m
  annotations: { summary: "Orders DLQ growing" }

🤔 의사결정 기준

상황 추천
AWS SQS Built-in redrive policy
RabbitMQ DLX + TTL queue
Kafka Retry topics 패턴
Pulsar Built-in retry/DLQ
작은 처리량 단일 DLQ + 수동 검사
큰 + 자동 복구 자동 redrive + 알람

안티패턴

  • DLQ 없음: 영원 재시도 → 큐 막힘.
  • MaxReceiveCount 너무 높음 (100+): poison 처리 늦음.
  • 너무 낮음 (1): 일시 에러도 DLQ.
  • Error context 없음: 디버깅 불가.
  • Alarm 없음: DLQ 가득 모름.
  • 자동 redrive 무한: 같은 에러 무한 반복. fix 후 manual.
  • DLQ retention 짧음: 분석 전에 사라짐. 14일 권장.

🤖 LLM 활용 힌트

  • maxReceiveCount = 3-5.
  • Error 메타 헤더 첨부.
  • DLQ size alarm 필수.

🔗 관련 문서