-
[Cloudflare Queues][운영] pull consumer에서 visibility_timeout이 지난 뒤 lease_id와 idempotency key를 어느 순서로 다시 검증하나기타개발지식/풀스택개발 2026. 8. 12. 20:15
IT 리서치 노트
[Cloudflare Queues][운영] pull consumer에서 visibility_timeout이 지난 뒤 lease_id와 idempotency key를 어느 순서로 다시 검증하나
Cloudflare Queues pull consumer를 운영하다 보면 같은 메시지가 다시 보일 때 'lease가 만료된 건지', 'retry로 바로 되돌아온 건지', '이미 side effect가 한 번 나간 중복 전달인지'가 한꺼번에 섞이는 순간이 온다. 2026년 8월 12일 기준 Cloudflare 공식 문서를 다시 보면 visibility_timeout이 지난 뒤 기존 lease_id는 더 이상 유효하지 않고, explicit retry는 그 timeout을 기다리지 않고 메시지를 다시 큐에 넣을 수 있다. 이 글은 visibility_timeout이 지난 뒤 lease_id와 idempotency key를 어느 순서로 다시 검증해야 pull consumer triage가 덜 꼬이는지 정리한 것이다.
1. 개요
결론부터 말하면 pull consumer triage는
lease_id 유효성을 먼저 분리하고, 그다음explicit retry 호출 기록, 마지막으로idempotency key와 side effect를 대조하는 순서가 가장 빠르다. visibility_timeout이 지난 뒤에는 기존 lease_id가 더 이상 유효하지 않으므로, 오래된 lease를 붙잡고 ack·retry를 반복하는 동안 실제 원인은 계속 멀어진다.이미 push vs pull replay 글이 consumer 종류별 첫 분기를 다뤘다면, 이번 글은 pull 쪽 안에서 다시
lease expiry와idempotency를 나누는 후속편이다. 또 replay 전 checklist 글과 같이 보면 어떤 메모를 먼저 남겨야 하는지도 바로 이어진다.2. 어디서 실제로 막히는가
실무에서 제일 흔한 실수는 세 가지다. 첫째, 메시지가 다시 보였다는 사실만 보고 lease_id가 아직 유효하다고 가정한다. 둘째, explicit retry를 넣은 직후 다시 읽힌 메시지를 visibility_timeout 만료로 오해한다. 셋째, 같은 idempotency key가 보이면 곧바로 애플리케이션 버그라고 단정하지만, 실제로는 새 lease로 다시 배달된 같은 메시지일 수 있다.
Cloudflare pull consumer 문서는 lease_id가 visibility_timeout 이후 더 이상 유효하지 않다고 분명히 적고 있다. batching and retries 문서는 explicit retry가 timeout을 기다리지 않고 메시지를 즉시 다시 큐에 넣는다고 설명한다. 이 두 문장을 같이 읽으면 '다시 보였다'는 한 증상이 최소 두 개의 완전히 다른 시간축을 가질 수 있다는 사실이 드러난다.
또 metrics 문서는 backlog, message operations, realtime backlog를 따로 제공한다. lease 만료 뒤 메시지가 적체에 그대로 남았는지, 재시도 때문에 read operation이 튄 것인지, oldest_message_timestamp_ms가 오래된 채로 멈춰 있는지를 같이 봐야 한다. body와 message_id만으로는 이 분기가 잘리지 않는다.
- 증상: 같은 message_id가 다시 보이는데 ack가 먹지 않는다.
- 실패: visibility_timeout 이후에도 예전 lease_id를 계속 쓴다.
- 막힘: explicit retry와 lease expiry를 같은 재전달로 취급한다.
- 누락: backlog_count와 oldest_message_timestamp_ms를 같이 안 본다.
증상 먼저 볼 곳 판단 기준 ack 호출이 계속 무효처럼 보인다 lease_id와 visibility_timeout lease 만료 뒤인지 먼저 본다 메시지가 너무 빨리 다시 나타난다 explicit retry 기록 timeout 경로가 아니라 즉시 재큐잉인지 본다 중복 side effect가 의심된다 idempotency key 저장소 새 lease의 재전달인지 별도 버그인지 나눈다 3. 실무에서 적용하는 순서
실무 확인 순서는 다섯 단계가 실용적이다. 먼저 현재 메시지의 lease_id가 아직 유효한지 visibility_timeout 기준으로 분리한다. 두 번째로 retry API를 직접 호출했는지, delay_seconds를 넣었는지 확인한다. 세 번째로 backlog_count와 oldest_message_timestamp_ms를 같이 조회한다. 네 번째로 message operations에서 read spike와 retryCount를 확인하고 기록한다. 마지막으로 그 뒤에야 idempotency key와 외부 side effect를 대조한다.
- lease_id가 아직 유효한지 먼저 자른다.
- explicit retry 호출 기록을 확인한다.
- backlog_count와 oldest_message_timestamp_ms를 본다.
- message operations의 read spike와 retryCount를 본다.
- 그다음 idempotency key와 side effect를 대조한다.
실제로는 pull 호출 시간을 확인하고, ack 요청 본문을 조회하고, backlog 지표를 기록하고, message operations 응답을 비교하고, idempotency key 저장소를 조회하는 순서가 재현 속도를 크게 줄인다. 각 단계에서 무엇을 확인했는지 로그에 기록하고, 같은 시간대의 read spike를 비교하고, retry 호출 유무를 다시 확인하면 중복 증상도 더 짧게 정리된다. 마지막에는 최종 응답과 결정도 다시 기록한다.
이 순서를 지키면 duplicate 원인 분기가 훨씬 빨라진다. lease가 이미 만료됐다면 우선 새 batch를 다시 pull해야 하고, explicit retry를 넣었다면 재전달이 빨랐던 이유가 설명된다. 둘 다 아닌데 side effect만 두 번 나갔다면 그때 비로소 idempotency key 저장과 write-before-check 흐름을 집중해서 보면 된다.
같은 포맷을 팀 runbook에 넣어 두면 DLQ replay 이후 backlog가 줄어도 왜 같은 메시지가 다시 보이는지 더 빨리 설명할 수 있다. pull consumer는 message body보다 lease 수명이 먼저라는 점을 메모 구조 자체에 반영하는 것이 중요하다.
4. 공식 문서와 예시 화면으로 확인하기
첫 실제 자료는 pull consumer 문서의 핵심 문장이다. visibility_timeout이 지나면 기존 lease_id는 더 이상 유효하지 않다.
즉 pull consumer triage에서 오래된 lease_id로 ack나 retry를 다시 시도하는 것은 출발부터 잘못된 증거를 붙잡는 일이다. 먼저 lease 만료 여부를 분리하고, 그다음 idempotency key와 실제 side effect를 대조해야 한다.
두 번째 자료는 retry timing 문장이다. pull consumer에서 retry를 명시적으로 넣으면 visibility_timeout 만료를 기다리지 않고 즉시 큐로 되돌아갈 수 있다.
그래서 같은 메시지가 다시 보였을 때 원인이 lease 만료인지, explicit retry인지, 이미 다른 consumer가 가져간 재전달인지 먼저 구분해야 한다. retry 경로와 timeout 경로를 같은 재시도라고 취급하면 로그가 섞인다.
세 번째 실제 자료는 realtime backlog metrics다. pull consumer에서 lease가 만료된 뒤에는 개별 메시지 body보다 backlog count와 oldest_message_timestamp_ms 변화가 먼저 힌트를 준다.
lease 만료 뒤 메시지가 다시 큐에 남았는지, 적체가 그대로인지, 새로운 write가 쌓이는지 보려면 이 지표가 필요하다. DLQ replay 이후 backlog만 보던 습관으로는 pull consumer 재전달 타이밍을 놓치기 쉽다.
HTTP ack payload를 보면 lease_id가 어디에 들어가는지 명확하다. pull consumer는 이 lease_id 기반으로 ack와 retry를 호출한다.
이 구조를 알면 만료된 lease_id로 재시도를 보내고 있지는 않은지 금방 확인할 수 있다. 이미 push vs pull replay 글을 읽었다면, 이번 코드는 그 안의 pull branch만 더 좁힌 것이다.
lease 만료, explicit retry, duplicate side effect는 같은 '다시 보였다'는 증상으로 보이지만 처리 순서는 다르다. 실무에서는 세 경우를 표로 고정해 두는 편이 빠르다.
특히 idempotency key를 먼저 보되, 그 전에 lease가 아직 유효한지부터 분리하지 않으면 같은 메시지의 서로 다른 delivery instance를 한 건으로 오해하기 쉽다.
마지막 자료는 pull consumer triage 메모다. 메시지 자체보다 먼저 lease 상태, timeout, retry 호출, backlog 지표를 한 줄에 묶어 적는 편이 좋다.
이 메모를 남기면 replay 뒤 backlog time axis를 다룬 backlog recovery 글과도 바로 연결된다. message operations spike와 실제 재전달 시점을 한 묶음으로 보는 감각이 생긴다.
5. 주의사항과 리스크
첫 번째 리스크는 만료된 lease_id로 재시도를 반복하면서 실제 문제를 가리는 것이다. 두 번째 리스크는 explicit retry를 켠 상태에서 재전달 속도를 backlog 적체 탓으로만 오해하는 것이다. 세 번째 리스크는 backlog 지표를 안 보고 idempotency key만으로 duplicate를 판단하는 것이다.
또 retry는 비용과 운영 신호를 모두 바꾼다. Cloudflare 문서는 각 retry가 추가 read operation으로 계산될 수 있다고 설명한다. 따라서 duplicate triage에서 retry 경로를 빨리 분리해 두지 않으면 과금과 적체 신호까지 함께 왜곡된다.
- 만료된 lease와 새 lease를 같은 delivery instance로 취급하지 않는다.
- explicit retry와 timeout 재전달을 분리해서 기록한다.
- idempotency key 판단 전에 backlog와 operations 지표를 같이 본다.
6. 결론
pull consumer에서 visibility_timeout이 지난 뒤에는 기존 lease_id가 더 이상 유효하지 않다. 그래서 triage는 lease 유효성, explicit retry 기록, backlog 지표를 먼저 보고, 그 뒤에야 idempotency key와 side effect를 대조하는 순서가 가장 빠르다.
- lease 만료 여부를 먼저 자른다.
- retry timing과 backlog 지표를 함께 본다.
- 그다음 idempotency key로 진짜 duplicate를 판별한다.
7. 참고 링크
'기타개발지식 > 풀스택개발' 카테고리의 다른 글