-
[Vercel][운영] Cron Job에서 missed run과 duplicate run이 함께 의심될 때 View Log, lock, reconciliation 메모를 어떤 순서로 남기나기타개발지식/풀스택개발 2026. 8. 21. 09:17
IT 리서치 노트
[Vercel][운영] Cron Job에서 missed run과 duplicate run이 함께 의심될 때 View Log, lock, reconciliation 메모를 어떤 순서로 남기나
Vercel Cron Job 장애를 다루다 보면 어떤 날은 스케줄이 안 돈 것 같고, 어떤 날은 같은 작업이 두 번 돈 것처럼 보인다. 2026년 8월 21일 기준 Vercel 공식 문서를 다시 보면 Cron Job은 실패해도 자동 재시도하지 않고, 전달은 best effort이며, 같은 scheduled run이 둘 이상 호출될 수도 있다. 그래서 운영 메모는 '실패했다'가 아니라 missed run인지 duplicate run인지, 혹은 로그만 비어 있는지부터 분리해야 한다. 이 글은 그 분리를 어떤 순서로 기록해야 다음 복구가 짧아지는지 정리한다.
1. 개요
결론부터 말하면 Vercel Cron Job에서 missed run과 duplicate run이 함께 의심될 때는
증거가 남는지 확인 → 전달 자체가 비었는지와 중복 호출인지 분리 → lock과 idempotent reconciliation을 동시에 적기 → 수동 재실행은 마지막으로 미루기순서가 가장 안전하다. Vercel은 cron 실패를 자동 재시도하지 않고, 배달은 best effort이며, 같은 scheduled run이 중복 호출될 수도 있다고 직접 안내한다.즉 복구 전략도 두 갈래여야 한다. duplicate run은 동시성 제어와 멱등성이 먼저고, missed run은 결과 집계를 다시 맞추는 reconciliation이 먼저다. 하나의 runbook에 이 둘을 같이 적어 두지 않으면 운영자가 배치 결과만 보고 감으로 재실행을 눌러 추가 장애를 만들기 쉽다.
2. 어디서 실제로 막히는가
실무에서 가장 흔한 막힘은 배치 결과 이상을 모두 'cron이 불안정하다'는 한 문장으로 뭉개는 것이다. 특정 시각의 로그가 전혀 없으면 missed run 같지만, 캐싱 때문에 로그만 안 보이는 경우도 있다. 반대로 같은 고객에게 같은 알림이 두 번 갔으면 duplicate run 같지만, 사실은 수동 재실행이 추가로 겹친 것일 수도 있다. 이 셋을 분리하지 않으면 장애 회고가 남지 않는다.
Vercel 공식 문서는 세 가지 중요한 전제를 동시에 준다. 첫째, 실패한 cron invocation은 자동 재시도되지 않는다. 둘째, cron delivery는 best effort다. 셋째, 동일한 scheduled run이 둘 이상 호출될 수도 있다. 이 세 문장을 한 번에 읽으면 운영 질문은 '실패했나'가 아니라
전달이 있었나,동일 window가 몇 번 실행됐나,실행 결과가 결과 저장소에 한 번만 반영됐나로 바뀐다.예를 들어 매일 08:00 정산 작업이 있다고 하자. 08:00 로그가 비었지만 08:05에 결과 테이블은 갱신돼 있을 수 있다. 이 경우는 missed run이 아니라 로그 가시성 문제일 수 있다. 반대로 08:00과 08:01에 두 invocation이 보이고, 결과는 멱등 키 덕분에 한 번만 반영됐을 수 있다. 그 경우 duplicate delivery는 있었지만 business side effect는 막힌 것이다. 또 가장 위험한 경우는 duplicate 호출과 수동 재실행이 함께 겹쳐 고객 노출이 두 번 생기는 상황이다.
- 증상: 특정 시각 로그가 비었는데 결과도 비어 보인다.
- 실패: best effort 전달과 로그 가시성 문제를 분리하지 않는다.
- 증상: 같은 알림, 정산, 이메일이 두 번 처리된 흔적이 보인다.
- 실패: duplicate delivery와 수동 replay를 한 사건으로 섞는다.
- 누락: lock만 남기고 reconciliation window를 적지 않거나, 반대로 reconciliation만 적고 동시성 제어를 빼먹는다.
보이는 현상 놓치기 쉬운 오해 먼저 확인할 증거 해당 window 로그가 없다 실행이 아예 없었다고 단정한다 View Log, force-dynamic, 결과 저장소 집계 같은 작업이 두 번 처리됐다 앱 코드 버그만 의심한다 invocation timestamp, lock, idempotency key 실패 후 수동 재실행이 잦다 재실행이 곧 복구라고 생각한다 reconciliation 기준 window와 side effect 보호 장치 3. 실무에서 적용하는 순서
가장 짧은 점검 순서는 다섯 단계다. 첫째, 그 window에 대한 View Log와 결과 저장소 집계를 같이 본다. 둘째, 로그가 비어 있으면 caching과 관측성 문제를 먼저 배제한다. 셋째, duplicate run이 의심되면 동일한 window key로 두 invocation이 있었는지와 lock 경로를 확인한다. 넷째, missed run이 의심되면 결과 저장소 기준 reconciliation query를 돌려 진짜 누락된 business result가 있는지 본다. 다섯째, 두 분기 모두를 운영 메모에 남긴 뒤에만 수동 재실행 여부를 결정한다.
- 해당 시각의 View Log와 결과 테이블 집계를 같이 확인한다.
- 로그가 비면
force-dynamic과 route caching 여부를 먼저 확인한다. - 중복 호출이 보이면 window key, lock state, idempotency key를 확인한다.
- 결과 누락이 보이면 reconciliation query로 실제 business gap을 계산한다.
- 메모에 missed run, duplicate run, replay 결정 사유를 남긴 뒤에만 수동 실행한다.
핵심은 lock과 reconciliation을 대체 관계로 보지 않는 것이다. lock은 동시에 같은 window가 둘 이상 side effect를 만들지 않게 도와주고, reconciliation은 어떤 window의 결과가 실제로 비었는지 다시 채우는 역할을 한다. duplicate run 경로를 막으려면 lock과 idempotent write가 필요하고, missed run 경로를 막으려면 결과 기준 재계산이 필요하다.
운영 메모에는 최소한 window key, expected run count, observed invocation count, 결과 저장소 gap 여부, 수동 replay 실행 여부를 적는 편이 좋다. 이렇게 해야 다음 사람이 로그 두 줄만 보고 불필요한 replay를 누르지 않는다. 특히 외부 이메일, 결제, 포인트 적립처럼 side effect가 비가역적인 작업은 reconciliation 결과가 비지 않았는데도 수동 재실행을 누르면 duplicate run보다 더 큰 피해가 난다.
정리하면 missed run은 결과 누락을 보는 문제이고, duplicate run은 side effect 중복을 막는 문제다. 둘을 동시에 메모에 두면 복구 순서가 단순해진다.
4. 공식 문서와 예시 화면으로 확인하기
첫 공식 캡처는 Vercel Cron Job 운영의 출발점이다. 실패한 invocation을 플랫폼이 자동 재시도하지 않는다는 문장이 보이면, missed run과 duplicate run을 같은 방식으로 복구하면 안 된다는 전제가 선다.
즉 실패 로그가 보였다고 바로 재실행 버튼부터 찾는 방식은 틀리기 쉽다. 이미 cron no-retry와 queue·workflow 분기 글에서 전제를 확인했다면, 이번 글은 그 다음 단계인 missed run과 duplicate run 분리 메모다.
두 번째 캡처는 best effort 전달 문구다. 이 문장은 특정 시각의 스케줄이 반드시 한 번, 정확히 그 시각에 배달된다고 가정하면 안 된다는 뜻으로 읽어야 한다.
이 문구를 빼먹으면 '로그가 없으니 정상 처리됐겠지'라고 착각하기 쉽다. best effort는 성공 보장이 아니라 전달 특성 설명이므로, 실행 증거와 결과 증거를 따로 남겨야 한다.
세 번째 자료는 같은 scheduled run이 두 번 이상 호출될 수 있다는 문장이다. duplicate run 가능성이 문서에 직접 적혀 있으면, 단순 재실행과 중복 실행을 하나의 장애로 뭉개면 안 된다.
여기서 lock만 쓰면 missed run을 놓치기 쉽고, reconciliation만 쓰면 동시 실행 비용이 커질 수 있다. 둘을 함께 적는 메모가 필요한 이유가 바로 이 지점이다.
네 번째 공식 자료는 로그가 안 보일 때 route caching과 `force-dynamic`을 먼저 확인하라는 KB 화면이다. missed run인지 duplicate run인지 논의하기 전에 증거가 실제로 남는지부터 복구해야 한다.
즉 복구 설계보다 관측성 복구가 앞선다. 이것은 Runtime Logs와 timeout 분기 글과도 이어진다.
실무에서는 duplicate run과 missed run을 어떤 기준으로 갈라 적는지가 제일 중요하다. 두 증상은 모두 '배치 결과가 이상하다'로 보이지만, 먼저 확인할 증거와 복구 방식이 다르다.
표를 먼저 정해 두면 같은 알림을 봐도 누군가는 lock부터, 누군가는 수동 재실행부터 하는 흔들림을 줄일 수 있다.
마지막 자료는 운영 메모 예시다. lock과 reconciliation을 같이 적지 않으면 duplicate run을 막다가 missed run을 놓치거나, 반대로 missed run을 복구하다가 중복 처리를 만들기 쉽다.
이 정도 필드만 고정해도 장애 회고 때 '왜 그때 수동 재실행을 눌렀나'를 설명하기 쉬워진다.
5. 주의사항과 리스크
첫 번째 리스크는 로그가 비어 있다는 이유만으로 곧바로 replay를 눌러 duplicate side effect를 만드는 것이다. 두 번째는 duplicate delivery만 막겠다고 lock을 걸고 결과 누락 탐지를 빼먹는 것이다. 세 번째는 결과 집계가 이미 맞는데도 외부 이메일, 결제, 알림 같은 비가역 side effect를 다시 보내는 것이다.
운영 전에 확인할 것은 세 가지다.
force-dynamic같은 관측성 전제, window 기반 idempotency key, 결과 기준 reconciliation query다. 이 셋이 없으면 best effort delivery와 duplicate delivery를 설명할 수 있어도 실제 복구는 계속 수동 감으로 하게 된다.- 로그 부재는 missed run과 동일하지 않다.
- lock만으로 결과 누락 복구가 되지 않는다.
- reconciliation만으로 동시 side effect 중복이 막히지 않는다.
6. 결론
Vercel Cron Job에서 missed run과 duplicate run이 함께 의심될 때는 자동 재시도 기대를 버리고, 증거와 결과를 분리해서 기록해야 한다. View Log와 결과 저장소를 먼저 맞추고, duplicate run에는 lock과 idempotency를, missed run에는 reconciliation을 적용하면 수동 replay 빈도를 크게 줄일 수 있다.
- 증거 복구가 복구 설계보다 먼저다.
- duplicate run과 missed run은 다른 메모 필드를 가져야 한다.
- 수동 재실행은 마지막 결정으로 미룬다.
같은 Vercel branch를 더 좁혀 보고 싶다면 lock TTL과 reconciliation window를 같은 값으로 두면 왜 missed run과 duplicate run이 같이 커지는지 정리한 2026년 8월 22일 후속 글도 이어서 보면 좋다. 이 글이
증상을 어떤 메모 필드로 분리할지를 다뤘다면, 후속 글은그 필드에 들어갈 시간값을 어떻게 따로 설계할지를 설명한다.7. 참고 링크
'기타개발지식 > 풀스택개발' 카테고리의 다른 글