A failing ("poison") message sits at the head of an in-order queue. The broker retries it on every tick but never gives up and never sets it aside, so it loops forever and blocks the good message behind it. Fix it with bounded retries plus a dead-letter queue.
You'll predict the failure first, then write the fix and watch your own code drive the behavior.
메시지가 줄(큐)을 서 있다. 처리기는 항상 맨 앞 하나만 꺼내서 처리하고, 그게 끝나야 다음으로 넘어간다 (순서대로).
String head = queue.peekFirst();
if (!willFail) {
queue.pollFirst();
delivered.add(head);
return true;
}“The broker processes the queue in order, one head message at a time.”
checking microphone…
그런데 맨 앞 메시지가 '독'이다. 무슨 짓을 해도 절대 성공 못 한다. (예: 데이터가 깨져 있어서 영원히 실패)
⚖️ Trade-off: 이 하나는 고칠 방법이 없다. 재시도한다고 갑자기 성공하지 않는다.
// poison: willFail is always true for this message, // so the !willFail branch never runs for it
“But the head message is poison — it can never succeed, no matter how many times we try.”
checking microphone…
버그 있는 코드는 매 틱마다 이 맨 앞 메시지를 계속 다시 시도한다. 포기도 안 하고, 옆으로 치우지도 않는다.
⚖️ Trade-off: 맨 앞이 안 빠지니까 뒤에 있는 멀쩡한 메시지는 차례가 영영 안 온다 (줄 전체가 막힘).
🔧 도구:in-order queue (FIFO)head-of-line blocking
// head-of-line blocking: the failing head is never removed, // so "good" behind it never reaches the front String head = queue.peekFirst(); // still "poison" every tick
“The broker retries that head forever, so the good message behind it never gets its turn.”
checking microphone…
해결 1단계: 시도 횟수를 센다. 한 메시지가 maxRetries번 실패하면 '이건 안 되는 거다'라고 판단하고 그만한다.
✅ Fix: 무한히 매달리지 않고, 정해진 횟수만큼만 해보고 손을 뗀다.
🔧 도구:retry counter / maxRetriesattempt cap
int n = attempts.merge(head, 1, Integer::sum);
if (n >= maxRetries) {
// stop retrying this message
}“I cap the attempts — after maxRetries failures, I stop trying that message.”
checking microphone…
해결 2단계: 포기한 그 메시지를 맨 앞에서 빼내(poll) 따로 만든 '죽은 편지함(DLQ)'에 넣는다. 그러면 뒤에 있던 멀쩡한 메시지가 드디어 처리된다.
✅ Fix: 독 메시지를 옆 통에 격리 → 줄이 다시 흐른다.
🔧 도구:dead-letter queue (DLQ)queue.poll()Kafka/SQS DLQ
if (attempts.get(head) >= maxRetries) {
queue.pollFirst();
dlq.add(head);
return true; // head advances, queue flows again
}“I move the failed message off the head into a dead-letter queue, so the rest can flow.”
checking microphone…
대가: DLQ에 들어간 메시지는 자동으로 처리되지 않는다. 사람이 직접 보거나, 따로 만든 소비자가 다시 꺼내 봐야 한다.
⚖️ Trade-off: '무조건 배달'을 포기하는 셈이다. 독 메시지 하나는 결국 배달 안 된 채 남는다.
✅ Fix: 대신 줄 전체는 살아 움직인다. 멈춘 하나 때문에 전부 죽는 것보다, 하나를 격리하고 나머지를 살리는 게 낫다 (전체 liveness > 한 건의 보장).
🔧 도구:DLQ consumermanual replay / reprocessing
// DLQ is not auto-processed: a separate consumer replays it
for (String msg : broker.dlq) {
inspectAndReplay(msg);
}“The trade-off is those dead-lettered messages need a human or a separate consumer to inspect and replay them.”
checking microphone…
6단계 영어를 다 말하면 → 이 메커니즘 전체를 영어로 설명할 수 있게 된다.
Say it out loud in your own words — hit these terms — then check against the gold answer.
Say it in your own words — but hit these: dead letter queue · retries · message queue · backpressure · java
checking microphone…