ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Cloudflare Queues][운영] oldest message timestamp는 높은데 backlog average는 정상일 때 SLA 문구와 pager 조건을 어떤 시간축으로 따로 쓰나
    기타개발지식/풀스택개발 2026. 8. 20. 09:13

    IT 리서치 노트

    [Cloudflare Queues][운영] oldest message timestamp는 높은데 backlog average는 정상일 때 SLA 문구와 pager 조건을 어떤 시간축으로 따로 쓰나

    Cloudflare Queues 운영에서 `oldest message timestamp`가 높게 보이면 많은 팀이 곧바로 queue 전체 장애처럼 적는다. 하지만 2026년 8월 19일 기준 Cloudflare 공식 문서를 다시 보면, oldest_message_timestamp_ms는 realtime point-in-time 지표이고 backlog, lag, retryCount는 집계 구간 평균이다. 이 글은 oldest message timestamp는 높은데 backlog average는 이미 정상으로 내려온 상황에서 SLA 문구와 pager 조건을 어떤 시간축으로 따로 써야 운영이 덜 꼬이는지 정리한다.

    1. 개요

    결론부터 말하면 Cloudflare Queues incident 문구는 queue 전체 적체와 tail 잔존을 분리해야 한다. backlog average가 정상인데 oldest message timestamp만 높다면 "전체 queue가 아직 밀려 있다"가 아니라 "일부 tail cohort가 남아 있다"에 가깝다. 이때는 backlog messages, lagTime, retryCount, DLQ 상태를 함께 읽어 pager 조건을 정해야 한다.

    즉 oldest_message_timestamp_ms는 단일 시점 신호이고, backlog average와 lagTime은 일정 기간 평균이라는 점을 먼저 팀 공용 문서에 고정해야 한다. 숫자 하나만 보고 page하면 backpressure 완화나 delayed retry tail까지 모두 같은 장애로 취급하게 된다.

    2. 어디서 실제로 막히는가

    실무에서 가장 흔한 혼선은 세 가지다. 첫째, oldest message timestamp가 목표치보다 높다는 이유만으로 queue 전체가 아직 회복되지 않았다고 적는다. 둘째, backlog average와 lagTime은 이미 내려왔는데 retry delay가 남은 cohort 때문에 tail만 오래 보이는 상황을 별도 incident로 분리하지 않는다. 셋째, live queue tail과 DLQ 미처리 tail을 같은 pager 문장으로 다뤄 on-call이 잘못된 대시보드를 열게 만든다.

    Cloudflare metrics 문서는 backlog messages와 bytes를 평균 backlog로, lagTime과 retryCount를 message operations 평균으로, oldest_message_timestamp_ms를 realtime backlog의 point-in-time 값으로 구분한다. 이 차이를 무시하면 "oldest가 높다 = 전체 적체"라는 잘못된 등식을 만들게 된다. 실제로는 평균 backlog가 거의 0에 가깝더라도 retry delay나 ack gap 때문에 오래된 몇 개 메시지가 남아 있을 수 있다.

    또 batching and retries 문서는 upstream 429 상황에서 retry delay를 써서 소비 속도를 늦추는 패턴을 설명한다. 이 경우 oldest timestamp는 일부러 늘어날 수 있다. 반대로 DLQ 문서는 active consumer가 없는 DLQ 메시지가 4일 동안 남을 수 있다고 적고 있다. 그러니 oldest timestamp가 크다고 해서 곧바로 live queue consumer 문제라고 단정할 수 없다.

    • 증상: backlog average는 안정적이지만 oldest message timestamp만 계속 높다.
    • 오류: tail 잔존과 queue 전체 적체를 같은 SLA 문장으로 적는다.
    • 실패: delayed retry tail과 DLQ 미처리를 같은 pager로 보낸다.
    • 재발: on-call이 평균 backlog 그래프와 realtime oldest timestamp를 서로 다른 축으로 읽지 못한다.
    보이는 신호 실제 분기 먼저 확인할 값
    oldest timestamp만 높다 tail cohort 또는 지연 재시도 retryCount, delay 설정, lagTime
    lag와 retry도 같이 오른다 실제 소비 압박 또는 장애 consumer concurrency, upstream 429, ack 흐름
    queue는 안정적인데 오래된 메시지가 계속 있다 DLQ 미처리 또는 long-tail retry DLQ consumer 유무, 메시지 cohort 기록

    3. 실무에서 적용하는 순서

    가장 실용적인 운영 순서는 다섯 단계다. 1단계에서 realtime oldest_message_timestamp_ms와 backlog_count를 같이 찍는다. 2단계에서 같은 기간 backlog average, lagTime, retryCount를 본다. 3단계에서 retry delay가 의도적으로 들어간 cohort인지, upstream 429나 외부 API 보호 때문에 tail이 남는지 확인한다. 4단계에서 DLQ로 이동한 메시지와 active consumer 유무를 따로 본다. 5단계에서 이 결과를 바탕으로 SLA 문장과 pager 조건을 분리한다.

    1. realtime oldest timestamp와 backlog_count를 같은 시각에 기록한다.
    2. 동일 기간의 backlog average, lagTime, retryCount를 묶어 본다.
    3. retry delay 또는 429 backpressure가 있었는지 확인한다.
    4. DLQ consumer 유무와 남은 메시지 cohort를 분리한다.
    5. queue 건강도 문장과 tail 잔존 문장을 अलग게 적는다.

    이 순서대로 보면 alert wording이 깔끔해진다. 예를 들어 backlog average와 lagTime이 정상인데 oldest timestamp만 높다면 "queue throughput recovered, tail cohort remains"처럼 쓰고, retryCount와 lagTime까지 함께 오르면 그때 비로소 queue-wide degradation으로 승격하는 식이다. SLA 문구를 이렇게 나누면 운영자와 제품팀이 같은 숫자를 보고도 서로 다른 해석을 줄일 수 있다.

    GraphQL + realtime 묶음 기록 예시
    realtime.backlog_count=12
    realtime.oldest_message_timestamp_ms=1724061600000
    window_15m.backlog_messages_avg=18
    window_15m.lag_time_avg_ms=2100
    window_15m.retry_count_avg=0.4
    consumer_classification=delayed-retry-tail
    page=false
    why=overall backlog stable, tail cohort still delayed on purpose

    운영자는 같은 시각에 REST API 응답을 조회하고, GraphQL 지표를 확인하고, on-call 로그 파일에 값을 저장하고, retry_delay 설정과 DLQ 설정을 다시 확인해야 한다. 필요하면 콘솔 화면을 열어 비교하고, incident note에 지표를 복사하고, 대응 명령과 설정 값을 같이 기록한다. 이렇게 확인, 조회, 저장, 비교, 실행, 기록 단계를 나눠 두면 oldest timestamp 경보가 실제 오류인지 의도된 backpressure인지 더 빨리 구분할 수 있다.

    핵심은 지표 하나를 절대화하지 않는 것이다. realtime oldest timestamp는 가장 오래된 한 메시지의 나이를 보여 주지만, 서비스 체감 장애나 queue 전체 복구 속도는 평균 backlog와 lag 축에서 더 잘 드러난다. 반대로 backlog average가 좋아졌다고 tail을 무시하면 SLA 약속을 어기게 된다. 그래서 두 시간축을 같은 메모 안에서 अलग게 기록해야 한다.

    4. 공식 문서와 예시 화면으로 확인하기

    첫 공식 화면은 realtime backlog metrics 표다. Cloudflare는 현재 시점의 queue 상태에서 backlog_count, backlog_bytes, oldest_message_timestamp_ms를 따로 내놓는다. 여기서 중요한 점은 oldest message timestamp가 평균 backlog와 다른 시간축을 가진 point-in-time 신호라는 사실이다.

    Cloudflare Queues는 realtime backlog에서 oldest_message_timestamp_ms를 별도 필드로 제공한다.
    Cloudflare Queues는 realtime backlog에서 oldest_message_timestamp_ms를 별도 필드로 제공한다.

    그래서 oldest message timestamp만 높다고 곧 backlog 전체가 회복되지 않았다고 쓰면 안 된다. 평균 backlog, retry 지표, DLQ 이동 여부를 따로 봐야 pager 문구가 정확해진다.

    두 번째 화면은 message operations 지표다. Cloudflare는 lagTime과 retryCount를 평균값으로 집계한다. oldest message timestamp가 point-in-time이라면 lag와 retries는 기간 평균에 가깝다. 이 둘을 같은 incident 문장으로 섞으면 이미 회복 중인 queue를 과도하게 page할 수 있다.

    Cloudflare Queues metrics 문서는 lagTime과 retryCount를 message operations 평균 지표로 구분한다.
    Cloudflare Queues metrics 문서는 lagTime과 retryCount를 message operations 평균 지표로 구분한다.

    즉 평균 backlog가 안정적인지, lag와 retries가 여전히 올라가는지, oldest timestamp만 tail처럼 남는지를 따로 적어야 한다. 기존 tail-cause 글들이 원인을 나누었다면 이번 글은 알림 문구와 SLA 문장을 나누는 운영판이다.

    세 번째 자료는 retry delay 설명이다. Cloudflare는 upstream이 429를 반환할 때 delay messages로 소비 속도를 늦출 수 있다고 적는다. 이 경우 oldest message timestamp는 높아질 수 있지만, backlog average나 concurrency가 바로 악화된다고 단정할 수는 없다.

    Cloudflare 문서는 upstream 429에 대응할 때 retry delay로 소비 속도를 늦추는 방식을 권장한다.
    Cloudflare 문서는 upstream 429에 대응할 때 retry delay로 소비 속도를 늦추는 방식을 권장한다.

    운영팀이 여기서 헷갈리는 부분이 바로 SLA 문구다. 의도적인 backpressure 완화와 비정상 tail 적체를 같은 경보로 쓰면 오탐이 쌓인다.

    네 번째 화면은 DLQ 처리 기한이다. DLQ에 쌓인 메시지가 active consumer 없이도 4일 유지된다는 문장이 있으면, oldest timestamp가 높다는 사실만으로는 live queue tail인지 DLQ 미처리인지 구분할 수 없다는 점이 분명해진다.

    Cloudflare는 active consumer가 없는 DLQ 메시지가 4일 동안 남을 수 있다고 설명한다.
    Cloudflare는 active consumer가 없는 DLQ 메시지가 4일 동안 남을 수 있다고 설명한다.

    따라서 pager 조건에는 live queue tail, delayed retry tail, DLQ 미처리 tail을 각각 다른 문장으로 두는 편이 안전하다.

    실무에서는 지표마다 시간축을 한 번에 보여 주는 표가 가장 유용하다. oldest message timestamp는 현재 queue의 가장 오래된 한 점이고, backlog average와 lag는 집계 구간 평균이다. 이 차이를 표로 고정하지 않으면 운영자가 서로 다른 그래프를 보고 같은 뜻으로 보고한다.

    Cloudflare Queues 지표를 point-in-time과 aggregated time window 기준으로 나눈 표다.
    Cloudflare Queues 지표를 point-in-time과 aggregated time window 기준으로 나눈 표다.

    특히 이미 backlog slope와 oldest age를 같이 보는 글을 읽었다면, 이번 표는 그 지표를 경보 문구로 바꾸는 단계다.

    마지막 자료는 incident 메모 템플릿이다. 어떤 지표를 언제 읽었는지 같은 메모 파일에 남겨 두면, 평균 backlog는 안정적이었는데 oldest timestamp만 tail로 남은 상황을 다음 교대자도 바로 이해할 수 있다.

    Cloudflare Queues tail incident에서 SLA와 pager 조건을 분리해 적는 메모 예시다.
    Cloudflare Queues tail incident에서 SLA와 pager 조건을 분리해 적는 메모 예시다.

    또 retry delay와 lease 재전달 증거를 나누는 글, delayed retry cohort와 ack gap을 가르는 글과 같이 보면 tail 원인과 alert 문구를 같은 runbook에 자연스럽게 묶을 수 있다.

    5. 주의사항과 리스크

    첫 번째 리스크는 oldest timestamp가 높다는 이유만으로 batch size나 consumer concurrency를 바로 올리는 것이다. delayed retry나 ack gap이 원인이라면 이런 변경은 tail 원인을 숨길 뿐 해결하지 못한다. 두 번째는 DLQ 미처리를 live queue consumer 문제로 오해하는 것이다. 세 번째는 의도적인 backpressure 완화와 비정상 적체를 같은 pager severity로 보내는 것이다.

    운영 문구에는 최소한 queue-wide backlog, tail-only delay, DLQ pending 세 문장을 अलग게 마련해 두는 편이 좋다. 그래야 교대자도 어떤 대시보드를 먼저 열어야 하는지 알 수 있다. 특히 retry delay를 광범위하게 쓰는 시스템이라면 oldest timestamp는 원인 분류 없이 단독 경보 조건으로 두지 않는 것이 안전하다.

    • oldest timestamp 단독 경보는 오탐을 부르기 쉽다.
    • retry delay와 DLQ 미처리는 live queue 적체와 अलग게 기록한다.
    • 평균 backlog와 point-in-time oldest timestamp를 같은 시간축으로 설명하지 않는다.

    6. 결론

    Cloudflare Queues에서 oldest message timestamp는 높지만 backlog average가 정상인 상황은 "queue 전체가 아직 밀려 있다"보다 "tail cohort가 남아 있다"에 가깝다. realtime oldest timestamp, 평균 backlog, lagTime, retryCount, DLQ 상태를 각각 다른 시간축으로 읽어 SLA 문구와 pager 조건을 분리하면 운영 소음이 크게 줄어든다.

    • oldest timestamp는 tail 잔존 신호로만 쓴다.
    • queue 전체 건강도는 backlog average와 lagTime으로 쓴다.
    • retry delay와 DLQ 상태를 넣어 pager 문구를 한 단계 더 좁힌다.

    7. 참고 링크

    1. https://developers.cloudflare.com/queues/observability/metrics/
    2. https://developers.cloudflare.com/queues/configuration/batching-retries/
    3. https://developers.cloudflare.com/queues/configuration/dead-letter-queues/
Designed by Tistory.