본문 바로가기
서버 인프라 실무

트레이스 기반 용량 계획: 스레드/커넥션 풀 USE 지표와 자동 스케일링 설계

by yamoojin83 2025. 10. 30.

트레이스 기반 용량 계획: 스레드/커넥션 풀 USE 지표와 자동 스케일링 설계

장애는 CPU 100%에서만 오지 않습니다. 웹 스레드 풀이 꽉 차서 대기열이 길어지거나, DB 커넥션 풀이 포화되어 요청이 기다리다가 타임아웃으로 끝나는 경우가 더 흔합니다. 이 글은 OpenTelemetry(Trace)와 Prometheus(메트릭)를 함께 써서 USE 모델(Utilization·Saturation·Errors)로 스레드 풀/DB 커넥션 풀을 관찰하고, 그 지표로 자동 스케일링을 설계하는 방법을 정리합니다.


1) USE 모델을 스레드/커넥션 풀에 매핑하기

  • Utilization: 사용률. busy_threads / max_threads, active_connections / max_pool_size
  • Saturation: 포화도. request_queue_length, connection_waiters, wait_time_p95
  • Errors: 실패. timeout_count, rejected_execution, SQL timeout

핵심은 대기열입니다. CPU는 여유가 있어도 큐가 길면 사용자 체감은 급격히 나빠집니다.


2) Spring Boot에서 계측 활성화(메트릭 + 트레이스)

2-1) 의존성

// build.gradle
dependencies {
  implementation("io.micrometer:micrometer-registry-prometheus")
  implementation("org.springframework.boot:spring-boot-starter-actuator")
  runtimeOnly("io.opentelemetry.javaagent:opentelemetry-javaagent:2.7.0")
}

2-2) Actuator 노출

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,metrics,prometheus
  metrics:
    tags:
      application: order-api

2-3) 톰캣/히카리 지표

Spring Boot는 기본으로 Tomcat, HikariCP 지표를 Micrometer로 노출합니다(Undertow/Jetty도 유사).

  • Tomcat: tomcat.threads.current, tomcat.threads.busy, tomcat.global.sent/received
  • Hikari: hikaricp.connections.active, hikaricp.connections.idle, hikaricp.connections.pending, hikaricp.connections.timeout.total

2-4) OTel 에이전트(트레이스)

# 실행 환경변수
JAVA_TOOL_OPTIONS="-javaagent:/opt/otel/opentelemetry-javaagent.jar"
OTEL_EXPORTER_OTLP_ENDPOINT="http://127.0.0.1:4317"
OTEL_SERVICE_NAME="order-api"

트레이스에는 HTTP 서버 스팬과 DB/외부 API 스팬의 구간 지연이 기록됩니다. 이후 대기열 지표와 함께 상관분석합니다.


3) Prometheus 기록식(Recording Rules)로 USE 파생지표 만들기

# /etc/prometheus/rules/use.yml
groups:
- name: use.threadpool
  rules:
  - record: svc:thread:utilization
    expr: (sum by (application) (tomcat_threads_busy)) / ignoring() (sum by (application) (tomcat_threads_config_max))
  - record: svc:thread:saturation
    expr: clamp_max(sum by (application) (tomcat_threads_current) - sum by (application) (tomcat_threads_config_max), 0)
  - record: svc:thread:errors_rate
    expr: rate(tomcat_rejected_connections_total[5m])

- name: use.hikari
  rules:
  - record: db:pool:utilization
    expr: (sum by (pool) (hikaricp_connections_active)) / ignoring() (sum by (pool) (hikaricp_connections_max))
  - record: db:pool:saturation
    expr: sum by (pool) (hikaricp_connections_pending)
  - record: db:pool:errors_rate
    expr: rate(hikaricp_connections_timeout_total[5m])

utilization은 0~1 범위로 직관적이고, saturation대기자/대기열을 직접 나타냅니다. 에러는 거부/타임아웃 카운터의 속도로 봅니다.


4) Grafana 패널 설계(현장에서 바로 쓰는 구성)

  • 상단: 스레드 사용률(라인), 대기열 길이(막대), 거부율(라인, 보조축)
  • 중단: DB 풀 사용률(라인), pending(막대), timeout rate(라인)
  • 하단: 트레이스 기반 p95 구간 지연(웹/DB/외부API) — Tempo 링크 연결

라인+막대 조합으로 사용률↑ → 대기열↑ → 지연↑의 인과관계를 한눈에 보이게 만듭니다.


