디버깅 / 트러블슈팅
추측으로 코드를 고치지 않는다. 원인을 특정하기 전에 수정하지 않는다.
절차
- 증상을 사실로 적는다. 무엇이, 언제부터, 어떤 입력에서, 얼마나 자주. 관측된 것과 해석을 섞지 않는다. ("느리다"가 아니라 "p95 응답이 200ms → 3s")
- 재현 경로를 만든다. 재현되지 않으면 재현 조건을 좁히는 것이 그날의 작업이다. 재현 없이 고친 것은 고쳤는지 알 수 없다.
- 범위를 절반씩 줄인다.
- 언제부터인가 → 그 사이의 변경(배포, 설정, 데이터, 의존 서비스)
- 어디까지 정상인가 → 입력 경계, 계층 경계, 외부 호출 경계에서 값을 확인
- 가설을 하나 세우고, 그것을 반증할 관측을 정한다. "이게 원인이면 X가 보여야 한다." X를 확인한다. 안 보이면 가설을 버린다.
- 최소 변경으로 고친다. 원인이 아닌 부분을 함께 정리하지 않는다.
- 재발을 막는다. 이 버그로 실패했을 테스트를 추가한다. 없으면 고친 것이 아니라 덮은 것이다.
범위를 좁히는 질문
- 특정 사용자/데이터에서만 나는가 → 데이터 의존
- 특정 시간대에만 나는가 → 배치, 타임존, 만료, 트래픽 피크
- 재시작하면 사라지는가 → 상태 누적, 커넥션/스레드 고갈, 캐시
- 한 인스턴스에서만 나는가 → 배포 불일치, 로컬 상태
- 부하가 있을 때만 나는가 → 동시성, 풀 크기, 타임아웃
- 최근에 바뀐 것이 정말 없는가 → 코드 외 변경(설정, 인증서, 의존 서비스, 데이터 양)
증상별 첫 관측 지점
| 증상 | 먼저 볼 것 |
|---|---|
| 간헐적 실패 | 동시성, 재시도, 타임아웃, 외부 의존 |
| 시간이 갈수록 느려짐 | 누적 상태, 커넥션/스레드 풀, 캐시 크기, 데이터 증가 |
| 특정 요청만 느림 | 실행계획 변화, N+1, 외부 호출 지연 |
| 배포 직후 발생 | 변경분 diff, 설정 차이, 스키마-코드 불일치 |
| 응답은 정상인데 데이터가 틀림 | 트랜잭션 경계, 부분 커밋, 순서 의존 |
| 로그에 아무것도 없음 | 예외를 삼키는 지점, 로그 레벨, 다른 인스턴스 |
하지 말 것
- 여러 곳을 동시에 바꾸고 "됐다"고 판단하기
- 재현 없이 "아마 이것 때문"으로 마무리하기
- 예외를 잡아서 로그만 남기고 정상 응답으로 바꾸기 (장애를 숨긴다)
- 타임아웃·재시도 값을 근거 없이 늘려 증상만 미루기
보고 형식
증상: <측정된 사실 + 범위>
재현: <조건 / 재현 불가 시 좁혀진 범위>
원인: <근거가 되는 관측>
수정: <변경 내용과 이유>
재발 방지: <추가한 테스트 또는 관측 수단>
남은 불확실성: <확인하지 못한 것>