A client calls a flaky downstream dependency (a pricing API) that is transiently down. The starter retries on failure with no attempt cap and a fixed 1-tick gap — so it hammers the struggling dependency immediately and forever (a retry storm), and on a long outage it never stops. The fix caps attempts at MAX_ATTEMPTS and waits an exponentially growing logical delay between tries (1, 2, 4, 8 ticks), then gives up. Same seed of failures in, same trace out.
You'll predict the failure first, then write the fix and watch your own code drive the behavior.
우리 서버가 가격 API를 부른다. 그런데 그 API가 잠깐 맛이 갔다(일시적 장애). 실패한다.
boolean call(int now) {
calls++;
attemptTicks.add(now);
return calls > failsBefore; // fails while the dep is still down
}“My server calls a pricing API, but it is temporarily down, so the call fails.”
checking microphone…
실패하면 다시 부른다. 그런데 쉬지 않고, 멈추지도 않고, 매 순간 계속 부른다.
⚖️ Trade-off: 쉬지 않고 계속 부르면, 가뜩이나 힘든 API를 우리가 더 두들겨 팬다. 죽어가는 사람 밀치는 셈.
🔧 도구:retry stormthundering herd
“On failure it retries every tick, forever, with no pause.”
checking microphone…
그러면 그 API는 회복할 틈이 없다. 그래서 더 안 살아난다. 이게 '재시도 폭주'다.
⚖️ Trade-off: 끝까지 안 살아나면 우리 코드도 영원히 멈춘다 — 절대 안 끝남.
🔧 도구:retry storm
“Hammering it nonstop keeps the dying service from ever recovering.”
checking microphone…
해결 1: 횟수에 한계를 둔다. 최대 몇 번만 시도하고(MAX_ATTEMPTS), 다 실패하면 깔끔하게 포기한다(-1 반환).
⚖️ Trade-off: 포기하면 그 요청은 실패로 끝난다 — 손님은 답을 못 받음.
✅ Fix: 영원히 매달리는 것보다, 빨리 실패를 알려주는 게 낫다. 한계가 있어야 끝이 난다.
🔧 도구:MAX_ATTEMPTS
int callWithRetry(int now) {
int tick = now;
for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
if (call(tick)) return tick;
}
return -1; // capped: give up cleanly after MAX_ATTEMPTS
}“First, I cap the tries at a max and give up cleanly if they all fail.”
checking microphone…
해결 2: 다시 부르기 전에 점점 더 오래 기다린다. 1번, 2번, 4번, 8번 — 두 배씩 늘려서 쉰다(지수 백오프).
⚖️ Trade-off: 기다리는 만큼 손님이 답 받기까지 더 느려진다(지연 증가).
✅ Fix: 그래도 천천히 두드려야 API가 숨 쉴 틈이 생겨서 살아난다. 그래서 그 정도 느림은 받아들인다.
🔧 도구:exponential backoffBASE * 2^(k-1)
for (int attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
if (call(tick)) return tick;
int gap = BASE << (attempt - 1); // BASE * 2^(k-1) -> 1, 2, 4, 8
tick += gap;
}“Second, I wait longer between each retry, doubling the gap each time.”
checking microphone…
마지막 문제: 손님이 천 명이면, 다들 똑같이 1, 2, 4초에 동시에 다시 부른다. 또 한꺼번에 몰린다.
⚖️ Trade-off: 다 같은 박자로 기다리면, 쉬는 시간이 끝나는 순간 또 떼로 몰려서 다시 폭주가 된다.
✅ Fix: 그래서 각자 기다리는 시간을 살짝 랜덤하게 흩뜨린다(지터). 그럼 한꺼번에 안 몰린다.
🔧 도구:jitter
int gap = BASE << (attempt - 1); int jitter = rng.nextInt(gap + 1); // spread retries so clients don't sync tick += gap + jitter;
“Finally I add a little random jitter so many clients don't retry in lockstep.”
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: concurrency · retry · backoff · resilience
checking microphone…