5) 경보 규칙: SLO와 연결

# /etc/prometheus/rules/use-alerts.yml
groups:
- name: use.alerts
  rules:
  - alert: ThreadPoolSaturated
    expr: svc:thread:utilization > 0.85 and svc:thread:saturation > 0
    for: 10m
    labels: {severity: warning}
    annotations:
      summary: "웹 스레드 풀 포화"
      description: "사용률 >85%이며 대기열 발생. 임시 스케일아웃 또는 max-threads 상향 검토"

  - alert: HikariPendingHigh
    expr: db:pool:saturation > 10
    for: 10m
    labels: {severity: warning}
    annotations:
      summary: "DB 커넥션 대기자 과다"
      description: "pending > 10. 커넥션 풀/DB 스케일 확인"

  - alert: HikariTimeouts
    expr: db:pool:errors_rate > 0
    for: 5m
    labels: {severity: critical}
    annotations:
      summary: "DB 커넥션 타임아웃 발생"
      description: "사용자 오류 노출 가능. 즉시 원인 파악 필요(Tempo로 트레이스 이동)"

6) 자동 스케일링 지표 설계(HPA/수동 스케일 기준)

Kubernetes HPA를 예로 들지만 VM/베어메탈에서도 동일 지표를 수동 스케일 트리거로 쓸 수 있습니다.

  • 주 지표: svc:thread:utilization(목표 0.6~0.7), svc:thread:saturation(0 유지)
  • 보조 지표: db:pool:utilization(목표 0.6), db:pool:saturation(0 유지), p95 응답시간

6-1) HPA 예시(Prometheus Adapter)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: order-api
spec:
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Pods
      pods:
        metric:
          name: svc_thread_utilization
        target:
          type: AverageValue
          averageValue: "0.70"
    - type: Pods
      pods:
        metric:
          name: svc_thread_saturation
        target:
          type: AverageValue
          averageValue: "0"

Prometheus Adapter에서 svc:thread:utilizationsvc_thread_utilization 등의 커스텀 메트릭 이름으로 노출해야 합니다.


7) 톰캣/히카리 튜닝 가이드(실전 값)

  • Tomcat: server.tomcat.threads.max를 CPU 코어×2~4로 시작. 포화 시 스케일아웃 우선.
  • HikariCP: maximumPoolSize는 DB가 허용하는 동시 연결 한계를 고려(인덱스/버퍼캐시/락 경합).
  • 타임아웃: connectionTimeout은 2~5초, socketTimeout은 p95+α.
  • 스레드+커넥션 밸런스: 웹 스레드가 DB 동시 연결 수를 지속 초과하면 대기열이 필연적으로 생깁니다.

8) 트레이스와 USE의 상관분석(Tempo ↔ Grafana)

  1. 대시보드에서 svc:thread:saturation이 튄 시점 클릭
  2. Templating/데이터 링크로 Tempo 이동 → 해당 시간대 트레이스 집합 조회
  3. 느린 스팬(외부 API/DB)을 열어 대기 시간(queue/wait)을 확인

대기열이 증가하면서 DB 호출 스팬의 duration이 늘어나면 풀 병목이 맞습니다.


9) 부하 테스트로 임계 확인(짧은 루틴)

# 10분, 초당 200요청까지 증가하며 p95/대기열 추적(예: hey)
hey -z 10m -q 200 -c 100 https://app.example.com/api/orders/1

테스트 동안 사용률→대기열→p95가 어떻게 연동되는지 그래프를 스냅샷으로 남겨 팀 기준선을 만듭니다.


10) 운영 체크리스트(복붙)

  • Micrometer/Actuator Prometheus 노출 확인(/actuator/prometheus)
  • Tomcat/Hikari 지표 수집 OK, 기록식(use.yml) 적용
  • Grafana 패널: 사용률·대기열·타임아웃을 한 화면에
  • Alert: 대기열 > 0 이면 경고, 타임아웃 발생 시 즉시 알림
  • HPA/수동 스케일 기준: 사용률 0.7 목표, 대기열 0
  • Tempo 링크: 느린 시점 → 트레이스 드릴다운

결론: CPU가 아니라 대기열을 보며 스케일합니다. USE 지표로 스레드/커넥션 풀의 병목을 조기에 발견하고, 트레이스로 바로 원인을 확인하세요. 그러면 “사용자 체감”에 맞춘 용량 계획과 자동 스케일링이 가능합니다.