정기 작업이 시작된 직후 게이트웨이를 종료해야 하는 상황을 생각해보자. 스케줄러는 이미 작업을 실행했고, 프로세스는 아직 끝나지 않았다. 이때 종료 버튼을 눌렀다는 이유만으로 작업을 성공 처리하면 실제 산출물은 없는데 성공 기록만 남는다. 반대로 무조건 즉시 죽이면 정상 종료까지 몇 초 남지 않은 작업도 잃는다.
이 글이 인용한 동결 구현의 종료 경로는 스케줄러가 추적하는 in-flight cron job ID를 drain의 active-work 수에 포함합니다. 유예가 끝난 뒤 게이트웨이가 전역 도구 subprocess 정리를 수행하면, 그 시점에 남아 있는 cron job ID를 보수적으로 중단된 작업으로 표시해 정상 응답 전달과 후속 success 덮어쓰기를 막습니다.[1][2][3] 이 표시는 PID와 job의 일대일 종료 증거가 아니라 강제 정리 시점의 in-flight 집합에 대한 보수적 판정입니다.
예약됨·시작됨·성공함은 세 상태다
크론 운영 화면에서 09:00 실행 기록이 보인다고 성공한 것은 아니다. 적어도 다음 상태를 구분해야 한다.
scheduled → started → completed
↘ interrupted
↘ failed
scheduled는 실행 의도다. started는 worker가 일을 받았다는 뜻이다. completed는 프로세스가 정상 종료하고 필요한 후처리까지 끝났다는 증거가 있어야 한다. 동결 종료 경로가 전역 도구 subprocess를 강제 정리한 시점에 job이 in-flight였다면 의미상 interrupted로 취급합니다. 구현은 실패한 실행 기록과 종료 이유를 남기고, 그 job thread가 뒤늦게 만든 정상 응답이나 성공 기록이 이를 덮어쓰지 못하게 합니다.[1][2] 여기서 interrupted는 설명을 위한 개념 라벨이지, 모든 저장소가 같은 이름의 별도 enum 필드를 제공한다는 뜻은 아닙니다.
이 구분이 없으면 다음 실행이 잘못된 전제를 쓸 수 있다. 예를 들어 백업 작업이 성공했다고 기록돼 watermark가 앞으로 이동했는데, 실제 압축 파일은 절반만 쓰인 상태라면 다음 백업은 누락을 발견하지 못한다.
drain은 무한 대기가 아니다
안전한 종료가 모든 작 업이 끝날 때까지 영원히 기다린다는 뜻은 아니다. 작업이 교착됐거나 외부 API가 응답하지 않으면 게이트웨이도 내려가지 못한다.
일반적인 drain은 두 단계다.
- 새 작업 수락을 멈추고 현재 in-flight 작업을 센다.
- 유예 시간 안에 끝나지 않은 작업을 중단하고 그 사실을 기록한다.
관련 회귀 테스트는 active cron job ID가 drain 대기에 포함되는지, 모의 전역 도구 subprocess 정리 뒤 in-flight job들이 중단된 것으로 표시되는지, 그리고 해당 job의 늦은 정상 응답·success 기록이 전달되거나 기존 실패 상태를 덮어쓰지 않는지를 확인합니다.[1][2] 별도의 테스트는 종료 중 게이트웨이 이벤트 루프에 예약된 cron delivery가 협력적으로 drain되는지를 확인합니다.[3]
이 구조에서 timeout은 실패를 감추는 장치가 아니라, 시스템 전체의 종료 가능성을 지키는 상한이다.
라이브 게이트웨이를 끄지 않고 재현하기
이 동작을 확인하려고 현재 사용하는 게이트웨이를 실제로 종료할 필요는 없습니다. 합성 in-flight job과 모의 process-registry 정리로 shutdown bookkeeping과 경쟁 방지 계약을 검사할 수 있습니다.
- scheduler의 running-job fixture에 job ID를 등록합니다.
- drain을 시작한 뒤 유예 안에 ID를 제거하면 timeout 없이 반환되는지 확인합니다.
- ID를 남긴 채 drain timeout과 모의 전역 subprocess 정리 경로를 실행합니다.
- 해당 시점의 in-flight job ID가 중단된 것으로 기록되는지 확인합니다.
- job thread가 뒤늦게 정상 결과를 반환해도 정상 응답이 전달되지 않고 success 기록이 실패 상태를 덮어쓰지 않는지 확인합니다.
이 fixture는 shutdown bookkeeping과 경쟁 방지 계약을 확인하는 것이며, 실제 운영체제 신호 전달이 나 특정 cron script의 non-zero exit 및 orphan 정리를 입증하는 실험은 아닙니다.
산출물은 원자적으로 승급해야 한다
종료 상태를 정확히 기록해도 부분 파일이 최종 파일명으로 남으면 소비자가 이를 읽을 수 있다. 긴 크론은 산출물 쓰기에도 두 단계 계약이 필요하다.
임시 파일에 작성
→ 내용·크기·형식 검증
→ 원자적 rename으로 최종 이름 승급
→ 성공 상태 기록
중간에 중단되면 임시 파일은 정리 후보로 남고 최종 이름은 생성되지 않는다. 데이터베이스 작업이라면 트랜잭션 commit이 이 승급 역할을 한다.
watermark도 산출물 승급 뒤에 이동해야 한다. 작업 시작 때 watermark를 먼저 갱신하면 중단된 실행이 처리 범위를 먹어버린다.
종료 유예 시간은 작업 의 commit 지점과 맞아야 한다
모든 작업에 같은 drain 시간을 주면 짧은 상태 점검에는 과하고, 대형 파일 승급에는 부족할 수 있다. 작업은 취소 가능한 구간과 취소하면 안 되는 짧은 commit 구간을 나눌 수 있다.
수집·계산: 취소 가능
임시 산출물 검증: 취소 가능
최종 rename/transaction commit: 짧고 원자적
외부 readback: 중단돼도 다음 실행에서 재확인 가능
유예 시간은 전체 평균 실행시간보다 마지막 원자적 승급을 안전하게 끝낼 시간과 관계가 깊다. 장시간 계산을 무조건 살리려고 drain을 수십 분 늘리는 대신, 중간 checkpoint를 남겨 다음 실행이 이어받게 할 수 있다. 반대로 commit 직전에 매번 즉시 종료하면 거의 완성된 결과가 반복해서 폐기된다.
프로세스가 종료 신 호를 받았을 때 현재 단계와 마지막 안전 checkpoint를 기록하도록 만들면 복구 판단이 쉬워진다. 단, signal handler에서 복잡한 네트워크 호출이나 긴 쓰기를 새로 시작하면 종료가 더 불안정해질 수 있다. handler는 취소 플래그와 최소 메타데이터만 남기고, 일관성은 staging·transaction 구조가 책임지는 편이 낫다.
다음 실행은 interrupted를 어떻게 다뤄야 하나
중단 상태를 남기는 것만으로 복구가 완성되지는 않는다. 다음 실행 정책이 필요하다.
- 멱등 작업: 같은 입력으로 안전하게 재시도
- 부분 산출물 가능: 임시 파일이나 staging row를 정리한 뒤 재시도
- 외부 발송 포함: 발송 idempotency key와 원격 readback으로 중복 여부 확인
- 재실행 위험: 사람 검토 큐로 보내고 자동 재시도 금지
특히 이메일, 결제, 게시처럼 외부 부작용이 있는 작업은 "프로세스가 interrupted"라는 로컬 상태만 보고 다시 보내면 중복이 생길 수 있다. 원격 시스템에서 실제 적용 여부를 읽어본 뒤 결정해야 한다.
확인 범위와 한계
고정된 Hermes 소스의 회귀 테스트에서 job-ID 수준의 active-work 추적, 모의 강제 정리 뒤 중단 표시, 늦은 정상 응답·success 덮어쓰기 차단, 그리고 in-flight delivery의 협력적 drain 계약을 확인했습니다.[1][2][3] 실제 운영체제 subprocess 신호 전달과 orphan 정리는 라이브 장애 실험으로 확인하지 않았습니다. 외부 서비스의 부분 적용 여부도 각 환경에서 별도 검증이 필요합니다.
안전한 종료의 목표는 모든 일을 무조건 살리는 것이 아니다. 완료한 일과 중단된 일을 구분해 다음 판단을 가능하게 만드는 것이다. 성공 기록은 스케줄러가 작업을 시작했다는 사실이 아니라, 산출물과 종료 증거가 함께 남았을 때만 붙어야 한다.
Sources
[1] Hermes Agent, cron active-work drain 회귀 테스트 — https://github.com/NousResearch/hermes-agent/blob/f51aa6a9b5ce514e15f8e337777f522fd5cc6fa2/tests/gateway/test_cron_active_work_drain.py [2] Hermes Agent, cron shutdown interruption 회귀 테스트 — https://github.com/NousResearch/hermes-agent/blob/f51aa6a9b5ce514e15f8e337777f522fd5cc6fa2/tests/cron/test_shutdown_interrupt.py [3] Hermes Agent, gateway cron shutdown drain 회귀 테스트 — https://github.com/NousResearch/hermes-agent/blob/f51aa6a9b5ce514e15f8e337777f522fd5cc6fa2/tests/gateway/test_cron_shutdown_drain.py