일하면서 만난 문제 가운데 제일 비쌌던 것들은 오류가 난 것이 아니라 성공한 것처럼 보였던 것들이다. 터지면 로그가 남고 알림이 오고 그날 고치지만 조용히 실패하면 몇 달 뒤에 다른 문제를 조사하다 우연히 발견한다.
Table of contents
Open Table of contents
반환값으로만 알리는 실패
라이브러리가 실패를 예외가 아니라 반환값으로 알리는 경우가 있는데 호출하는 쪽이 그 값을 안 받으면 실패를 모른다. 내부에서 예외를 잡아 거짓을 돌려주는 함수가 대표적이다.
한 번은 이 때문에 데이터베이스에는 레코드가 있고 실제 파일은 없는 상태가 수백 건 쌓였다. 화면에서는 등록된 것으로 보이고 열면 없으므로 같은 패턴을 쓰는 곳을 전부 찾아 고쳐야 했다.
0건이 가리는 두 가지
없다는 것과 못 찾았다는 것은 다른 사실인데 결과는 똑같이 0건으로 나온다. 이름 정규화가 다르거나 권한이 없어 오류가 버려졌거나 컬럼이 다른 값을 담고 있어도 결과는 같다.
그래서 0건이 나오면 그 조회가 무엇을 측정했는지부터 의심해야 한다. 확실히 존재하는 것으로 한 번 돌려 보면 조회 자체가 도는지 아닌지가 갈린다.
조건이 전부 생략되는 자리
선택적 조건만 덧붙이는 코드에서 값이 전부 비면 조건 자체가 사라진다. 중복 검사에 그런 코드가 있으면 아무것도 넣지 않았을 때 전부가 중복으로 판정된다.
반대 방향은 더 위험한데 대상을 고르는 쿼리에서 조건이 사라지면 전부가 대상이 된다. 어느 쪽이든 코드는 정상으로 돌고 오류도 나지 않는다.
정상처럼 보이는 재시도
작업이 실패했는데 상태를 그대로 두면 계속 재시도되고 재시도가 도는 것 자체는 정상으로 보인다. 어떤 요청이 몇 분마다 다시 시도되며 만 번 넘게 실패하고 있었는데 아무도 몰랐다.
재시도가 돈다는 사실이 오히려 처리 중인 것처럼 보였기 때문이다. 반대로 실패를 완료로 기록하면 재시도 대상에서도 빠져 영영 처리되지 않으므로 이쪽이 더 나쁘다.
갱신을 가로채는 캐시
배치가 데이터를 갱신하고 무효화까지 하는데도 화면이 그대로인 경우가 있었다. 찾아보니 앞단에 로컬 캐시가 하나 더 있었고 그것이 있으면 무조건 먼저 반환하고 있었다.
배치 설계 전체가 무력화돼 있었는데 오류는 하나도 없었다. 무효화가 닿지 않는 층이 하나만 있어도 갱신 경로 전체가 무의미해진다.
성공 개수와 대상 개수의 대조
찾는 방법은 몇 가지로 모인다. 대상 개수와 성공 개수를 대조하고 0건이 나오면 실패해야 하는 입력과 성공해야 하는 입력으로 각각 돌려 대조군을 만든다.
행위의 성공을 결과의 성공으로 삼지 않는 것이 핵심이다. 명령이 성공했다는 것과 의도한 상태가 됐다는 것은 다르므로 만든 개수가 아니라 다시 조회한 개수를 본다.
정리
- 예외 대신 반환값으로 실패를 알리는 함수가 있다
- 0건은 없음과 못 찾음을 구분하지 못한다
- 선택적 조건이 전부 생략되면 조건 자체가 사라진다
- 대상 선택 쿼리에서 조건이 사라지면 전부가 대상이 된다
- 재시도가 도는 것은 정상처럼 보인다
- 실패를 완료로 기록하면 재시도에서도 빠진다
- 앞단 캐시 하나가 갱신 설계를 통째로 무력화한다
- 대상 개수와 성공 개수를 대조하고 0건에는 대조군을 돌린다