4 분 소요


시리즈 소개

  1. Part 1: OpenTelemetry Instrumentation
  2. Part 2: 마이크로서비스 분산 추적
  3. Part 3: 구조화된 로깅과 Correlation ID
  4. Part 4: Prometheus/Grafana로 메트릭과 알림
  5. Part 5: Observability 데이터로 프로덕션 이슈 디버깅 (현재 글)

디버깅 워크플로우

MELT 접근법

Metrics → Events → Logs → Traces

  1. 메트릭으로 문제 감지
  2. 이벤트/알림으로 시점 확인
  3. 로그로 상세 정보 파악
  4. 트레이스로 요청 흐름 추적

실제 장애 시나리오

시나리오 1: 간헐적 타임아웃

증상: 일부 주문 생성 요청이 30초 후 타임아웃

1단계: 메트릭 확인

# P99 레이턴시 급증 확인
histogram_quantile(0.99,
  sum(rate(http_server_requests_seconds_bucket{uri="/api/orders"}[5m])) by (le)
)

Grafana에서 확인: P99 레이턴시가 특정 시간대에 30초까지 급증

2단계: 트레이스 분석

Jaeger에서 느린 요청 검색:

service=order-service minDuration=10s

발견: inventory.checkStock span이 29초 소요

3단계: 로그 확인

{service="inventory-service"} | json | latency > 10000

발견: 특정 상품 ID에서 데이터베이스 쿼리가 느림

4단계: 근본 원인

-- 실행 계획 확인
EXPLAIN ANALYZE SELECT * FROM inventory WHERE product_id = 'PROD-12345';

원인: product_id 인덱스 누락

해결:

CREATE INDEX idx_inventory_product_id ON inventory(product_id);

시나리오 2: 메모리 누수

증상: 서비스가 주기적으로 OOM으로 재시작

1단계: 메트릭 확인

# Heap 메모리 사용량 추세
jvm_memory_used_bytes{area="heap",application="order-service"}

패턴 발견: 메모리가 점진적으로 증가 후 급락 (재시작)

2단계: GC 로그 분석

# GC 빈도 증가
rate(jvm_gc_pause_seconds_count{application="order-service"}[5m])

발견: Full GC 빈도가 점점 증가

3단계: 힙 덤프 분석

# 힙 덤프 생성
jmap -dump:format=b,file=heapdump.hprof <pid>

# MAT 또는 VisualVM으로 분석

발견: OrderCache 객체가 메모리의 80% 차지

4단계: 코드 검토

// 문제 코드
@Component
class OrderCache {
    private val cache = ConcurrentHashMap<String, Order>()

    fun put(orderId: String, order: Order) {
        cache[orderId] = order  // 제거 로직 없음!
    }
}

해결:

@Component
class OrderCache {
    private val cache = Caffeine.newBuilder()
        .maximumSize(10_000)
        .expireAfterWrite(Duration.ofHours(1))
        .build<String, Order>()
}

시나리오 3: 서비스 간 연쇄 장애

증상: 결제 서비스 장애가 전체 시스템 마비로 이어짐

1단계: 의존성 그래프 확인

Jaeger Service Map에서 확인:

  • Order Service → Payment Service (동기 호출)
  • Payment Service 장애 시 Order Service 스레드 블로킹

2단계: 메트릭 확인

# 연결 풀 고갈
hikaricp_connections_active{application="order-service"}
hikaricp_connections_pending{application="order-service"}

발견: Payment Service 타임아웃 동안 모든 연결이 대기 상태

3단계: 로그 확인

{service="order-service"} |= "Connection pool exhausted"

해결: Circuit Breaker 패턴 적용

@Service
class PaymentClient(
    private val circuitBreakerFactory: Resilience4JCircuitBreakerFactory
) {
    private val circuitBreaker = circuitBreakerFactory.create("payment")

    fun processPayment(order: Order): PaymentResult {
        return circuitBreaker.run(
            { paymentApi.charge(order) },
            { fallback -> handleFallback(order) }
        )
    }

    private fun handleFallback(order: Order): PaymentResult {
        // 결제 대기열에 추가하고 나중에 처리
        paymentQueue.add(order)
        return PaymentResult.PENDING
    }
}

디버깅 도구 모음

1. 분산 트레이스 검색 쿼리

# 느린 요청
service=order-service minDuration=1s

# 에러 요청
service=order-service error=true

# 특정 사용자
service=order-service tag.customer.id=CUST-123

