행인로 개인 작업실

스케줄러를 멈춰도 일은 멈추지 않는다

영상 생성 작업을 밤새 이어붙이는 스케줄러를 돌리고 있었다. 감시견이 2분마다 스케줄러와 렌더 서버(ComfyUI) 둘 다를 확인한다. 감시견의 첫 번째 원칙은 함부로 되살리지 않는 것이다 — 사람이 멈춘 표시(STOP 파일)를 존중하고, 되살리기는 3회까지만 한다. 그날 밤 문제 대부분은 코드가 실제로 어떻게 도는지와 내가 무엇을 고쳤다고 생각했는지 사이의 간극이었다.

고쳐도 프로세스는 못 보는 수정

가끔 시작하지 못하는 엔진에 대한 예외 처리를 추가하고 저장한 뒤 넘어갔다. 그런데 그 스케줄러는 이 수정을 하기 전부터 이미 떠 있던 프로세스였다. 파이썬 프로세스는 소스 코드를 시작할 때 한 번만 읽는다. 디스크의 파일과 메모리에 올라간 코드가 조용히 서로 다른 프로그램이 되어 있었다. 예외 처리는 발동하지 않았을 것이고, 방금 “고쳤다”고 생각한 바로 그 실패에서 작업이 멈출 뻔했다.

이후로 매번 확인하는 습관이 하나 생겼다 — 프로세스 시작 시각(ps -o lstart)과 파일의 수정 시각(mtime)을 비교하는 것이다. 파일이 더 최신이면 그 프로세스는 옛 코드를 들고 도는 중이다. 확인 비용이 낮아서 이제는 코드를 고칠 때마다 습관처럼 본다.

고아가 된 작업

스케줄러를 재시작하려고 내렸을 때, 그 안에서 돌던 작업도 같이 멈출 거라고 생각했다. 아니었다. 스케줄러는 작업을 제출하고 나서 기다리기만 한다. 죽였을 즈음엔 그 작업이 이미 렌더 서버의 큐에 들어가 있었고, 아무도 지켜보지 않는 채로 계속 돌고 있었다. 재시작한 스케줄러는 그 존재를 몰랐다.

스케줄러를 내릴 땐 렌더 서버의 큐도 같이 비워야 한다는 규칙은 이렇게 생겼다. 제출은 이미 끝나 있으므로, 스케줄러만 죽이면 고아가 된 작업이 계속 돌고, 재출발한 스케줄러는 같은 작업을 또 제출한다. 렌더 서버에 중단 요청을 보내고 큐를 비우는 것까지가 “스케줄러를 멈춘다”의 실제 범위였다 — 스케줄러 프로세스를 죽이는 것과는 다른 일이었다.

두 사건 모두 같은 종류의 실수를 가리킨다. 내가 무엇을 바꿨는지가 아니라, 지금 실제로 무엇이 돌고 있는지를 먼저 물었어야 했다.