ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Vercel][운영] Cron Job에서 lock TTL과 reconciliation window를 같은 값으로 두면 missed run과 duplicate run을 왜 같이 키우나
    기타개발지식/풀스택개발 2026. 8. 22. 20:20

    IT 리서치 노트

    [Vercel][운영] Cron Job에서 lock TTL과 reconciliation window를 같은 값으로 두면 missed run과 duplicate run을 왜 같이 키우나

    Vercel Cron Job 장애를 운영하다 보면 어떤 날은 같은 window가 두 번 실행된 것 같고, 어떤 날은 아예 실행이 비어 보인다. 2026년 8월 22일 기준 Vercel 공식 문서는 실패한 invocation을 자동 재시도하지 않고, cron delivery는 best effort이며, 같은 scheduled run이 둘 이상 호출될 수도 있다고 적고 있다. 그래서 lock TTL과 reconciliation window를 같은 값으로 두면 두 종류의 실패를 동시에 잘못 다루기 쉽다. 이 글은 그 두 시간값을 왜 따로 잡아야 하는지 운영 메모 관점에서 정리한다.

    1. 개요

    결론부터 말하면 lock TTL은 동시에 겹치는 duplicate run을 막는 시간이고, reconciliation window는 실패 후 business result가 비었는지 다시 계산하는 시간이다. Vercel Cron Job은 자동 재시도가 없고 delivery는 best effort이며 중복 호출도 가능하므로, 두 값을 같은 숫자로 두면 missed run과 duplicate run을 같은 방식으로 복구하게 된다.

    보통은 lock TTL이 훨씬 짧고, reconciliation window가 더 넓다. lock TTL은 실행 겹침 구간만 막으면 되지만, reconciliation window는 지연된 결과 반영이나 누락 여부를 다시 읽을 수 있을 만큼 넓어야 한다. 이 차이를 메모에 남기지 않으면 운영자가 장애 중에 시간을 늘려야 할지 줄여야 할지부터 흔들린다.

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

    가장 흔한 실수는 두 값 모두 '5분 cron이니 5분'처럼 스케줄 간격에 맞춰 한 번에 정하는 것이다. 하지만 duplicate run은 같은 scheduled run이 짧은 시간 안에 겹쳐 들어오는 문제이고, missed run은 그 window의 business result가 실제로 비었는지 더 넓은 시야에서 확인해야 하는 문제다.

    예를 들어 billing reconciliation job이 5분마다 돈다고 하자. lock TTL을 30분으로 두면 이전 window의 lock이 살아 있는 동안 다음 정상 run도 막을 수 있다. 반대로 reconciliation window를 5분으로 줄이면 네트워크 지연이나 downstream write 지연 때문에 실제 누락 여부를 다시 계산할 시간 자체가 모자랄 수 있다.

    또 사람의 수동 replay는 둘 사이를 더 헷갈리게 만든다. lock TTL이 짧아 duplicate run이 열린 상태에서 운영자가 replay를 누르면 중복 side effect가 커질 수 있다. 반대로 reconciliation window를 짧게 둔 상태에서 replay만 늘리면 누락된 결과가 아닌데도 동일 작업을 반복할 수 있다.

    실제 증상도 다르게 나타난다. 어떤 장애는 로그 조회 응답이 비고 결과 파일만 비어 있으며, 어떤 장애는 같은 window key가 두 번 저장되고 오류 응답은 없을 수 있다. 또 어떤 경우는 수동 실행 직후 다시 같은 결과가 적재되어 재발 위험이 커진다. 이런 증상, 오류, 원인, 재발 신호를 같은 문장에 섞으면 다음 점검이 느려진다.

    • lock TTL을 너무 길게 잡으면 다음 정상 run까지 막을 수 있다.
    • lock TTL을 너무 짧게 잡으면 중복 invocation이 side effect를 다시 만들 수 있다.
    • reconciliation window를 너무 짧게 잡으면 실제 missed result를 놓친다.
    • reconciliation window를 너무 길게 잡으면 비용과 불필요한 재처리 범위가 커진다.
    • 증상과 원인을 분리해 기록하지 않으면 같은 장애가 재발했을 때 로그 확인과 결과 조회가 꼬인다.

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

    실무에서는 시간을 세 개로 나누는 편이 가장 안전하다. 첫째, duplicate run 겹침을 막는 lock TTL. 둘째, 결과 누락을 다시 계산하는 reconciliation window. 셋째, 사람이 성급하게 재실행하지 않게 하는 manual replay hold다. 이 세 값을 따로 적으면 장애 중에도 무엇을 조회하고, 무엇을 확인하고, 무엇을 저장하고, 무엇을 실행 보류할지 분명해진다.

    1. 먼저 View Log를 조회하고 결과 저장소 응답을 함께 확인한다.
    2. 다음으로 같은 window의 invocation 수를 조회하고 lock TTL, idempotency key, 로그 저장 시각을 확인한다.
    3. 이어서 결과 저장소가 비면 reconciliation query를 실행하고 파일이나 메모에 범위를 저장한다.
    4. 마지막으로 애매하면 수동 replay는 hold 상태로 두고 다음 check time을 저장한다.
    분리해서 적어야 하는 필드
    window_key
    lock_ttl
    reconciliation_window
    observed_invocation_count
    result_gap
    manual_replay_state
    next_check_time

    핵심은 lock TTL을 늘려서 missed run을 해결하려 하지 않는 것이다. lock TTL은 중복 side effect 억제용이고, missed run 복구는 결과 집계를 다시 맞추는 reconciliation으로 풀어야 한다. 둘을 섞지 않으면 duplicate와 missed를 같은 숫자로 다루는 나쁜 습관을 끊을 수 있다.

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

    첫 공식 자료는 실패한 cron invocation을 플랫폼이 자동 재시도하지 않는다는 문장이다. 이 전제가 있어야 reconciliation window가 lock TTL보다 넓어야 하는 이유를 설명할 수 있다.

    Vercel Cron Job은 실패한 invocation을 자동 재시도하지 않으므로 결과 누락 복구 창을 따로 설계해야 한다.
    Vercel Cron Job은 실패한 invocation을 자동 재시도하지 않으므로 결과 누락 복구 창을 따로 설계해야 한다.

    즉 duplicate run 방지용 lock과 missed run 복구용 window는 서로 다른 실패면을 다룬다. 이미 no-retry와 queue·workflow 분기 글을 읽었다면, 이번 글은 그 다음 단계인 두 시간값 분리 기준이다.

    두 번째 캡처는 cron delivery가 best effort라는 공식 문구다. 이 문장이 있으면 스케줄 전달이 항상 제시간에 한 번만 일어난다고 가정할 수 없다.

    best effort 전달 전제 때문에 reconciliation window는 실제 business gap을 다시 읽을 수 있을 만큼 넓어야 한다.
    best effort 전달 전제 때문에 reconciliation window는 실제 business gap을 다시 읽을 수 있을 만큼 넓어야 한다.

    여기서 lock TTL을 같은 크기로 잡아 버리면 다음 정상 window까지 막거나, 반대로 복구 범위를 지나치게 짧게 둬 누락된 결과를 놓치기 쉽다.

    세 번째 자료는 같은 scheduled run이 둘 이상 호출될 수 있다는 문장이다. duplicate run 가능성이 문서에 직접 적혀 있으므로 lock TTL은 겹침 구간을 흡수하는 값이어야 한다.

    같은 scheduled run의 중복 호출 가능성 때문에 lock TTL은 동시 실행 겹침을 막는 방향으로 설계해야 한다.
    같은 scheduled run의 중복 호출 가능성 때문에 lock TTL은 동시 실행 겹침을 막는 방향으로 설계해야 한다.

    이때 reconciliation window까지 같은 값으로 맞추면 duplicate 방지와 결과 복구가 같은 시계로 움직여 설계가 뒤엉킨다. missed run과 duplicate run 분리 메모 글에서 왜 field를 따로 적는지와도 이어진다.

    실무에서 가장 많이 섞이는 값이 lock TTL과 reconciliation window다. 이름은 둘 다 시간값이지만, 막는 대상과 실패 신호가 전혀 다르다.

    lock TTL과 reconciliation window를 같은 값으로 두지 말아야 하는 이유를 비교한 표다.
    lock TTL과 reconciliation window를 같은 값으로 두지 말아야 하는 이유를 비교한 표다.

    표를 한 번 정해 두면 장애 회고에서 '왜 15분 lock이 15분 복구 창과 같이 움직였나' 같은 혼선을 줄일 수 있다.

    다섯 번째 자료는 운영 메모 예시다. 같은 window를 다루더라도 lock 만료 시각과 reconciliation 조회 범위를 따로 적어야 다음 판단이 빨라진다.

    lock TTL과 reconciliation window를 분리해 기록하는 최소 운영 메모 예시다.
    lock TTL과 reconciliation window를 분리해 기록하는 최소 운영 메모 예시다.

    특히 메일 발송, 정산, 포인트 적립처럼 side effect가 큰 작업은 이 메모 구조가 없으면 수동 replay가 duplicate run과 겹치기 쉽다.

    마지막 자료는 triage 순서다. duplicate run 의심과 missed run 의심을 같은 분기에 넣지 않고, lock과 결과 집계를 따로 보는 흐름으로 정리했다.

    Vercel Cron Job에서 lock TTL과 reconciliation window를 분리해 점검하는 운영 순서다.
    Vercel Cron Job에서 lock TTL과 reconciliation window를 분리해 점검하는 운영 순서다.

    이 순서대로 보면 lock을 늘릴지, reconciliation 범위를 넓힐지, 아니면 수동 replay를 계속 막을지 결정을 빠르게 고를 수 있다.

    5. 주의사항과 리스크

    첫 번째 리스크는 스케줄 간격과 lock TTL을 동일시하는 것이다. 작업이 5분마다 돈다고 해서 lock TTL도 5분일 필요는 없고, 반대로 30분 lock이 항상 안전한 것도 아니다. 실제 side effect 지속 시간과 downstream idempotency 보장 범위를 보고 정해야 한다.

    두 번째 리스크는 reconciliation window를 '장애가 나면 한 번 돌리는 SQL' 정도로 축소하는 것이다. 자동 재시도가 없고 best effort 전달인 환경에서는 reconciliation이 사실상 missed result를 복구하는 본선이다. 이 범위가 너무 짧으면 복구를 한다고 해도 실제 gap을 놓친다.

    세 번째 리스크는 수동 replay를 너무 빨리 허용하는 것이다. 특히 외부 메일, 결제, 영수증, 메시지 발송처럼 되돌리기 어려운 side effect는 lock TTL, 결과 gap, duplicate marker를 본 뒤에만 replay해야 한다.

    6. 결론

    Vercel Cron Job에서 lock TTL과 reconciliation window를 같은 값으로 두면 duplicate run 방지와 missed run 복구가 한 시계에 묶여 둘 다 어긋나기 쉽다. lock TTL은 동시 실행 겹침을 막는 값이고, reconciliation window는 business result를 다시 맞추는 값이다. 두 값을 분리해서 메모에 남기면 장애 중에도 무엇을 늘리고 줄여야 하는지가 훨씬 분명해진다.

    같은 Vercel branch를 이어 읽는다면 no-retry와 queue 분기 글, missed run과 duplicate run 분리 메모 글까지 함께 보면 runbook이 더 단단해진다.

    7. 참고 링크

    1. https://vercel.com/docs/cron-jobs/manage-cron-jobs
    2. https://vercel.com/kb/guide/troubleshooting-vercel-cron-jobs
Designed by Tistory.