Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

33장. 장애와 성능 분석 — 로그에서 Root Cause까지, Slow Query와 N+1

6부의 마지막 장이다.

앞의 일곱 장은 Agent가 실수하는 지점을 다뤘다.

이 장은 반대다.

Agent가 사람보다 확실히 잘하는 영역이 있다.


왜 이 영역에서 강한가

장애 분석은 두 가지 일의 반복이다.

넓게 훑기 → 가설 세우기 → 확인 → 다시 훑기

이 중 첫 번째가 사람에게는 지루하고 느리다.

호출 경로를 열 단계 따라가고,
로그 형식이 다른 파일 세 개를 대조하고,
비슷한 이름의 메서드 여섯 개를 구별하는 일.

Agent는 이것을 지치지 않고 한다.

🔥 그리고 가설을 여러 개 동시에 세운다.

사람은 하나를 붙잡고 파는데,
Agent는 다섯 개를 나열하고 각각의 확인 방법을 제시한다.


장애 분석의 순서

flowchart LR
    A[증상] --> B[시간·범위 특정]
    B --> C[로그]
    C --> D[코드 경로]
    D --> E[가설]
    E --> F[재현 테스트]
    F --> G[수정]

F 가 종착점이다.
재현 테스트 없이 수정하면 고쳤는지 알 수 없다.

23장의 원칙이 장애 대응에서도 그대로다.


증상부터 정확히 준다

가장 흔한 실수는 결론을 먼저 주는 것이다.

# ❌ 결론을 준 지시
포인트 환급 로직에 버그가 있는 것 같아. 찾아줘.

# ✅ 증상을 준 지시
어제 14시경 일부 사용자의 포인트 잔액이 음수가 됐어.

- 발생 건수: 12건
- 공통점: 전부 부분 취소를 2회 이상 한 주문
- 로그: PointHistory 에 REFUND 가 3건씩 있음
- 정상 케이스와의 차이는 아직 모름

원인 가설을 세워줘. 코드는 아직 고치지 마.

⚠️ 결론을 주면 Agent는 그 결론을 뒷받침한다.

12장에서 본 오염이 시작부터 들어가는 것이다.

증상만 주면 우리가 생각 못 한 경로를 찾아온다.


로그는 잘라서 준다

13장의 원칙이다.

# ❌
docker logs order-service > all.log        # 200MB

# ✅
docker logs order-service --since 14:00 --until 14:30 \
  | grep "8f3a91c2" | head -100

24장에서 traceId 를 강조한 이유가 여기서 회수된다.

추적 ID가 있으면 한 요청의 전체 흐름을
100줄 안으로 뽑을 수 있다.

없으면 시간 범위로 자르고, 그래도 크면
Agent에게 먼저 필터 조건을 만들게 한다.

이 장애와 관련된 로그를 찾으려고 해.
어떤 키워드로 grep 하면 좋을지 먼저 알려줘.

가설을 여러 개 받는다

Agent의 강점을 쓰는 방법이다.

가능한 원인을 3개 이상 제시해줘.

각각에 대해:
- 근거가 되는 코드 위치 (파일:줄)
- 이 가설이 맞다면 로그에 무엇이 남아 있어야 하는가
- 어떻게 확인할 수 있는가

가장 유력한 것부터 정렬해줘.

두 번째 항목이 특히 유용하다.

가설을 반증 가능한 형태로 만들면
확인이 빨라진다.

가설 2: 부분 취소 시 이전 취소분을 차감하지 않는다
  → 맞다면 3번째 취소의 refundAmount 가
     남은 금액보다 클 것
  → 확인: PointHistory 의 금액 합 vs 원 결제 금액

재현이 종착점

원인을 찾았으면 테스트로 만든다.

이 원인이 맞다면 재현되는 테스트를 만들어줘.
부분 취소를 3회 하는 시나리오로.

지금은 실패해야 정상이야.

실패를 확인한 다음에 수정한다.

9장과 같은 흐름이고,
장애 대응에서 이 순서를 지키기가 더 어렵다.

⚠️ 급하기 때문이다.

