디버깅 실전 · 다시 하면 된다는 문제 · 이론
다시 해 보니 되던데요
한 줄 요약
간헐 실패는 "가끔 나는 일" 이 아니라 아직 재지 않은 확률이고, 재는 순간 몇 번을 돌려야 고쳤다고 말할 수 있는지도 함께 정해진다.
왜 이게 필요했나
"다시 해 보니 되던데요." 이 문장이 나오면 대부분의 조사가 거기서 멈춘다. 재현이 안 되니 고칠 수도 없고, 고칠 수 없으니 기록도 남지 않는다. 다음 주에 같은 일이 벌어지면 처음부터 다시 시작한다.
그런데 이 문장에는 이미 자료가 하나 들어 있다. 한 번은 실패했고 한 번은 성공했다. 두 번의 시행 중 한 번이 실패했다는 뜻이다. 물론 이것으로 "실패율 50%" 라고 말할 수는 없다. 시행이 두 번뿐이기 때문이다. 그래서 다음에 할 일은 정해져 있다 — 더 돌려 보는 것이다.
간헐 실패가 특별히 위험한 이유는 따로 있다. 고쳤는지 아닌지를 한 번의 성공으로 판단하게 만들기 때문이다. 실패율이 10%인 문제를 안 고친 채 한 번 돌려 통과하면, 아무 일도 하지 않았는데 고친 것처럼 보인다. 그 확률은 90%다.
어떻게 동작하나
1. 시행 횟수를 적는다. 실패율은 언제나 분수다. "가끔 실패합니다" 는 분자도 분모도 없는 말이다. 200번 돌려 24번 실패했다면 0.12 다.
2. 추정치에 구간을 붙인다. 200번 중 24번과 20번 중 2번은 둘 다 0.1 이지만 믿을 만한 정도가 다르다. 비율의 구간을 구하는 방법은 여럿인데, 실패가 0건이거나 표본이 작을 때도 무너지지 않는 것으로 윌슨 점수 구간이 흔히 쓰인다. 실패 0건일 때 구간의 폭이 0 이 되지 않는다는 점이 중요하다 — 0번 봤다고 확률이 0 인 것은 아니다.
3. 실패한 판의 증거를 그 자리에서 남긴다. 간헐 실패는 다시 부를 수 없다. 시행마다 종료 코드, 표준오류, 걸린 시간, 앞 실행과의 간격을 파일로 적어 두면, 실패한 판에서만 참인 조건이 표에서 저절로 드러난다. 이 기록이 없으면 "그때 뭐라고 나왔더라" 를 물을 대상이 없다.
4. 몇 번을 돌려야 하는지 계산한다. 실패율 p 인 문제를 안 고친 채 n 번 돌려 전부 통과할 확률은 (1-p)의 n 제곱이다. 그 확률을 1-c 이하로 누르려면 다음이 필요하다.
(1 - p)^n <= 1 - c 양변에 로그를 취하면n >= log(1 - c) / log(1 - p)p=0.25, c=0.95 -> n >= 10.4 -> 11번p=0.10, c=0.95 -> n >= 28.4 -> 29번p=0.02, c=0.95 -> n >= 148.3 -> 149번이 표가 말하는 것은 분명하다. 드문 문제일수록 고쳤다는 주장에 더 많은 시행이 든다. 실패율 2%짜리를 한 번 통과시키고 고쳤다고 말하는 것은 아무 말도 하지 않은 것과 같다.
현장에서 만나는 모습
첫째, 관측이 증상을 바꾼다. 로그를 붙이고, 디버거를 걸고, 한 단계씩 돌리면 증상이 사라진다. 타이밍에 달린 문제는 느려지는 순간 조건이 성립하지 않기 때문이다. 이것은 문제가 사라진 것이 아니라 관측 방법이 조건을 바꾼 것이고, 사라졌다는 사실 자체가 단서다. 느리게 돌려 사라진다면 원인은 속도나 순서에 있다.
둘째, 재시도로 덮는다. 재시도는 잘못된 대응이 아니다. 다만 재시도 횟수를 정하려면 실패율이 필요하다. 실패율 25%에 재시도를 세 번 허용하면 네 번 모두 실패할 확률은 0.4%다. 이 계산 없이 "일단 세 번" 을 적으면 근거가 없는 숫자가 운영 문서에 들어간다.
셋째, 격리를 안 한다. 간헐 실패하는 시험 하나가 배포 파이프라인 전체를 열 번에 한 번씩 막으면, 사람들은 결국 실패를 무시하는 습관을 배운다. 그 습관은 진짜 실패에도 똑같이 적용된다. 그래서 고칠 때까지는 격리하고, 격리한 목록과 격리한 날짜를 남긴다.
넷째, 고쳤다는 근거가 한 번의 성공이다. 앞의 계산이 있는 이유다. 고친 뒤 몇 번을 연속으로 통과시켜야 하는지 먼저 정하고, 그 횟수를 돌린 기록을 보고서에 붙인다.
다섯째, 측정 조건을 안 적는다. 같은 작업이라도 잇달아 돌렸을 때와 사이를 벌려 돌렸을 때의 실패율이 다르면, 그 자체가 원인을 가리키는 자료다. 그런데 보고서에 "실패율 25%" 만 적으면 다음 사람은 자기 조건에서 0%를 보고 "재현 안 됨" 으로 닫는다. 시행 횟수와 함께 어떻게 돌렸는지를 적어야 그 숫자가 다시 쓰인다.
여기까지를 한 줄로 줄이면 이렇다. 간헐 실패를 다루는 일은 버그를 찾는 일이기 전에 분수를 만드는 일이다. 분자와 분모가 생기면 그 뒤의 결정 — 재시도를 몇 번 허용할지, 격리할지, 고쳤다고 말해도 되는지 — 이 전부 계산이 된다. 분수가 없으면 그 결정들은 전부 취향이 된다.
실무에서 진짜 중요한 것
- "가끔" 을 분수로 바꾼다. 분모 없는 실패율은 자료가 아니다.
- 0건은 0%가 아니다. 구간을 함께 적는다.
- 증거는 실패한 그 자리에서만 모을 수 있다. 시행마다 기록한다.
- 고쳤다는 주장에는 계산된 시행 횟수가 따라야 한다.
다음 실습에서 할 것
이따금 실패하는 작업을 받아 시행 횟수와 실패 횟수로 실패율과 구간을 재고, 실패한 판에서만 참인 조건을 시행 기록에서 뽑아낸다. 사이를 벌려 다시 재서 관측이 증상을 지우는 것을 직접 보고, 재시도 예산과 격리 기준을 계산으로 정한다. 마지막으로 고친 판을 계산된 횟수만큼 돌려 "고쳤다" 를 근거와 함께 주장한다.