새로 등록한 조건을 상주 프로세스가 영원히 못 알아본 이유
코드는 배포됐는데 그 코드를 실행할 프로세스가 그 전부터 떠 있었던 경우
자동매매 시스템에는 "지금은 아니지만 나중 특정 조건이 되면 실행"할 작업을 등록해두는 예약 큐가 있습니다. 최근 이 큐에 새 조건 하나를 추가했는데, 등록한 작업이 한 시간 넘게 아무 반응 없이 조용히 멈춰 있었습니다.
무슨 일이 있었는지
새 조건은 "지금 상주 프로세스를 재시작해도 안전한가"를 판정하는 용도였습니다. 다른 야간 작업과 겹치면 안 되는 재시작이라, 이 조건이 참이 될 때까지 기다렸다가 재시작을 트리거하도록 설계했습니다.
코드를 배포하고 작업을 큐에 등록했습니다. 그런데 조건이 충분히 참일 만한 시간대인데도 한 시간 넘게 발화하지 않았습니다.
이 큐는 5분마다 조건을 다시 평가하는 구조라, 한두 번 놓치는 건 정상입니다. 하지만 한 시간이면 열두 번 넘게 재평가가 돌았을 시점이었습니다.
처음엔 뭐라고 생각했는지
가장 먼저 의심한 건 조건 판정 로직 자체였습니다. "재시작이 안전한 상태"를 판정하는 함수가 다른 야간 작업의 진행 여부를 잘못 읽어서, 실제로는 안전한데도 계속 "아직 안 된다"고 오판하고 있는 게 아닐까 생각했습니다.
판정 함수의 로그를 찾아봤는데, 정작 그 함수가 호출된 흔적 자체가 없었습니다. 조건이 거짓으로 평가된 게 아니라, 애초에 평가를 시도한 적이 없었던 겁니다.
진짜 원인
이 큐를 5분마다 돌리는 주체는 장시간 상주하는 프로세스였습니다. 그리고 그 프로세스는 새 조건을 추가한 배포보다 먼저 떠 있었습니다.
새 조건은 코드 안에 문자열로 등록된 이름(화이트리스트)과, 그 이름을 실제 판정 로직으로 연결하는 분기 두 곳에 나눠 들어갑니다. 배포는 파일 시스템에는 반영됐지만, 이미 메모리에 그 모듈을 올려둔 채 몇 시간째 돌고 있던 프로세스는 그 변경을 전혀 모르는 상태였습니다.
그 프로세스 입장에서 새 조건 이름은 "모르는 값"이었습니다. 이 시스템은 모르는 값이 들어오면 안전 쪽으로 기울여서 "조건 미충족"으로 처리하도록 설계돼 있었습니다. 에러도 안 나고, 예외도 안 던지고, 그냥 조용히 매번 거짓으로 평가됐습니다.
정리하면 이런 순환이었습니다. 새 조건을 평가하려면 그 조건을 아는 프로세스가 있어야 하는데, 그 프로세스를 새로 띄우는 일 자체가 바로 이 조건이 지키려던 "안전한 재시작"이었습니다. 조건이 재시작을 기다리고, 재시작은 조건이 참이 되기를 기다리는 닭과 달걀 문제였습니다.
어떻게 고쳤는지
당장의 조치는 그 프로세스를 한 번 재시작해서 새 코드를 메모리에 올리는 것이었습니다. 재시작하자마자 큐가 정상적으로 조건을 평가하고 작업이 발화했습니다.
근본적인 재발 방지는 코드가 아니라 절차에 걸었습니다. "이 큐에 새 조건을 추가하는 배포는, 등록 직후 반드시 발화 여부를 직접 확인하거나, 그 조건을 평가할 상주 프로세스를 최소 한 번 먼저 재시작해서 새 코드를 태울 것"을 배포 체크리스트에 못박았습니다.
조건 판정 로직 자체를 고치는 방법도 있었겠지만, 이번 사고의 본질은 로직의 버그가 아니라 "배포됐다"와 "실행 중인 프로세스가 그 배포를 안다"가 서로 다른 사건이라는 점을 놓친 것이었습니다. 그래서 그 간극을 없애는 절차를 남기는 쪽을 택했습니다.
일반화하면
오래 떠 있는 프로세스에 화이트리스트나 디스패치 테이블을 추가할 때는, "파일이 바뀌었다"와 "그 파일을 메모리에 올려둔 프로세스가 그 변경을 안다"를 별개의 이벤트로 취급해야 합니다. 인터프리터 기반 언어에서 특히 이 간극이 잘 숨습니다. 코드는 이미 배포됐다는 확신 때문에, 실행 중인 프로세스가 여전히 옛 버전이라는 가능성을 가장 나중에 의심하게 됩니다.
"모르는 값은 안전하게 거짓으로 처리한다"는 설계는 그 자체로는 옳지만, 침묵하는 실패와 결합하면 위험해집니다. 어떤 값이 인식되지 않았을 때 조용히 거짓을 반환하는 대신, 최소한 한 줄이라도 "이 조건 이름을 모른다"는 로그나 경보를 남겨야 합니다. 그래야 판정 로직의 정상적인 거짓과, 애초에 그 조건을 평가할 능력이 없어서 나온 거짓을 구분할 수 있습니다.
배포와 프로세스 생명주기가 분리된 시스템에서는, 디스패치 로직을 건드리는 배포마다 "이걸 실행하는 프로세스도 재시작이 필요한가"를 체크리스트 항목으로 명시하는 게 안전합니다. 사람이 매번 기억하기보다, 배포 스크립트가 변경된 파일 목록을 보고 그 파일을 임포트하는 상주 프로세스 목록을 역으로 알려주는 자동화가 있다면 더 좋습니다.
새로 등록한 것이 예정대로 한 번은 실제로 발화하는지 직접 눈으로 확인하는 습관이 저렴한 보험입니다. 이번 경우 등록 직후 발화 여부를 확인했다면 한 시간을 기다리지 않고 바로 원인을 좁힐 수 있었을 겁니다. "등록됐다"는 확인과 "실제로 작동한다"는 확인은 다른 질문입니다.