그래서 규칙으로 적어둘 가치가 있다.

- 장애 수정은 재현 테스트를 먼저 만든 뒤에 한다
  (핫픽스가 급하면 수정을 먼저 하되, 테스트 추가 없이 종료하지 않는다)

괄호 안이 현실적인 타협이다.


성능 — 측정 없이 최적화하지 않는다

여기서 Agent의 성향이 함정이 된다.

“이 API가 느려” 라고 하면
Agent는 즉시 개선안을 낸다.

캐시를 붙이고, 쿼리를 합치고, 인덱스를 제안한다.

⚠️ 전부 그럴듯하고, 대개 원인이 아니다.

# ❌
주문 목록 API가 느려. 개선해줘.

# ✅
주문 목록 API가 느려 (p95 1.8초).

먼저 원인을 특정하자.
- 실행되는 쿼리를 전부 찾아줘 (JPA 로그 기준)
- 쿼리 개수와 각각의 실행 계획을 확인해줘
- 애플리케이션 로직에서 느릴 만한 곳도 확인해줘

개선안은 원인이 확인된 다음에.

N+1은 Agent가 잘 찾는다

가장 흔한 성능 문제이자
정적 분석으로 잡히는 문제다.

주문 목록 조회에서 N+1이 발생하는 지점을 찾아줘.

- 엔티티 연관관계의 fetch 전략을 확인해줘
- 루프 안에서 lazy 필드에 접근하는 곳을 찾아줘
- 실제 실행되는 쿼리 개수를 로그로 확인해줘

세 번째가 확인 단계다.

logging.level.org.hibernate.SQL=debug

쿼리 개수가 1 + N이면 확정이다.

해결 방법은 여러 개다.

방법주의
fetch join페이징과 함께 쓰면 메모리에서 자름
@EntityGraph조인이 많아지면 카티션 곱
@BatchSize쿼리 수는 줄지만 여전히 여러 번
DTO 직접 조회가장 빠르지만 코드가 늘어남

Agent는 대개 fetch join 을 제안한다.
페이징이 있으면 그게 함정이다.


개선은 숫자로 확인한다

완료 조건에 측정을 넣는다.

## Acceptance Criteria
- 주문 목록 조회 시 실행 쿼리 3개 이하 (기존 41개)
- 로컬 1,000건 데이터에서 응답 200ms 이하
- 실행 계획에 풀스캔 없음
- 기존 주문 테스트 32건 통과

⚠️ 로컬 측정은 참고값이다.

27장에서 말한 대로 데이터 분포가 다르다.
쿼리 개수는 신뢰할 수 있고, 시간은 아니다.

그래서 첫 번째 항목이 가장 확실한 기준이다.


6부를 마치며

여덟 장을 관통하는 패턴이 있다.

Agent가 잘하는 것사람이 해야 하는 것
전수 조사 (누락·패턴 찾기)정책 결정
가설 세우기가설 선택
규칙대로 구현하기규칙 정하기
반복 작업되돌릴 수 없는 실행

왼쪽에 맡기고 오른쪽을 지킨다.

이 구분이 7부와 8부에서
레거시를 다룰 때 그대로 이어진다.


이 장의 핵심

  • 장애 분석은 Agent가 사람보다 확실히 잘하는 영역이다
  • 지치지 않고 훑고, 가설을 여러 개 동시에 세운다
  • 결론을 주면 Agent는 그 결론을 뒷받침한다 — 증상만 준다
  • traceId 가 있으면 한 요청 흐름을 100줄로 뽑을 수 있다
  • 가설은 반증 가능한 형태로 받는다 — “맞다면 무엇이 남아 있어야 하는가”
  • 재현 테스트가 장애 분석의 종착점이다
  • 급할수록 이 순서를 어기게 되므로 규칙으로 적어둔다
  • “느려” 라고만 하면 그럴듯하고 대개 틀린 개선안이 나온다
  • N+1은 쿼리 개수로 확정하고, fetch join 은 페이징과 함께 쓸 때 함정이 있다
  • 로컬에서 쿼리 개수는 신뢰할 수 있고 응답 시간은 참고값이다