조용한 고장
이 프로젝트에서 잡은 버그가 거의 전부 성공을 리턴하면서 아무것도 안 했습니다.
기능을 하나씩 붙이는 동안 잡은 버그를 모아 놓고 보니 거의 전부가 같은 모양이었다.
성공을 리턴하면서 아무것도 안 하기
포크해서 쓰던 사람이 이슈 셋을 진단과 패치까지 붙여 보내줬는데, 셋 다 이 모양이었다.
소수점 자릿수 — 슬랙 히스토리 API는 시각 인자의 소수점이 6자리를 넘으면 에러가 아니라 ok: true + 빈 목록을 준다. 파이썬 time.time()은 보통 7자리다. 그래서 채널 맥락 읽기가 몇 주 동안 빈 히스토리로 돌았다. 전수 확인해 보니 실수를 그대로 넘기던 곳은 여기 하나뿐이었다 — 그 하나가 기능 하나를 통째로 무력화했다.
막는 규칙이 허용을 이긴다 — 손님 울타리가 다른 드라이브를 통째로 막는데, 작업 폴더가 그 드라이브에 있으면 허용 목록이 전부 죽는다. 나오는 메시지는 “권한 없음” 한 줄뿐이라 울타리가 아니라 목록이 고장 난 걸로 읽힌다.
별칭이 저장되고 영영 안 뜬다 — 이름 충돌 검사가 출하 명령 이름을 하드코딩하고 있어서 플러그인 명령이 안 보였다. 저장은 성공하고, 부르면 아무 일도 안 일어난다.
문자열이 아니라 구조를 읽는다
명령 하나가 안 먹던 이야기인데, 고치고 나니 기능이 하나 늘었다.
사람이 슬랙 입력창에 !usage라고 치면 잘 되는데, 다른 기계의 세션이 커넥터로 대신 보내면 명령이 전부 빗나갔다. 슬랙이 본문 끝에 출처 표기를 이어붙이기 때문이다. 그것도 줄바꿈 없이 같은 줄에.
증상이 두 갈래로 갈렸다. 앵커가 걸린 명령은 아예 매치가 안 돼 두뇌로 새어 들어갔고(그래서 “그런 명령 없습니다”라고 답했다), 느슨한 명령은 매치는 되고 인자가 오염됐다.
처음엔 그 문구를 잘라내려 했다. 틀린 접근이다 — 출처 표기는 워크스페이스 언어로 번역된다. 한국어, 영어, 일본어가 전부 다르게 온다. 단어로 맞추는 순간 다른 나라 워크스페이스에서 깨진다.
답은 문자열이 아니라 슬랙이 준 구조에 있었다. 출처 표기는 본문에 섞인 게 아니라 맨 뒤에 붙은 별도 블록이고, 사람이 쓴 글은 따로 남아 있다. 그리고 사람은 슬랙 입력창으로 그 블록을 만들 수 없다 — 오탐이 원리적으로 없다는 뜻이다.
고치고 나서 생긴 기능이 이것이다. 다른 기계의 세션이 내 계정으로 슬랙에 글을 올리면 이 PC의 Loki가 그걸 실행하고 스레드로 답한다. 봇이 아니라 사람 명의라 앱을 하나 더 만들 필요가 없다.
생존 확인이 대상을 죽일 수 있다
감시 기능을 만들면서, 워커가 살아 있는지 확인하는 코드를 os.kill(pid, 0)으로 썼다. 유닉스에서는 관용구다 — 시그널 0은 아무것도 보내지 않고 존재 여부만 묻는다.
윈도우 파이썬은 이걸 프로세스 종료로 구현한다. 시그널 0을 포함해서. 생존 확인이 워커를 죽인다.
읽기 전용 조회로 바꿨다. 같은 계열의 함정이 하나 더 있었는데, 재시작이 5분 주기 감시자와 경합해 워커가 셋 붙은 적이 있다. 같은 토큰에 소켓이 셋이면 이벤트가 갈린다. 그런데 헬스체크는 기록된 pid 하나만 보므로 정상이라고 보고한다.
지금은 개수가 아니라 프로세스 계보로 판정한다. 개수로 세면 안 되는 이유가 있다 — 실행 환경상 부모와 자식 두 프로세스가 뜨는 게 정상이라, “둘이면 이상”도 “하나여야 정상”도 틀리다.
자동 감지 하나를 접었다
“대화가 헛돌고 있다”를 자동으로 감지해 넛지에 물리려고 두 번 시도했고 두 번 다 실패했다. 어휘 신규성을 재는 방식이었다.
기각 근거는 튜닝 실패가 아니라 측정 그 자체였다. 실제로 진행되는 대화의 신규성 구간과, 말만 바꿔 같은 자리를 도는 대화의 구간이 겹쳤다. 어떤 임계값도 둘을 못 가른다.
이유는 구조적이다. “같은 주제로 새 질문”과 “같은 교착을 다시 서술”은 쓰는 단어가 같다. 임계값을 조정해 예시 세 개를 통과시키는 건 오버피팅이지 증거가 아니다.
대신 아무것도 이해할 필요 없는 것들로 갔다 — 시간당 요청 상한, 비용 상한, 대화를 명시적으로 끊는 명령. 비용은 이미 응답 안에 들어오고 있었고 버리는 중이었다.
테스트가 라이브 상태를 편집하고 있었다
같은 종류로 두 번 데었다. 각 모듈이 불러올 때 상태 파일 경로를 잡는 구조라, 테스트 준비 코드가 이름으로 하나씩 막지 않으면 라이브 파일이 그대로 편집된다.
실제 사고는 이랬다. 어댑터 테스트가 채널 메시지를 흘려 보냈고, 그게 라이브 상태 파일에 기록되면서 방금 만든 경고 하나가 프로덕션에서 조용히 꺼졌다. 테스트는 전부 통과했다.
지금은 상태 경로 열일곱 개를 통째로 돌려 놓는다. 그리고 새 상태 파일을 만들면 그 목록에 등재하는 게 규칙이다 — 이런 건 다음 사람이 잊는 게 아니라 만든 사람이 잊는다.