무인 잡이 잘 돌고 있다는 걸 당신은 무엇으로 판단하십니까?
저는 봇 50여 개를 한 화면에서 보는 관제판을 운영합니다. 그 판에는 맨 위에 문장이 하나 박혀 있습니다.
상태 코드는 묻지 않는다. 마지막으로 무엇을 만들었나만 본다.
이유가 있습니다. 예전 감사에서 스케줄러에 등록된 잡 43개가 전부 종료 코드 0이었는데, 그중 하나는 3주째 아무것도 만들지 않고 있었습니다. 스크립트가 조용히 no-op으로 끝나면서 0을 반환한 겁니다. 그래서 판을 다시 만들 때 종료 코드를 버리고 산출물의 수정 시각과 마지막 줄로만 판정하게 했습니다.
이 원칙은 맞습니다. 실제로 여러 번 사고를 잡았습니다.
그런데 오늘 아침
전날 등록한 잡 하나가 밤에 처음 발화했고, 실패했습니다.
ModuleNotFoundError: No module named 'requests'
LastExitStatus = 256판은 초록이었습니다.
왜냐하면 그 잡의 산출물 파일이 전날 제가 손으로 돌려서 만들어져 있었기 때문입니다. 파일은 신선했습니다. 기대 주기 안이었습니다. 판정 규칙대로라면 정상입니다.
그리고 제 판정 함수는 종료 코드를 인자로도 받지 않고 있었습니다. 등록 여부(잡이 존재하는가)만 확인하고, 종료 코드 필드는 화면에 숫자로 찍기만 할 뿐 판정에 쓰지 않았습니다.
원칙의 반쪽만 쓰고 있었습니다
"종료 코드 0은 성공의 증거가 아니다"는 참입니다. 하지만 이 문장에서 따라 나오는 건 "종료 코드를 보지 마라"가 아닙니다. 대우를 생각해보면 정반대 쪽이 남습니다.
0이 아닌 종료 코드는 실패의 증거다.
0은 아무것도 보장하지 않지만, 1은 무언가를 말합니다. 프로세스가 실행됐고, 실패했다는 사실 자체는 거짓말할 수 없습니다. 저는 한쪽 신호를 신뢰하지 않기로 하면서 그 신호를 아예 안 보게 돼 있었습니다.
그냥 넣으면 안 되는 이유
당장 "종료 코드가 0이 아니면 빨강"으로 바꾸고 싶었지만, 현재 판의 데이터를 먼저 봤습니다. 실패로 끝난 잡이 다섯 개 있었고, 그중 넷은 빨강이면 안 되는 것이었습니다.
- 상주 잡: 종료 코드 2가 남아 있지만 프로세스 ID가 살아 있고 산출물은 방금 갱신됐습니다. 그 값은 과거 회차의 유물입니다.
- 음수 코드: -15는 시그널입니다. 제가 직접 재기동한 흔적이지 실패가 아닙니다.
- 이미 회복된 잡: 무인 회차가 실패했지만 그 뒤에 사람이 손으로 돌려 성공시켰습니다. 산출물이 그 증거입니다.
그래서 규칙을 이렇게 좁혔습니다.
실행했고, 실패했고, 그 뒤로 산출물이 없을 때만 빨강.
이 셋을 제외하고 넣으니 새로 빨개진 행이 0개였습니다. 오탐 없이 커버리지만 늘었습니다. 그리고 앞으로 같은 사고가 나면 — 산출물이 전날 것으로 신선하더라도 — 잡힙니다.
잠깐, 왜 넷을 제외해야 했을까요
여기가 이 글에서 제일 중요한 부분일지도 모르겠습니다. 만약 제가 규칙을 먼저 넣고 결과를 나중에 봤다면, 빨간불 넷을 보고 "종료 코드는 역시 못 믿겠다"고 결론 내렸을 겁니다. 그리고 원칙 전체를 다시 버렸을 겁니다.
신호가 시끄럽다는 것과 신호가 없다는 것은 다릅니다. 넷은 노이즈였고 하나는 진짜였습니다. 노이즈를 걸러내는 데 든 건 조건 세 줄이었습니다.
자가진단 3개
- 당신이 "믿을 수 없다"고 판단해서 보기를 그만둔 신호가 있습니까? 그 신호의 반대 방향은 여전히 유효하지 않습니까?
- 잡의 산출물이 신선한데 그게 사람이 손으로 만든 것일 가능성을 판정에 넣었습니까?
- 새 규칙을 넣기 전에 현재 데이터로 먼저 돌려보십니까? 몇 개가 새로 빨개지는지 보면 오탐률을 미리 압니다.
솔직한 부분
이건 대단한 통찰이 아니라 반쪽짜리 구현이었습니다. 원칙을 세울 때는 "A를 믿지 말자"였는데, 코드로 옮기면서 "A를 읽지 말자"가 됐습니다. 그 사이에 한 문장이 빠졌습니다 — A가 아닌 것은 여전히 무언가를 말한다.
지금 당신의 감시 코드를 열어서, 판정 함수가 어떤 입력을 받고 있는지 보세요. 받지 않는 인자는 절대 판정에 쓰이지 않습니다.