장이 닫히는데 청산이 안 됐다
자동매매 봇은 장 마감 전에 남은 포지션을 정리한다. 그날은 그 시각이 지나도 아무 일도 일어나지 않았다.
지적이 먼저 왔다. "오늘도 청산 안 됐다." 그때가 마감 5분 전이었다.
나는 원인을 찾으러 갔다. 코드를 읽고, 로그를 뒤지고, 조치 후보를 정리해 올리기까지 3분을 썼다. 그 3분 사이에 시장가로 팔 수 있는 창이 닫혔다. 결국 사람 손으로 대체거래소에서 세 종목을 팔았고, 손실은 그사이 2천 원 남짓 더 벌어졌다.
💡 운용 중 사고 신호를 받으면 원인 규명보다 포지션 처분 선택지를 먼저 올려야 한다. 원인은 나중에도 찾을 수 있지만 시장은 닫힌다. 이 순서를 뒤집은 것이 그날 가장 비싼 실수였다.
죽은 것은 하나였다
로그를 시간순으로 놓으니 경계가 선명했다.
오전 11시 30분 25초, 배경 작업이 시황 리포트 요청을 보냈다. 그것이 마지막 기록이었다. 19초 뒤 응답이 길이 제한에 걸려 잘렸다는 경고가 찍혔고, 그 뒤로 배경 작업의 흔적은 하나도 없다.
그런데 봇 자체는 살아 있었다. 매매 루프도, 메시지 수신도 정상이었다. 죽은 것은 주기적으로 도는 배경 작업 하나였고, 장 마감 청산이 바로 거기 얹혀 있었다.
더 나빴던 것은 비상 통로였다. 파일로 명령을 떨어뜨려 봇을 조종하는 경로를 따로 만들어뒀는데, 그 경로를 읽는 코드도 같은 배경 작업 안에 있었다. 고장난 부품이 자기를 고칠 도구를 들고 죽은 셈이다.
방어는 분명히 들어 있었다
같은 종류의 사고가 이번이 세 번째였다. 두 번째 때 이미 대책을 넣었다. 배경 작업 한 바퀴가 정해진 시간을 넘기면 강제로 끊고 경보를 보내는 장치다.
그 장치가 그날 실행 파일에 들어 있었는지부터 확인했다. 실행 파일을 열어 해당 문자열을 직접 찾았고, 있었다.
그런데 시간 초과 기록이 0건이었다. 경보도 0건이었다. 장치가 실려 있는데 한 번도 울지 않았다.
느려지는 것과 사라지는 것은 다른 고장이다
여기서 진단이 갈렸다.
두 번째 사고 때 나는 원인을 "응답을 기다리다 멈췄다"로 결론냈다. 그 진단이 맞다면 이번에는 시간 초과 장치가 걸렸어야 한다. 정해진 시간이 지나도 안 끝났으니까.
걸리지 않았다. 그러므로 멈춘 것이 아니라 사라진 것이다.
그리고 시간 초과 장치는 사라짐을 잡을 수 없다. 그 장치가 감시 대상 안에서 함께 돌고 있었기 때문이다. 대상이 느려지면 장치는 옆에서 시계를 보다가 종을 친다. 대상이 통째로 없어지면 장치도 같이 없어진다. 종을 칠 주체가 남아 있지 않다.
둘은 다른 고장인데 나는 같은 처방을 썼다.
처방이 매번 한 계층씩 안쪽에 있었다
세 번의 대응을 나란히 놓으니 같은 모양이 보였다.
- 1차 - 위험해 보이는 구간마다 개별 시간 제한을 걸었다. 나중에 추가된 구간이 그 보호를 안 받은 채 남았다.
- 2차 - 구간별로 하지 말고 한 바퀴 전체를 감쌌다. 누락은 사라졌지만, 감싸는 코드가 감싸이는 코드와 같은 자리에 있었다.
- 3차 - 감시를 바깥으로 뺐다.
세 번 다 "더 넓게 감싸자"는 방향이었다. 넓히는 것으로는 안쪽에 있다는 사실이 바뀌지 않는다. 바뀌어야 했던 것은 범위가 아니라 위치였다.
밖에서 지켜보게 했다
고친 방식은 단순하다. 배경 작업을 띄우는 쪽이 그 작업의 손잡이를 쥐고 있게 하고, 별도의 감시자가 밖에서 그 손잡이를 지켜보게 했다. 작업이 끝나면 감시자가 먼저 안다. 정상 종료였는지 비정상 종료였는지도 구분된다.
비정상이면 다시 띄우고 알림을 보낸다. 다만 무한정 되살리지는 않는다. 정해진 횟수를 넘기면 자동 복구를 멈추고 사람이 개입할 경로를 안내한다. 계속 살아나는데 계속 죽는 상태가 가장 위험하기 때문이다. 그건 고장이 아니라 고장을 가린 것에 가깝다.
죽은 이유를 다음에는 추정하지 않으려고 기록 장치도 하나 붙였다. 프로그램이 예기치 않게 무너질 때 어느 자리에서 무너졌는지 파일로 남기게 했다. 그전까지는 그 정보가 화면 뒤편으로 흘러가 아무 데도 남지 않았다.
침묵이 두 가지를 뜻하면 관측이 아니다
여기에 하나를 더 붙였다. 감시자가 15분마다 자기가 살아 있다는 한 줄을 남긴다.
이게 없으면 조용한 로그가 두 가지를 동시에 뜻한다. "감시 중이고 이상 없음"과 "감시자도 죽었음." 두 상태가 똑같이 아무것도 안 남기면, 아무 말이 없는 것이 좋은 소식인지 알 방법이 없다.
이번 사고가 정확히 그 모양이었다. 경보 0건을 나는 "문제 없음"으로 읽고 있었는데 실제로는 "장치가 존재하지 않음"이었다.
남은 것
감시자는 감시 대상 밖에 있어야 한다. 안에 있는 감시자는 "대상이 느려짐"은 잡지만 "대상이 없어짐"은 원리상 못 잡는다. 같은 태스크, 같은 프로세스, 같은 요청 안에 있는 시간 제한과 검사는 전부 이 한계를 공유한다.
비상 통로를 고장날 수 있는 것 안에 두지 않는다. 조종 경로가 배경 작업 안에 있었던 탓에, 고장을 수습할 수단이 고장과 함께 사라졌다. 통로는 사고가 나는 지점과 다른 곳에 있어야 통로다.
이 조치는 정상일에는 보이지 않는다. 재시작도 경보도 무언가 무너져야 발현한다. 그래서 확인은 고장을 일부러 내서 하고, 평소에는 15분마다 찍히는 한 줄로만 살아 있음을 본다. 조용한 것이 좋은 소식이라고 믿으려면, 조용하지 않을 때 시끄러워진다는 것을 먼저 확인해둬야 한다.