느리지만 결국 응답합니다. 자원, 쿼리, 외부 연동이 주 후보입니다.
이 파트에서 다루는 내용
증상은 네 가지로 나뉩니다
장애 증상은 표현이 제각각이지만 실제로는 네 가지 중 하나입니다. 어느 쪽인지 정하면 의심할 경로가 크게 줄어듭니다.
먼저 이 분류를 하지 않고 로그를 뒤지면, 관련 없는 오류 메시지를 붙잡고 시간을 씁니다. 운영 로그에는 평소에도 오류가 쌓여 있기 때문입니다.
타임아웃까지 아무 응답이 없습니다. 스레드 고갈, 락, 무한 대기가 주 후보입니다.
명시적으로 실패합니다. 메시지가 있으므로 가장 다루기 쉽습니다.
가장 어렵습니다. 재현이 안 되므로 조건을 찾는 것이 목표가 됩니다.
느릴 때는 전체인지 일부인지가 갈림길입니다
이 질문 하나로 의심 범위가 반으로 줄어듭니다. 모든 화면이 느린 것과 특정 화면만 느린 것은 원인이 전혀 다릅니다.
- 재기동 직후 괜찮다가 몇 시간 뒤 느려진다면 누수를 강하게 의심합니다.
- 특정 기능만 느린데 그 기능이 외부 시스템을 부른다면, 우리 문제가 아닐 가능성이 높습니다. 먼저 확인해야 엉뚱한 곳을 파지 않습니다.
- DB가 느린 것과 DB를 기다리느라 느린 것은 다릅니다. DB 코스 Part 14의 방법으로 DB 쪽에 실제 부하가 있는지 확인합니다.
CPU 포화, 메모리 부족과 GC 과다, 디스크 I/O, 네트워크, DB 전체 부하. 서버 지표부터 봅니다.
그 기능이 쓰는 쿼리나 외부 연동입니다. DB 코스의 슬로우 쿼리 추적으로 바로 이어집니다.
시간이 갈수록 나빠진다면 무언가 쌓이고 있습니다. 메모리 누수, 커넥션 누수, 데이터 증가, 로그 파일 누적입니다.
배치와 겹치거나 사용자가 몰리는 시간입니다. 그 시간에 무엇이 도는지 확인합니다.
멈췄을 때는 스레드가 답을 갖고 있습니다
응답이 아예 없다는 것은 요청을 처리할 스레드가 없거나, 스레드가 무언가를 기다리며 돌아오지 않는다는 뜻입니다.
이 경우 스레드 덤프가 결정적입니다. 어떤 스레드가 어디에서 멈춰 있는지 그대로 보이기 때문입니다. 읽는 방법은 트랙 B에서 다루고, 여기서는 후보를 좁히는 데 집중합니다.
DB 커넥션을 못 얻어 대기합니다. DB 코스 Part 16에서 DB 쪽 증상까지 함께 다뤘습니다.
상대가 응답하지 않는데 타임아웃이 없으면 영원히 기다립니다. 스레드가 하나씩 묶이다가 전부 고갈됩니다.
DB 코스 Part 11의 blocking_session 조회로 원인 세션을 찾습니다.
자바 코드 안에서 락이 엇갈린 경우입니다. 스레드 덤프에 명시적으로 표시됩니다.
메모리가 부족하면 GC가 계속 돌며 애플리케이션이 사실상 멈춥니다. 겉으로는 응답 없음으로 보입니다.
동시 요청이 풀 크기를 넘으면 대기합니다. 위 원인들의 결과로 나타나는 경우가 더 많습니다.
오류가 날 때는 첫 오류를 찾습니다
오류 메시지가 있으면 절반은 해결된 것입니다. 다만 흔한 실수가 하나 있습니다. 로그의 마지막 오류를 보고 원인이라고 판단하는 것입니다.
장애는 연쇄적으로 번집니다. 처음에 커넥션을 못 얻은 오류가 나고, 그 뒤로 수천 개의 파생 오류가 쌓입니다. 마지막 오류는 결과일 뿐입니다.
찾아야 할 것은 그 시간대에 처음 나타난 오류입니다. 평소 로그와 비교해 새로 등장한 종류를 찾는 것도 방법입니다.
상대 서버에 접속하지 못한 것입니다. 네트워크, 방화벽, 상대 서버 다운을 봅니다.
접속은 됐는데 응답이 늦은 것입니다. 상대가 느린 것이므로 원인이 다릅니다.
null이 들어온 이유가 진짜 원인입니다. 조회가 실패했거나 설정이 없거나 응답 형식이 바뀌었을 수 있습니다.
운영 로그에는 평소에도 오류가 있습니다. 장애 전 로그와 비교해 새로 생긴 것인지 확인합니다.
간헐적이면 조건을 찾는 것이 목표입니다
가장 다루기 어려운 유형입니다. 재현이 안 되니 검증도 안 되고, 고쳤는지도 확신할 수 없습니다.
이때는 원인을 바로 찾으려 하지 말고, 어떤 조건에서 발생하는지부터 좁힙니다. 조건을 알면 재현할 수 있고, 재현하면 나머지는 일반적인 디버깅입니다.
- **특정 서버만인가.** 여러 대로 운영 중이라면 한 대만 상태가 다를 수 있습니다. 접근 로그에서 어느 인스턴스가 처리했는지 확인합니다.
- **특정 시간대인가.** 배치, 백업, 통계 수집 시간과 겹치는지 봅니다.
- **특정 데이터인가.** 특정 고객이나 주문에서만 난다면 그 데이터에 예외적인 값이 있습니다.
- **특정 사용자·단말인가.** 브라우저나 앱 버전, 권한 설정 차이일 수 있습니다.
- **부하와 관련 있는가.** 동시 접속이 늘 때만 난다면 경합이나 자원 한계입니다.
- 발생 시각을 계속 기록해 둡니다. 몇 건이 모이면 패턴이 보입니다. 이것만으로 원인이 잡히는 경우가 많습니다.
당장 원인을 못 찾을 수 있습니다. 그때의 목표는 다음 발생 때 확실히 잡을 준비를 해 두는 것입니다. 로그를 보강하고, 발생 시점 자동 수집을 걸고, 관련 지표를 남깁니다. 이걸 해 두면 다음 발생이 마지막이 됩니다.
계층을 갈라 범위를 반씩 줄입니다
요청은 여러 구간을 지납니다. 사용자 단말에서 웹서버로, 애플리케이션 서버로, DB와 외부 시스템으로 갑니다.
어느 구간이 느린지 모른 채 전부를 의심하면 진도가 안 나갑니다. 구간별로 시간을 재서 자르면 한 번에 절반씩 줄어듭니다.
- 웹서버 접근 로그에 응답시간을 남기면 서버가 실제로 얼마나 썼는지 알 수 있습니다. 사용자 체감과 서버 응답시간이 다르면 네트워크나 브라우저 쪽입니다.
- 애플리케이션 로그에 주요 구간의 소요 시간을 남깁니다. 장애가 난 뒤에 넣으면 늦으므로 평소에 준비해 둡니다.
- DB 구간이 의심되면 DB 코스 Part 14의 방법으로 그 시간대에 실제 부하가 있었는지 확인합니다.
- 외부 연동 구간은 상대의 응답시간을 우리 로그에 남겨 둬야 합니다. 없으면 우리 잘못인지 아닌지 증명할 수 없습니다.
- 터미널·서버 코스의 네트워크 진단으로 구간별 연결 상태를 확인할 수 있습니다.
증상을 네 가지로 분류하고, 느림은 전체인지 일부인지로, 멈춤은 스레드 덤프로, 오류는 첫 오류로, 간헐은 조건 찾기로 접근합니다. 그리고 계층을 갈라 범위를 줄입니다. 여기까지 하면 확인해야 할 후보가 두세 개로 줄어듭니다. 그 후보를 실제로 검증하는 방법이 트랙 B입니다.