Flush가 실패하면 어떻게 될까요? — 삭제 정책과 Retry 전략 이야기
안녕하세요, 벌써 3회차 글쓰기에 접어들었습니다
이번엔 회사에서 삭제 정책을 설계하면서 맞닥뜨린 고민을 나눠보려 해요. Retry를 어떻게 써야 할까, 라는 주제예요.
어떤 상황이었냐면요
저희는 3주 이상 요청이 오지 않은 데이터를 soft delete 하는 기능을 구현하고 있었어요.
동작 방식은 이랬어요.
- 하루치 요청을 인메모리 캐시에 마지막 조회 시각으로 갱신해두고
- 자정이 되면 flush → DynamoDB에 마지막 사용일자를 일괄 업데이트
- 쿠버네티스 롤링 배포 시 캐시 유실을 막기 위해 배포 시그널 수신 시에도 flush (30초 이내 완료)
flush 시 동시성 문제는 스냅샷 복사 방식으로 처리해요. lock을 잠깐 잡고 캐시 전체를 복사한 뒤, 복사본 기준으로 DB에 flush하는 거예요. 덕분에 flush가 수십 초 걸려도 실제 요청에는 영향이 없어요.
그런데 여기서 한 가지 엣지케이스가 보이더라고요.
flush가 실패하면 어떻게 되지?
flush가 실패하면 당일 활동한 데이터의 마지막 사용일자가 업데이트되지 않아요. 이후 소프트 삭제 판별 시 3주 초과로 잘못 판단될 수 있는 거죠. 흔히 발생하는 케이스는 아니지만, 발생하면 꽤 치명적이었어요.
그래서 자연스럽게 Retry 전략을 고민하게 됐어요.
Fault-tolerance, 두 가지 접근법
장애 허용(fault-tolerance)을 위한 대표적인 기술로 Retry와 Circuit Breaker가 있어요.
Retry
요청이 실패하면, 정의된 정책에 따라 지정된 횟수만큼 다시 시도해요.
일시적인 오류에 강한 방식이에요. 네트워크 문제, DB 순간 장애처럼 잠깐 뭔가 삐끗한 상황에 잘 맞아요.
장점은 명확해요. 구현이 단순하고, 재시도 횟수나 간격을 세밀하게 제어할 수 있어요.
다만 주의할 점이 있어요. 오류가 일시적이지 않다면 재시도는 의미가 없고, 오히려 서버에 과부하를 줄 수 있어요. 본질적인 문제 해결 없이 재시도만 반복하면 서비스 신뢰도가 떨어질 수 있거든요.
Circuit Breaker
Retry와 반대 방향의 접근이에요. 실패가 계속되면 요청을 아예 차단해서 시스템 전체를 보호하는 방식이에요.
세 가지 상태를 오가며 동작해요. 정상일 땐 CLOSED, 실패가 임계치를 넘으면 OPEN 상태로 전환되면서 요청을 막아요. 이후 일정 시간이 지나면 HALF_OPEN 상태에서 소량의 요청으로 회복 여부를 확인하고, 성공하면 다시 CLOSED로 돌아가요.
데이터 정확도보다 서비스 안정성이 더 중요한 상황에 적합하지만, Retry보다 구현이 복잡해요.
Retry에도 종류가 있어요
Retry 방식은 크게 세 가지로 나눌 수 있어요.
1. Fixed Backoff
매번 고정된 간격으로 재시도해요. 단순하지만, 여러 서버가 동시에 실패할 경우 문제가 생길 수 있어요.
2. Exponential Backoff
실패할수록 대기 시간을 2배씩 늘려요.
1회 실패 → 1초 대기 → 재시도
2회 실패 → 2초 대기 → 재시도
3회 실패 → 4초 대기 → 재시도
4회 실패 → 8초 대기 → 재시도
즉시 재시도를 반복하면 과부하 상태인 서버에 요청을 퍼붓는 격이 돼요. Exponential Backoff는 서버가 회복할 시간을 벌어줘요. 실제로 AWS 공식 권장 패턴이기도 해요.
3. Jitter (랜덤 딜레이)
Exponential Backoff도 한계가 있어요. 여러 서버가 동시에 실패하면, 동시에 대기하다가 동시에 재시도하거든요.
jitter 없음 → 모두 정확히 2초 후 재시도 → 또 동시에 몰려서 또 실패 (thundering herd)
jitter 있음 → 각자 1.3초, 2.7초, 1.8초… 랜덤하게 재시도 → 요청 분산, 성공 확률 ↑
이걸 Thundering Herd 문제라고 해요. Jitter는 이 문제를 간단하게 해결해줘요.
Exponential Backoff로 계산한 대기 시간에 랜덤값을 더해서 각 서버의 재시도 타이밍을 흩뜨려주는 거예요. 구현도 어렵지 않아요. 복잡한 로직 없이 대기 시간 계산식 하나만 바꿔주면 되거든요.
저희처럼 여러 서버가 자정에 동시에 flush하는 구조라면 특히 Jitter를 고려해볼 만해요.
그래서 저희는 이렇게 설계했어요
저희는 서버 여러 대가 자정에 동시에 flush를 시도하는 구조예요.
최종적으로 단계별 재시도 전략을 이렇게 구성했어요.
1단계 — Exponential Backoff (즉시 재시도) flush 실패 시 AWS 공식 권장 패턴인 exponential backoff로 단기 재시도해요. 1초 → 2초 → 4초, 연속 3회까지 시도해요.
2단계 — 3회 모두 실패하더라도 저희는 4시간마다 정기적으로 flush를 수행해요. 실패 여부와 관계없이 항상 실행되기 때문에, 단기 재시도에서 놓친 항목이 자연스럽게 다음 flush에서 반영돼요. 그리고 날짜가 바뀌기 전, 자정에 최종 flush를 한 번 더 수행해요.
3단계 — 유실 처리 + 알림 자정까지 실패가 지속되면 해당 항목은 유실 처리하고 당일 flush를 시작해요. 매 실패마다 로그를 남기고, 최종 실패 시에만 알림을 발송해요. 중간 실패마다 알림을 보내면 노이즈가 너무 많아지거든요.
결국 저희 상황처럼 정확성이 중요한 배치 작업에서는 Circuit Breaker보다 Retry가 더 적합했고, 별도의 재시도 로직 없이도 정기 flush 구조 덕분에 자연스러운 fault-tolerance를 확보할 수 있었어요.
읽어주셔서 감사해요! 비슷한 상황에서 다른 방식으로 해결하신 분 있으시면 댓글로 공유해주세요 :)
'밍개발일지' 카테고리의 다른 글
| 의미 있는 소통이란 ? (0) | 2026.05.15 |
|---|---|
| AI 시대에 어떤 개발자로 살아남아야 할까 ? (2) | 2026.04.16 |
| 센츄리 + discord 알림 연동 (1) | 2026.03.24 |
| 서준수의 글쓰기 클럽 (0) | 2026.03.24 |
| InnoDB 락 (0) | 2026.03.08 |