이미 있는 기능이라 켜기만 하면 될 줄 알았다
종목 뉴스를 모아 판단 재료로 넣는 코드가 있었다. 다른 축을 검토하다가 그 기능을 떠올렸고, 이미 구현돼 있으니 켜기만 하면 되겠다 싶었다.
수집하는 부분도, 결과를 문자열로 만드는 함수도, 그 문자열을 프롬프트에 끼우는 자리도 전부 멀쩡히 있었다. 코드를 읽어서는 흠잡을 데가 없었다.
그래서 한 번 직접 호출해봤다.
응답은 정상이었고 내용은 비어 있었다
응답 코드는 200이었다. 예외도 나지 않았다. 여기까지만 보면 성공이다.
본문을 열어보니 이렇게 적혀 있었다.
최근 1년 내 검색된 '' 뉴스가 없습니다
따옴표 사이가 비어 있다. 검색어가 빈 값으로 넘어가고 있었다.
그리고 한 겹 더 있었다. 우리 코드가 응답에서 찾는 표식이 있는데, 그 표식은 돌아온 본문 어디에도 없었다. 검색어가 제대로 들어갔더라도 결과를 뽑아내지 못했을 것이다.
두 층이 동시에 어긋나 있었다. 하나만 고쳐도 여전히 아무것도 못 가져온다.
진짜 문제는 언제부터인지 모른다는 것이었다
결함 자체는 고치면 된다. 그런데 고치면서 알게 된 것이 더 나빴다.
언제부터 이랬는지 확인할 방법이 없었다.
수집이 실패하면 코드는 빈 문자열을 돌려준다. 흔한 방어다. 그리고 프롬프트를 만드는 쪽은 빈 문자열이면 그 줄을 아예 빼도록 되어 있었다. 이것도 합리적이다. 재료가 없는데 빈 칸을 넣을 이유가 없으니까.
두 합리적인 선택이 만나서 이렇게 됐다.
"뉴스가 원래 없는 종목"과 "수집기가 죽은 상태"가 완전히 같은 관측을 냈다.
로그도 남지 않고, 경보도 울리지 않고, 지표에도 안 잡힌다. 프롬프트에 그 줄이 없는 날이 정상인지 고장인지 구분할 수단이 하나도 없었다. 그래서 며칠짜리 문제인지 몇 달짜리 문제인지 지금도 모른다.
기본값이 정상과 구별되지 않으면 방어가 아니라 은폐다
외부에서 무언가를 가져오는 코드에 실패하면 기본값을 쓴다는 처리를 붙이는 건 흔하다. 나도 그래왔다. 호출 한 번 실패했다고 전체가 멈추면 안 되니까.
이 건이 알려준 것은 그 처리의 조건이다.
그 기본값이 정상 결과와 구별되지 않으면, 방어한 것이 아니라 실패를 숨긴 것이다. 빈 문자열은 "못 가져왔다"와 "가져왔는데 없더라"를 같은 값으로 만든다. 값이 같으면 나중에 되짚을 수 없다.
고친 것은 로직이 아니라 로그였다
수정은 두 층의 결함을 바로잡는 것으로 끝나지 않았다. 오히려 중요한 쪽은 세 경우를 각각 다르게 기록하도록 만든 것이다.
- 정상 수집: 몇 건을 가져왔는지
- 응답은 정상인데 0건: 그 사실을 그대로
- 수집 실패: 실패로
이렇게 갈라두면 다음에 같은 일이 생겨도 언제부터인지는 알 수 있다. 그게 이번에 못 했던 것이다.
남은 것
정상 응답이 정상 동작을 뜻하지 않는다. 200은 서버가 요청을 받았다는 뜻이지 우리가 원한 것을 받았다는 뜻이 아니다.
침묵이 두 가지를 동시에 뜻하면 그 침묵은 정보가 아니다. 이상 없음과 감시 실패가 같은 모습이면, 조용한 날에 무엇이 일어났는지 영영 알 수 없다.
실패를 흡수하는 기본값에는 흡수했다는 흔적을 남긴다. 그 흔적 한 줄이 있고 없고가 나중에 되짚을 수 있느냐를 가른다.