이름을 바꿨더니 모두가 말을 잃었다
이름을 바꾸는 일에는 위험이 없다고 생각했다. 새 이름이 마음에 안 들면 다시 바꾸면 되니까. 그런데 이름을 바꾸려면 옛 이름을 지워야 했고, 옛 이름에 매달려 있던 것들이 있었다.
예쁘게 하려던 일이었다
기계 넷에 각각 이름이 붙어 있다. 처음엔 하드웨어 특징에서 따온 실용적인 이름이었다. 어느 시점부터 그게 거슬렸다. 기계가 늘어나고 역할이 갈리면서, 하드웨어 이름은 그 기계가 무엇을 하는지를 전혀 말해주지 않았다.
그래서 신화 이름으로 통일하기로 했다. 사령탑, 대장간, 번개, 저울, 전령. 각자의 역할이 이름에 담기게.
표시 라벨을 바꿨다. 문서를 바꿨다. 코드 주석을 바꿨다. 여기까지는 순수한 문자열 치환이고, 실제로 아무것도 깨지지 않았다. 옛 이름은 별칭으로 남겨서 옛 이름으로 들어오는 입력도 계속 받게 했다.
그리고 마지막 단계로, 메신저 봇 자체의 이름을 바꿨다.
봇을 지우면 열쇠도 사라진다
봇 이름을 바꾸는 과정에서 옛 봇이 삭제됐다.
봇이 삭제되면 그 봇의 접근 토큰이 무효가 된다. 당연한 일이다. 그런데 그 토큰이 어디에 쓰이고 있었는지는 당연하지 않았다.
기계들끼리 서로에게 메시지를 보내는 통로가 있다. 한 대가 작업을 끝내면 사령탑에 보고하고, 사령탑이 결과를 다른 기계에 알린다. 이 통로가 옛 봇의 토큰으로 발신하고 있었다.
토큰이 죽자 발신이 전부 401 로 떨어졌다. 401 은 “네가 누군지 모르겠다” 는 뜻이다.
아무도 소리를 내지 않았다
여기가 진짜 문제다.
메시지를 못 보내게 된 시스템이 그 사실을 알리려면 — 메시지를 보내야 한다. 알림 통로가 죽었는데 알림 통로로 알릴 수는 없다.
그래서 기계 넷이 동시에, 조용히, 각자 실패하고 있었다. 로그에는 남았다. 로그를 그 순간 보고 있는 사람이 없었을 뿐이다.
수리 자체는 간단했다. 새 봇 넷의 토큰과 이름을 자격증명 창고에 다시 넣으면 된다. 커밋 하나로 끝났다. 찾는 데 걸린 시간이 고치는 시간보다 훨씬 길었다.
그날은 마침 조용한 실패를 잡는 날이었다
공교롭게도 같은 날 들어간 다른 수리들이 전부 같은 주제였다.
테스트 하나가 영원히 멈추면 전체 검사가 그 자리에 서 버리는데, 겉으로는 “아직 돌고 있음”과 구분이 안 됐다. 시간 상한을 걸어서 멈춤을 실패로 바꿨다.
경보를 보냈는데 경보 종류가 사전에 등록돼 있지 않아서 조용히 버려지던 것도 있었다. 보내는 쪽은 보냈다고 믿고, 받는 쪽은 온 적 없다고 알고, 둘 다 아무 말이 없었다.
하루에 네 건이었다. 전부 같은 모양이다 — 선언은 성공했는데 실물은 안 도는 상태.
그리고 검증했다는 말을 못 믿게 만들었다
며칠 전 이런 일이 있었다. 어떤 변경을 올리면서 이름 패턴에 맞는 테스트 열일곱 개를 돌리고 “전량 통과”라고 보고했다. 규칙은 원래부터 전량을 요구하고 있었는데, 문서 한 구절이 “영향받는 것만”으로 읽히게 쓰여 있었다. 패턴 밖에 있던 테스트가 깨진 채로 통과했다.
그래서 이날, 검증 결과를 코드 상태에 묶었다.
이제 “다 돌렸습니다”는 주장으로는 부족하다. 실제로 돌린 실행 기록을 붙여야 하고, 그 기록은 지금 코드의 지문과 대조된다. 코드를 한 줄이라도 더 고치면 기록이 낡고, 낡은 기록은 거부된다.
주장은 언제든 낡는다. 하지만 지문에 묶인 기록은 낡을 수가 없다 — 낡는 순간 스스로 그렇다고 말하니까.
배운 것
이름을 바꾸는 작업의 위험은 새 이름 쪽에 없다. 옛 이름에 매달려 있던 것들 쪽에 있다. 표시 라벨 교체와 자격증명 교체는 작업 목록에서 나란히 한 줄씩 차지하지만, 실패했을 때의 모양이 완전히 다르다. 라벨이 틀리면 보기 싫고, 자격증명이 틀리면 아무도 말을 못 한다.
알림 통로의 고장은 알림 통로로 알릴 수 없다. 그러니 알림 통로만큼은 다른 방법으로 살아 있는지 확인해야 한다. 자기 자신을 감시 대상으로 삼는 감시자는 자기가 죽으면 조용하다.
그리고 “다 했다”는 말은 그 말을 한 시점의 코드에만 해당한다. 코드가 움직이면 그 말도 같이 낡아야 한다.
지운 것보다 지운 것에 매달려 있던 게 더 많았다. 늘 그렇다.
— 강대종 / @ssamssae