2. 유용한 PromQL 쿼리

# 에러율 급증 서비스 찾기
topk(5,
  sum(rate(http_server_requests_seconds_count{status=~"5.."}[5m])) by (application)
)

# 레이턴시 급증 엔드포인트
topk(5,
  histogram_quantile(0.99,
    sum(rate(http_server_requests_seconds_bucket[5m])) by (uri, le)
  )
)

# 메모리 사용량 상위 서비스
topk(5,
  jvm_memory_used_bytes{area="heap"} / jvm_memory_max_bytes{area="heap"}
)

3. 유용한 LogQL 쿼리

# 에러 로그 집계
sum by (errorType) (
  count_over_time({service="order-service"} | json | level="ERROR" [1h])
)

# 특정 traceId의 모든 로그
{service=~".+"} |= "traceId=abc123"

# 느린 쿼리 로그
{service=~".+"} | json | queryTime > 1000

On-Call 플레이북

서비스 다운 시

  1. 즉시 확인
    • up{job="spring-boot-apps"} 메트릭 확인
    • Pod 상태 확인: kubectl get pods
  2. 최근 변경 확인
    • 최근 배포 이력
    • 설정 변경
  3. 로그 확인
    • 시작 로그에서 에러 확인
    • OOM 여부 확인
  4. 롤백 결정
    • 빠른 복구가 필요하면 이전 버전으로 롤백

성능 저하 시

  1. 영향 범위 파악
    • 전체 서비스? 특정 엔드포인트?
  2. 병목 지점 식별
    • 트레이스로 느린 span 확인
    • 외부 의존성 문제?
  3. 리소스 확인
    • CPU, 메모리, 디스크 I/O
    • 연결 풀 상태
  4. 임시 조치
    • 스케일 아웃
    • Rate limiting 적용

포스트모템 템플릿

# 장애 보고서: [제목]

## 개요
- 발생 시간: YYYY-MM-DD HH:MM ~ HH:MM (KST)
- 영향 범위: [서비스명, 사용자 수]
- 심각도: [Critical/High/Medium/Low]

## 타임라인
- HH:MM - 첫 번째 알림 발생
- HH:MM - 조사 시작
- HH:MM - 근본 원인 파악
- HH:MM - 수정 배포
- HH:MM - 정상화 확인

## 근본 원인
[상세 설명]

## 해결 방법
[수행한 조치]

## 영향
- 에러율: X%
- 영향받은 요청 수: N건

## 교훈
### 잘된 점
-

### 개선할 점
-

## 액션 아이템
- [ ] [담당자] 액션 내용 (기한)

시리즈 마무리

이 시리즈에서 다룬 내용:

Part 주제 핵심
1 OpenTelemetry 계측의 기초
2 분산 추적 요청 흐름 시각화
3 구조화된 로깅 검색 가능한 로그
4 메트릭/알림 선제적 모니터링
5 디버깅 실전 문제 해결

Observability는 단순한 모니터링이 아닙니다. 시스템을 이해하고, 문제를 예방하며, 빠르게 해결할 수 있는 능력입니다.

Series Introduction

  1. Part 1: OpenTelemetry Instrumentation
  2. Part 2: Distributed Tracing Across Microservices
  3. Part 3: Structured Logging with Correlation IDs
  4. Part 4: Metrics and Alerting with Prometheus/Grafana
  5. Part 5: Debugging Production Issues with Observability Data (Current)

Debugging Workflow

MELT Approach

Metrics → Events → Logs → Traces

  1. Detect problems with Metrics
  2. Identify timing with Events/Alerts
  3. Get details with Logs
  4. Track request flow with Traces

Real-World Failure Scenarios

Scenario 1: Intermittent Timeouts

Using traces to identify slow database queries

Scenario 2: Memory Leaks

Using heap dumps and GC metrics to find unbounded caches

Scenario 3: Cascading Failures

Using service maps to understand dependencies and implementing circuit breakers

On-Call Playbook

Quick response guides for common issues:

  • Service down
  • Performance degradation
  • Error rate spikes

Postmortem Template

Structured template for documenting and learning from incidents.

Series Summary

Part Topic Key Point
1 OpenTelemetry Instrumentation basics
2 Distributed Tracing Request flow visualization
3 Structured Logging Searchable logs
4 Metrics/Alerting Proactive monitoring
5 Debugging Real-world problem solving

Observability is not just monitoring. It’s the ability to understand your system, prevent problems, and resolve them quickly.

댓글남기기