fos-blog/study
01 / 홈02 / 카테고리
01 / 홈02 / 카테고리

카테고리

  • AI 페이지로 이동
    • RAG 페이지로 이동
    • langgraph 페이지로 이동
    • agents.md
    • BMAD Method — AI 에이전트로 애자일 개발하는 방법론
    • Claude Code의 Skill 시스템 - 개발자를 위한 AI 자동화의 새로운 차원
    • Claude Code를 5주 더 쓴 결과 — 스킬·CLAUDE.md를 키워가는 방식
    • Claude Code를 11일 동안 쓴 결과 — 데이터로 본 나의 사용 패턴
    • Claude Code 멀티 에이전트 — Teams
    • AI 에이전트와 디자인의 새 컨벤션 — DESIGN.md, Google Stitch, Claude Design
    • 하네스 엔지니어링 실전 — 4인 에이전트 팀으로 코딩 파이프라인 구축하기
    • 하네스 엔지니어링 — 오래 실행되는 AI 에이전트를 위한 설계
    • 멀티모달 LLM (Multimodal Large Language Model)
    • AI 에이전트와 함께 MVP 만들기 — dooray-cli 사례
  • ai 페이지로 이동
    • agent 페이지로 이동
  • algorithm 페이지로 이동
    • live-coding 페이지로 이동
    • 분산 계산을 위한 알고리즘
  • architecture 페이지로 이동
    • [초안] 시니어 백엔드를 위한 API 설계 실전 스터디 팩 — REST · 멱등성 · 페이지네이션 · 버전 전략
    • [초안] API Versioning과 Backward Compatibility: 시니어 백엔드 관점 정리
    • 캐시 설계 전략 총정리
    • [초안] CJ푸드빌 커머스/F&B 도메인 설계 면접 대비 — 슬롯 경험을 주문·결제·쿠폰·매장 상태 설계로 번역하기
    • [초안] 커머스 Spring 서비스에 Clean/Hexagonal Architecture를 실용적으로 적용하기
    • [초안] 커머스 주문 상태와 데이터 정합성 기본기 — CJ푸드빌 면접 대비
    • [초안] 쿠폰/프로모션 동시성과 정합성 기본기 — 선착순·중복 사용 방지·발급/사용/복구
    • [초안] DDD와 도메인 모델링: 시니어 백엔드 관점의 전술/전략 패턴 실전 가이드
    • [초안] Decorator & Chain of Responsibility — 행동을 체인으로 조립하는 두 가지 방식
    • 디자인 패턴
    • [초안] 분산 아키텍처 완전 정복: Java 백엔드 시니어 인터뷰 대비 실전 가이드
    • [초안] 분산 트랜잭션과 Outbox 패턴 — 왜 2PC를 피하고 어떻게 대신할 것인가
    • 분산 트랜잭션
    • [초안] e-Commerce 주문·결제 도메인 모델링: 상태머신, 멱등성, Outbox/Saga 실전 정리
    • [초안] F&B 쿠폰·프로모션·멤버십·포인트 설계
    • [초안] F&B · e-Commerce 디지털 채널 도메인 한 장 정리 — CJ푸드빌 디지털 채널 백엔드 면접 대비
    • [초안] F&B 주문/매장/픽업 상태머신 설계 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] F&B 이커머스 결제·환불·정산 운영 가이드
    • [초안] Hexagonal / Clean Architecture를 Spring 백엔드에 적용하기
    • [초안] 대규모 커머스 트래픽 처리 패턴 — 1,600만 고객과 올영세일을 버티는 설계
    • [초안] 레거시 JSP/jQuery 화면과 신규 API가 공존하는 백엔드 운영 전략
    • [초안] MSA 서비스 간 통신: Redis Cache-Aside × Kafka 이벤트 하이브리드 설계
    • [초안] Observability 입문: 시니어 백엔드가 장애를 탐지하고 대응하는 방식
    • [초안] Outbox / Inbox Pattern 심화 — 분산 메시징의 정합성 문제를 DB 트랜잭션으로 풀어내기
    • [초안] 결제 도메인 멱등성과 트랜잭션 재시도 기본기
    • [초안] 시니어 백엔드를 위한 Resilience 패턴 실전 가이드 — Timeout, Retry, Circuit Breaker, Bulkhead, Backpressure
    • [초안] REST API 버저닝과 모바일 앱 하위 호환성 — CJ푸드빌 디지털 채널 백엔드 관점
    • [초안] Strategy Pattern — 분기문을 없애는 설계, 시니어 백엔드 인터뷰 핵심 패턴
    • [초안] 시니어 백엔드를 위한 시스템 설계 입문 스터디 팩
    • [초안] 템플릿 메서드 패턴 - 백엔드 처리 골격을 강제하는 가장 오래되고 가장 위험한 패턴
    • [초안] 대규모 트래픽 중 무중단 마이그레이션 — Feature Flag + Shadow Mode 실전
  • database 페이지로 이동
    • mysql 페이지로 이동
    • opensearch 페이지로 이동
    • redis 페이지로 이동
    • 김영한의-실전-데이터베이스-설계 페이지로 이동
    • 커넥션 풀 크기는 얼마나 조정해야 할까?
    • 인덱스 - DB 성능 최적화의 핵심
    • [초안] JPA N+1과 커머스 조회 모델: 주문/메뉴/쿠폰 도메인에서 살아남기
    • [초안] MyBatis 기본기 — XML Mapper, resultMap, 동적 SQL, 운영 패턴 정리
    • [초안] MyBatis와 JPA/Hibernate 트레이드오프 — 레거시 백엔드를 다루는 시니어 관점
    • 역정규화 (Denormalization)
    • 데이터 베이스 정규화
  • devops 페이지로 이동
    • docker 페이지로 이동
    • k8s 페이지로 이동
    • k8s-in-action 페이지로 이동
    • observability 페이지로 이동
    • [초안] 커머스/F&B 채널 장애 첫 5분과 관측성 기본기
    • Envoy Proxy
    • Graceful Shutdown
  • finance 페이지로 이동
    • industry-cycle 페이지로 이동
    • stock-notes 페이지로 이동
  • http 페이지로 이동
    • HTTP Connection Pool
  • interview 페이지로 이동
    • [초안] AI 서비스 팀 경험 기반 시니어 백엔드 면접 질문 뱅크 — Spring Batch RAG / gRPC graceful shutdown / 전략 패턴 / 12일 AI 웹툰 MVP
    • [초안] CJ푸드빌 디지털 채널 Back-end 개발자 직무 분석
    • [초안] CJ푸드빌 디지털 채널 Back-end 면접 답변집 — 슬롯 도메인 경험을 커머스/F&B 설계로 번역하기
    • [초안] F&B / e-Commerce 운영 모니터링과 장애 대응 인터뷰 정리
    • Observability — 면접 답변 프레임
    • [초안] 시니어 Java 백엔드 면접 마스터 플레이북 — 김병태
    • [초안] NSC 슬롯팀 경험 기반 질문 은행 — 도메인 모델링·동시성·성능·AI 협업
  • java 페이지로 이동
    • concurrency 페이지로 이동
    • jdbc 페이지로 이동
    • opentelemetry 페이지로 이동
    • spring 페이지로 이동
    • spring-batch 페이지로 이동
    • 더_자바_코드를_조작하는_다양한_방법 페이지로 이동
    • [초안] Java 동시성 락 정리 — 커머스 메뉴/프로모션 정책 캐시 갱신 관점
    • [초안] JVM 튜닝 실전: 메모리 구조부터 Virtual Threads, GC 튜닝, 프로파일링까지
    • Java의 로깅 환경
    • MDC (Mapped Diagnostic Context)
    • Java StampedLock — 읽기 폭주에도 쓰기가 밀리지 않는 락
    • Virtual Thread와 Project Loom
  • javascript 페이지로 이동
    • typescript 페이지로 이동
    • AbortController
    • Async Iterator와 제너레이터
    • CommonJS와 ECMAScript Modules
    • 제너레이터(Generator)
    • Http Client
    • Node 백엔드 운영 패턴 — Streams 백프레셔, pipe/pipeline, 멱등성 vs 분산 락
    • Node.js
    • npm vs pnpm — 어떤 기준으로 선택했나
    • `setImmediate()`
  • kafka 페이지로 이동
    • [초안] Kafka 기본 개념 — 토픽, 파티션, 오프셋, 복제
    • Kafka를 사용하여 **데이터 정합성**은 어떻게 유지해야 할까?
    • [초안] Kafka 실전 설계: 파티션 전략, 컨슈머 그룹, 전달 보장, 재시도, 순서 보장 트레이드오프
    • 메시지 전송 신뢰성
  • linux 페이지로 이동
    • fsync — 리눅스 파일 동기화 시스템 콜
    • tmux — Terminal Multiplexer
  • network 페이지로 이동
    • L2(스위치)와 L3(라우터)의 역할 차이
    • L4와 VIP(Virtual IP Address)
    • IP Subnet
  • rabbitmq 페이지로 이동
    • [초안] RabbitMQ Basics — 실전 백엔드 관점에서 정리하는 메시지 브로커 기본기
    • [초안] RabbitMQ vs Kafka — 백엔드 메시징 선택 기준과 실전 운영 관점
  • security 페이지로 이동
    • [초안] 시니어 백엔드를 위한 보안 / 인증 스터디 팩 — Spring Security, JWT, OAuth2, OWASP Top 10
  • task 페이지로 이동
    • ai-service-team 페이지로 이동
    • nsc-slot 페이지로 이동
    • sb-dev-team 페이지로 이동
    • the-future-company 페이지로 이동
  • testing 페이지로 이동
    • [초안] 시니어 Java 백엔드를 위한 테스트 전략 완전 정리 — 피라미드부터 TestContainers, 마이크로벤치, Contract까지
  • travel 페이지로 이동
    • 오사카 3박 4일 일정표: 우메다 쇼핑, USJ, 난바·도톤보리, 오사카성
  • web 페이지로 이동
    • [초안] HTTP / Cookie / Session / Token 인증 기본기 — 레거시 JSP와 모바일 API가 공존하는 백엔드 관점
FOS-BLOG · FOOTERall systems normal·v0.1 · 2026.04.27·seoul, kr
Ffos-blog/study

개발 학습 기록을 정리하는 블로그입니다. 공부하면서 기록하고, 기록하면서 다시 배웁니다.

visitors
01site
  • Home↗
  • Posts↗
  • Categories↗
  • About↗
02policy
  • 소개/about
  • 개인정보처리방침/privacy
  • 연락처/contact
03categories
  • AI↗
  • Algorithm↗
  • DB↗
  • DevOps↗
  • Java/Spring↗
  • JS/TS↗
  • React↗
  • Next.js↗
  • System↗
04connect
  • GitHub@jon890↗
  • Source repositoryjon890/fos-study↗
  • RSS feed/rss.xml↗
  • Newsletter매주 1 회 · 한 편의 글→
© 2026 FOS Study. All posts MIT-licensed.
built with·Next.js·Tailwind v4·Geist·Pretendard·oklch
fos-blog/devops/Datadog APM 실전 투입 가이드: J…
devops

Datadog APM 실전 투입 가이드: Java/Spring 서비스 관측성 스택 구축하기

분산 요청이 5\10 개 마이크로서비스를 타고 흐르는 환경에서 장애 탐지 시간(MTTD)과 복구 시간(MTTR)을 초/분 단위로 줄이려면, 로그만 뒤져서는 답이 안 나온다. "어디서 느려졌나"를 5분 안에 집어내려면 metric / log / trace 를 같은 traceid 로 엮을 수 있어야 한다. Datadog 은 Metrics / Logs / APM...

2026.05.09·14 min read·2 views

왜 Datadog인가

분산 요청이 5~10 개 마이크로서비스를 타고 흐르는 환경에서 장애 탐지 시간(MTTD)과 복구 시간(MTTR)을 초/분 단위로 줄이려면, 로그만 뒤져서는 답이 안 나온다. "어디서 느려졌나"를 5분 안에 집어내려면 metric / log / trace 를 같은 trace_id 로 엮을 수 있어야 한다.

Datadog 은 Metrics / Logs / APM / RUM / Profiler / Synthetics 를 한 UI 에서 상관관계로 엮을 수 있는 통합 관측성 플랫폼이다. ELK + Prometheus + Jaeger 를 각각 운영하는 팀 입장에서는 "같은 요청의 로그와 trace 를 한 번의 클릭으로 연결" 이라는 경험이 생산성을 결정한다.

이 문서는 일반 observability 이론 팩이 아닌, Datadog 을 실전에 투입할 때 반드시 알아야 하는 데이터 모델, 태깅, 샘플링, 비용, 알람, 장애 대응 플레이북을 시니어 백엔드 관점에서 다룬다.

운영 중 장애 탐지·추적을 묻는 면접 질문 답변 프레임은 observability-interview-frame.md 에 따로 정리.

1. Datadog 데이터 모델: 4개 제품의 범위와 한계

Datadog을 쓰다 보면 "이 지표를 Metric으로 볼지, Log로 볼지, APM Span으로 볼지" 같은 결정을 자주 한다. 각 제품의 본질적인 차이를 이해해야 비용과 정확도를 동시에 잡을 수 있다.

Metrics는 시계열 수치 데이터다. CPU, 메모리, 요청 수, latency p99 같은 값들. 15초 이하 집계 단위로 저장되고 장기 보관(기본 15개월)이 가능하다. 특징은 태그 조합 카디널리티가 폭증하면 비용이 수직 상승한다는 점이다. user_id, order_id 같은 고유 식별자를 태그로 박으면 재앙이 시작된다.

Logs는 구조화된 이벤트다. JSON 로그로 보내면 각 필드가 검색 가능해진다. 보관 계층이 둘로 나뉜다 — 인덱싱된 로그(빠른 검색, 비싸다)와 리하이드레이션 가능한 아카이브(S3, 저렴하지만 즉시 검색은 안 됨). 로그는 "개별 사건" 추적에 강하다.

APM(Application Performance Monitoring) 은 요청 단위 분산 trace다. 하나의 HTTP 요청이 Spring Controller → Service → JPA → MySQL → Redis → Kafka Producer로 흐르는 전체 경로를 Span 트리로 재구성한다. 사용자가 "주문이 느려요"라고 할 때, 정확히 어느 span에서 800ms를 태웠는지 본다.

RUM(Real User Monitoring) 은 브라우저/모바일 프론트 성능이다. 백엔드 개발자가 직접 다룰 일은 적지만, Frontend trace와 Backend trace를 x-datadog-trace-id 헤더로 이어 붙이면 "첫 페이지 로드부터 DB 쿼리까지"의 end-to-end view가 완성된다.

한계도 명확하다. APM은 기본적으로 샘플링되므로 모든 요청을 다 보존하지 않는다. Logs는 오래되면 검색이 느려지거나 인덱싱에서 빠진다. Metrics는 개별 사건 디버깅엔 쓰지 못한다. 이 한계 때문에 세 제품을 trace_id로 엮는 상관관계 설계가 실전 포인트가 된다.

2. Java/Spring 앱에 Datadog Agent + Tracer 붙이기

실전 설치는 크게 두 컴포넌트다. 호스트(또는 Kubernetes DaemonSet)에서 도는 Datadog Agent와 애플리케이션 JVM에 attach되는 dd-java-agent.jar.

도커/쿠버네티스 환경에서의 최소 구성은 다음과 같다.

dockerfile
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY build/libs/app.jar /app/app.jar
 
# Datadog Java tracer
ADD https://dtdg.co/latest-java-tracer /app/dd-java-agent.jar
 
ENV DD_SERVICE=oliveyoung-order-api
ENV DD_ENV=prod
ENV DD_VERSION=1.42.0
ENV DD_LOGS_INJECTION=true
ENV DD_PROFILING_ENABLED=true
ENV DD_TRACE_SAMPLE_RATE=1.0
 
ENTRYPOINT ["java", "-javaagent:/app/dd-java-agent.jar", "-jar", "/app/app.jar"]

Kubernetes에서는 Datadog Agent를 DaemonSet으로 배포하고, 각 Pod의 localhost:8126(APM), localhost:8125(StatsD), localhost:10518(Logs) 포트로 tracer가 에이전트에 전송한다. Pod annotation이나 환경변수로 Unified Service Tagging을 넣는다.

yaml
spec:
  containers:
    - name: order-api
      env:
        - name: DD_AGENT_HOST
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        - name: DD_SERVICE
          value: oliveyoung-order-api
        - name: DD_ENV
          value: prod
        - name: DD_VERSION
          value: "1.42.0"

Auto-instrumentation 범위가 실전에선 매우 넓다. 설정 한 줄 없이 자동으로 trace가 잡히는 것들:

  • Spring Web / WebFlux / MVC 컨트롤러 진입점
  • JDBC / Hibernate / JPA 쿼리 (rendered SQL + 실행 시간)
  • HTTP 클라이언트 (RestTemplate, WebClient, OkHttp, Apache HttpClient)
  • Redis (Lettuce, Jedis)
  • Kafka (Producer / Consumer, partition, offset, consumer group 태그 자동)
  • gRPC, Elasticsearch, MongoDB
  • AWS SDK (S3, SQS, DynamoDB 호출에 aws.operation span 태그 자동 부착)

이 자동 계측만으로도 80%의 bottleneck을 잡아낸다. 내가 경험한 MDC / OpenTelemetry 기반의 수동 trace 코드 작업이 Datadog에선 설치와 동시에 제공된다는 점이 가장 큰 생산성 차이다.

3. Trace → Span → Service Map으로 Bottleneck 잡기

실전 디버깅 플로우를 예시로 본다. p99 latency alert이 oliveyoung-order-api 서비스에서 발생했다고 가정한다.

1단계 — Service Map 진입. APM → Service Map 에서 해당 서비스 노드를 클릭한다. 연결된 upstream / downstream 서비스가 화살표로 그려지고, 각 엣지에 requests/s, error %, p99 latency가 뜬다. 예를 들어 order-api → inventory-api 엣지의 p99가 평소 120ms에서 1.8s로 튀어 있으면 문제 지점이 좁혀진다.

2단계 — Trace 리스트 필터링. APM → Traces 에서 service:oliveyoung-order-api env:prod @duration:>1s status:error 같은 쿼리를 건다. Datadog의 trace query 문법은 로그와 비슷하다. 상위 10개 slow trace를 뽑아 본다.

3단계 — Flame Graph 분석. 개별 trace를 열면 flame graph가 펼쳐진다. x축은 시간, y축은 호출 스택. Root span이 POST /orders라면 그 밑에 OrderService.placeOrder → InventoryClient.reserve → http.request GET /inventory/reserve → jdbc.query SELECT ... 가 펼쳐진다. 어느 span에서 대부분의 시간을 쓰는지 한눈에 보인다.

4단계 — Span 상세 조사. SQL span을 클릭하면 실제 실행된 쿼리, DB host, connection pool wait time까지 태그로 붙는다. "connection pool이 고갈되어 300ms 대기 후 실행되었다" 같은 판정이 가능하다.

이 흐름이 실전에서 의미 있는 건, Grafana + Jaeger 조합에서는 같은 작업에 3개 탭을 왔다 갔다 해야 하지만 Datadog은 한 화면에서 끝난다는 점이다.

4. Unified Service Tagging: DD_ENV / DD_SERVICE / DD_VERSION

Datadog의 가장 강력한 기능 중 하나가 Unified Service Tagging이다. 이 세 태그를 일관되게 부착하면 Metrics, Logs, APM, Profiler가 자동으로 상관관계를 갖게 된다.

  • env — 환경 (prod, staging, dev, canary)
  • service — 서비스 이름 (oliveyoung-order-api, oliveyoung-catalog-api 등)
  • version — 배포 버전 (Git tag, SHA, 빌드 번호)

배포 단위 비교 시나리오. v1.42.0 배포 직후 p99가 튄다면, APM 대시보드에서 version:1.42.0 vs version:1.41.0 로 필터를 나눠 latency 분포를 중첩해 본다. 특정 엔드포인트에서만 regression이 생겼는지, 특정 downstream 호출에서 차이가 나는지 즉시 보인다.

릴리스 추적. Deploy Tracking 기능을 쓰면 Git provider(GitHub / GitLab)와 연동되어 "이 배포는 어떤 커밋을 포함하나"가 UI에 붙는다. 장애 발생 시간대와 배포 타임라인을 겹쳐 "12:07 배포 → 12:09부터 error rate 급증"이 시각화된다.

태그 전략 실수 패턴. DD_VERSION을 빌드 번호 build-1234로 쓰면 Git 커밋을 역추적하기 어렵다. Git short SHA나 semantic version을 쓰는 게 낫다. DD_SERVICE를 인스턴스마다 다르게 넣는 것도 피해야 한다(서비스 = 배포 단위여야 한다).

5. Custom Span: @Trace와 비즈니스 경계

Auto-instrumentation이 커버하지 못하는 비즈니스 계층 경계는 직접 span을 만들어야 한다.

java
import datadog.trace.api.Trace;
 
@Service
public class OrderService {
 
    @Trace(operationName = "order.place", resourceName = "OrderService.placeOrder")
    public OrderResult placeOrder(OrderCommand cmd) {
        validateCoupon(cmd);
        reserveInventory(cmd);
        createPayment(cmd);
        publishOrderPlacedEvent(cmd);
        return OrderResult.success();
    }
 
    @Trace(operationName = "coupon.validate")
    private void validateCoupon(OrderCommand cmd) { /* ... */ }
}

@Trace는 개별 메서드를 span으로 만든다. 내가 언제 이걸 넣는지 기준:

  1. 비즈니스 단계 경계 — validateCoupon, reserveInventory, createPayment 같은 단계. flame graph에서 단계별 시간 분포가 보인다.
  2. 복잡한 loop / batch — for 루프 안에서 무거운 연산을 하면 한 번의 loop iteration을 span으로 뜬다.
  3. Kafka consumer loop — 자동 계측은 deliverMessage 레벨에서만 span을 뜨는 경우가 많다. 메시지별 비즈니스 처리를 span으로 감싸야 "어떤 메시지 타입이 느린가"가 보인다.
  4. 외부 호출 직전 afterAction — retry loop, circuit breaker fallback 같은 지점.

수동 API도 쓸 수 있다.

java
import io.opentracing.Span;
import io.opentracing.Tracer;
import io.opentracing.util.GlobalTracer;
 
Tracer tracer = GlobalTracer.get();
Span span = tracer.buildSpan("inventory.reserve.retry").start();
try {
    span.setTag("retry.attempt", attempt);
    span.setTag("order.id", orderId);
    doReserve();
} catch (Exception e) {
    span.setTag("error", true);
    span.log(Map.of("event", "error", "error.message", e.getMessage()));
    throw e;
} finally {
    span.finish();
}

주의 — 고유 식별자(order.id, user.id)를 span 태그로 붙일 때는 APM 검색엔 유용하지만, 이걸 그대로 custom metric 태그로 가져가면 cardinality가 폭발한다.

6. Log Correlation: trace_id 자동 주입

Datadog이 로그와 trace를 이어주는 핵심이 MDC(Mapped Diagnostic Context) 자동 주입이다. DD_LOGS_INJECTION=true 환경변수를 켜면 Java tracer가 SLF4J MDC에 dd.trace_id, dd.span_id, dd.service, dd.env, dd.version을 자동으로 박는다.

Logback 설정 예시:

xml
<configuration>
    <appender name="JSON" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="net.logstash.logback.encoder.LogstashEncoder">
            <includeMdcKeyName>dd.trace_id</includeMdcKeyName>
            <includeMdcKeyName>dd.span_id</includeMdcKeyName>
            <includeMdcKeyName>dd.service</includeMdcKeyName>
            <includeMdcKeyName>dd.env</includeMdcKeyName>
            <includeMdcKeyName>dd.version</includeMdcKeyName>
        </encoder>
    </appender>
    <root level="INFO">
        <appender-ref ref="JSON"/>
    </root>
</configuration>

출력되는 JSON 로그:

json
{
  "@timestamp": "2026-04-18T10:23:45.123Z",
  "level": "ERROR",
  "logger_name": "o.c.o.o.OrderService",
  "message": "Inventory reservation failed: SKU=SKU-123",
  "dd.trace_id": "6841823973034827458",
  "dd.span_id": "4821039485720384",
  "dd.service": "oliveyoung-order-api",
  "dd.env": "prod",
  "dd.version": "1.42.0"
}

Datadog UI에서는 이 trace_id가 자동 파싱되어, 로그 한 줄 옆에 "View in APM" 버튼이 생긴다. 반대로 APM trace에서 "Logs" 탭을 누르면 해당 요청의 모든 로그가 뜬다. 이 양방향 점프가 실전 디버깅의 핵심이다.

수동 MDC 관리 경험이 있다면 자연스럽다. 기존에 OpenTelemetry 기반으로 MDC.put("traceId", ...) 해왔다면, Datadog에선 그 작업이 tracer가 자동으로 해주고 필드 네이밍 컨벤션(dd.trace_id)만 따르면 된다.

7. Monitor / SLO / SLI: RED와 USE

SLI(Service Level Indicator) 는 측정 대상 메트릭, SLO(Service Level Objective) 는 그 메트릭의 목표값이다.

Backend API 관측에 흔히 쓰는 두 프레임워크:

RED — User-facing request 중심.

  • Rate — 초당 요청 수
  • Errors — 에러율
  • Duration — 응답 시간 분포 (p50, p95, p99)

USE — Resource 중심.

  • Utilization — CPU/Memory 사용률
  • Saturation — queue 길이, 대기 시간
  • Errors — 에러 카운트

실전 SLO 예: "oliveyoung-order-api의 /orders POST 엔드포인트 p99 latency < 800ms, 에러율 < 0.5%, 30일 윈도우 99.5% 준수."

Datadog SLO Widget으로 이걸 추적한다. Error budget이 소진되면 Slack으로 alert이 간다. Monitor 종류:

  • Metric Monitor — 단순 threshold (avg(last_5m):p99:trace.servlet.request{service:oliveyoung-order-api} > 0.8)
  • Anomaly Monitor — 계절성과 trend를 학습해 "평소 대비 이상"을 탐지. 요일/시간대 패턴이 명확한 커머스 트래픽에 적합하다.
  • Forecast Monitor — "현재 추세로 7일 내 디스크가 꽉 찬다" 같은 예측 알람.
  • Composite Monitor — 여러 monitor를 AND/OR로 엮는다. "error rate 상승 AND deploy 이벤트 발생" 같은 조건부 알람에 쓴다.

언제 어떤 걸 쓰나?

Threshold monitor는 SLO 위반 같은 명확한 기준이 있을 때. Anomaly는 트래픽 패턴이 주기적이고 절대 threshold를 잡기 어려울 때(예: 심야 주문량 vs 피크 시간 주문량). Forecast는 자원 고갈(디스크, 커넥션 풀). Composite는 false positive를 줄여야 할 때 — 단일 메트릭 튐은 무시하고 복합 조건일 때만 호출.

8. Dashboard / Notebook: 장애 초기 10분 판단용 뷰

서비스 온콜을 받았을 때, 10분 안에 "지금 문제가 내 서비스 책임인가, downstream인가"를 판정해야 한다. 그걸 위한 전용 대시보드 구성 예:

Row 1 — Golden Signals(RED)

  • Request rate (rpm) — 30분 window, 평소 대비 drop/spike 확인
  • Error rate (%) — trace.servlet.request.errors / trace.servlet.request.hits
  • Latency p50/p95/p99 — 각 엔드포인트별

Row 2 — Dependency Health

  • Downstream HTTP call latency per service (inventory, payment, coupon)
  • Redis command duration p99
  • MySQL query p99, 특히 connection_wait_time
  • Kafka consumer lag per topic

Row 3 — Infrastructure

  • JVM heap usage, GC pause duration
  • CPU / Memory per pod
  • HTTP 5xx / 4xx 분포
  • Pod restart 이벤트

Row 4 — Business Metrics

  • 주문 성공률, 결제 성공률
  • 이상 값이 비즈니스 레벨에 보이는가

Notebook은 incident review 시점에 쓴다. 라이브 대시보드는 현재 상태를 보고, Notebook은 "어제 14:32 장애 회고"를 위해 그 시점 고정 쿼리를 엮어 문서화한다. 팀 포스트모템에 그대로 붙인다.

9. Continuous Profiler: CPU/Memory/Lock

Datadog Continuous Profiler는 JVM에서 JFR(Java Flight Recorder)을 상시 돌려 stack trace sample을 계속 수집한다. DD_PROFILING_ENABLED=true 하나로 켠다.

제공하는 프로파일 종류:

  • CPU profile — 어떤 메서드가 CPU time을 먹는가. hot path 식별.
  • Allocation profile — 어디서 객체가 많이 만들어지는가. GC pressure 원인 추적.
  • Lock profile — synchronized / ReentrantLock 경합이 어디서 생기나.
  • Exception profile — 예외가 어느 지점에서 던져지고 캐치되나.

실전 활용 예 — "특정 엔드포인트만 p99가 튀는데 DB는 빠르다." APM trace를 봐도 Java 내부 CPU 시간이 긴 상태라면 Profiler로 가서 같은 시간대의 hot method를 본다. com.fasterxml.jackson.databind.ObjectMapper 직렬화가 CPU의 40%를 먹고 있다면, DTO 구조나 JSON 필드 수 문제가 원인이다.

10. 비용 관리: Cardinality, Sampling, Indexing

Datadog 비용이 터지는 세 지점:

Custom Metric Cardinality. Custom metric당 unique tag 조합 수가 과금 단위다. order.placed.count 메트릭에 {env, service, version} 태그만 붙이면 태그 조합 수십 개지만, {env, service, version, user_id, sku_id} 를 붙이면 수백만 개로 폭발한다. 규칙: 고유 식별자는 로그/span attribute에만, Metric tag에는 절대 금지.

APM Span Sampling. 프로덕션에서 모든 trace를 보내면 APM 비용이 살인적이다. 두 종류의 샘플링:

  • Head-based sampling — 요청 시작 시점에 "이 trace를 기록할지"를 결정. DD_TRACE_SAMPLE_RATE=0.1로 설정하면 10%만 수집. 단점은 희귀한 에러 trace를 놓칠 수 있다.
  • Tail-based sampling — trace 종료 후 "이 trace가 에러가 있었나, 느렸나" 보고 결정. 에러 및 slow trace는 100% 보존. Datadog Agent가 Ingestion Control과 연동해 지원한다. 비용과 신호 대비 효율이 훨씬 낫다.

정책 예: "정상 trace 5% + 에러 trace 100% + p99 > 1s trace 100%."

Log Indexing. Datadog Logs는 수집(ingestion)과 인덱싱(indexing)이 별도로 과금된다. 수집은 상대적으로 싸고, 인덱싱(7~30일 검색 보관)이 비싸다. Log Pipeline에서 exclusion filter를 걸어 "DEBUG 로그는 인덱싱하지 않는다", "health check 요청 로그는 제외" 같은 규칙을 만든다. 필요하면 장기 아카이브를 S3로 보내 reshydration으로 꺼내 쓴다.

11. 장애 대응 플레이북

실제 호출을 받았을 때의 표준 플로우:

  1. APM Alert 확인 — Slack에 뜬 alert link를 연다. Monitor의 context로 영향 받는 서비스와 태그를 파악한다.
  2. Service Map 진입 — 문제 서비스의 upstream/downstream edge 중 red 색상이 어디인지. 내 책임인지 downstream 책임인지 30초 안에 판정.
  3. Error Trace 샘플 분석 — Traces 탭에서 status:error 로 필터. 상위 trace 3개를 열어 flame graph에서 실패 지점 확인. 에러 메시지, stack trace 태그 확인.
  4. Deploy Diff 확인 — Deployments 타임라인을 본다. 장애 시작 시각 직전에 내 서비스나 downstream 서비스 배포가 있었나. 있다면 Git diff 링크로 이동.
  5. Feature Flag Toggle — LaunchDarkly/Unleash 등이 통합되어 있다면 해당 플래그 변경 이력을 본다. 플래그 전환으로 regression이 생긴 거면 즉시 롤백.
  6. Mitigation 선택 — (a) 배포 롤백 (b) feature flag off (c) 트래픽 throttle (d) circuit breaker open 중 가장 빠른 수단.
  7. Postmortem Notebook — Datadog Notebook에 해당 시간대의 모든 쿼리와 트레이스 스냅샷을 고정 저장해 회고 자료로 쓴다.

12. Bad vs Improved 예시

Bad 예시 1 — Unified Tagging 누락

yaml
env:
  - name: DD_SERVICE
    value: api
  - name: DD_VERSION
    value: "latest"

api라는 이름은 어떤 서비스인지 알 수 없고, latest는 롤백 시 버전 비교를 불가능하게 한다. 서비스맵에서 다른 "api" 서비스와 섞여 디버깅이 지옥이 된다.

Improved

yaml
env:
  - name: DD_SERVICE
    value: oliveyoung-order-api
  - name: DD_VERSION
    value: "1.42.0-a1b2c3d"
  - name: DD_ENV
    value: prod

Bad 예시 2 — Custom Metric에 고유 식별자

java
statsd.increment("order.placed",
    "user_id:" + userId,
    "order_id:" + orderId);

사용자 10만 명 × 주문 수백만 → metric 카디널리티 폭발, 월말 청구서 3~10배 증가.

Improved — span attribute로 이동, metric은 low cardinality로

java
statsd.increment("order.placed",
    "env:prod",
    "payment_method:" + paymentMethod,
    "tier:" + userTier);
 
Span span = GlobalTracer.get().activeSpan();
if (span != null) {
    span.setTag("user.id", userId);
    span.setTag("order.id", orderId);
}

Bad 예시 3 — 로깅과 trace 단절

java
log.error("Failed to process order: " + orderId);

plain string으로 로그 쓰면 JSON 필드가 아니라 message 전체로 검색해야 한다. trace correlation이 안 된다.

Improved

java
log.error("Failed to process order", 
    kv("order.id", orderId),
    kv("payment.method", paymentMethod),
    kv("error.type", e.getClass().getSimpleName()),
    e);

MDC trace_id는 tracer가 자동으로 주입하고, key-value는 로그 필드가 된다. Datadog에서 @order.id:12345 로 직접 검색 가능.

13. 로컬 실습 환경

Datadog은 30일 trial이 있다. 학습용 환경을 간단히 구축하자.

Docker Compose로 Datadog Agent + Spring 앱 + MySQL + Redis + Kafka:

yaml
version: '3.8'
services:
  datadog-agent:
    image: gcr.io/datadoghq/agent:7
    environment:
      - DD_API_KEY=${DD_API_KEY}
      - DD_SITE=datadoghq.com
      - DD_APM_ENABLED=true
      - DD_APM_NON_LOCAL_TRAFFIC=true
      - DD_LOGS_ENABLED=true
      - DD_LOGS_CONFIG_CONTAINER_COLLECT_ALL=true
      - DD_DOGSTATSD_NON_LOCAL_TRAFFIC=true
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /proc/:/host/proc/:ro
      - /sys/fs/cgroup/:/host/sys/fs/cgroup:ro
    ports:
      - "8126:8126"
      - "8125:8125/udp"
 
  order-api:
    build: ./order-api
    environment:
      - DD_AGENT_HOST=datadog-agent
      - DD_SERVICE=order-api
      - DD_ENV=local
      - DD_VERSION=0.1.0
      - DD_LOGS_INJECTION=true
      - DD_TRACE_SAMPLE_RATE=1.0
      - DD_PROFILING_ENABLED=true
    depends_on:
      - datadog-agent
      - mysql
      - redis
 
  mysql:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: orders
 
  redis:
    image: redis:7-alpine

실습 과제:

  1. 위 환경으로 Spring Boot 앱을 띄운다.
  2. POST /orders 엔드포인트 — validate → inventory check(외부 API call simulation) → save to MySQL → publish to Redis Stream.
  3. curl로 요청을 100개 쏜다.
  4. Datadog UI → APM → Service Map에서 order-api 노드 확인.
  5. Traces 리스트에서 individual trace 하나 열고 flame graph 관찰.
  6. 의도적으로 Thread.sleep(2000)을 중간에 박고 trace에서 해당 구간이 어떻게 표시되는지 확인.
  7. Profiler 탭으로 가서 해당 메서드가 CPU profile에 잡히는지 확인.
  8. Monitor를 하나 만든다 — avg(last_5m):avg:trace.servlet.request.duration{service:order-api} > 1.
  9. 부하를 다시 걸어 alert이 Slack에 뜨는지 확인.

14. 체크리스트

설치/설정 단계

  • Datadog Agent를 DaemonSet(K8s) 또는 호스트 데몬(VM)으로 배포했는가
  • dd-java-agent.jar를 모든 JVM에 -javaagent로 붙였는가
  • DD_ENV, DD_SERVICE, DD_VERSION을 모든 환경에서 일관되게 지정했는가
  • DD_LOGS_INJECTION=true로 MDC trace_id 자동 주입을 켰는가
  • Logback/Log4j에 dd.trace_id, dd.span_id, dd.service, dd.env, dd.version MDC 출력을 추가했는가
  • DD_PROFILING_ENABLED=true로 Continuous Profiler를 켰는가

태깅 / 메트릭 단계

  • Custom metric 태그에 user_id / order_id 같은 고유 식별자가 들어가지 않는가
  • 비즈니스 단계 경계에 @Trace 또는 수동 span을 추가했는가
  • Kafka consumer 처리 루프에 메시지별 span을 감쌌는가

Monitor / SLO 단계

  • RED 메트릭(Rate/Errors/Duration)으로 서비스별 SLO를 정의했는가
  • 트래픽 계절성이 있는 지표는 anomaly monitor를 고려했는가
  • 알람 조건이 복합적인 경우 composite monitor로 false positive를 줄였는가
  • Error budget 소진 알람이 설정되어 있는가

대시보드 단계

  • 서비스별 "장애 10분 판정용" 대시보드가 있는가
  • Golden Signals, Dependency Health, Infrastructure, Business Metrics 4개 레이어가 한 화면에 있는가

비용 단계

  • Tail-based sampling으로 에러/slow trace는 100% 보존, 정상은 저율 샘플링하는가
  • Log Pipeline에 DEBUG / health check exclusion이 적용되어 있는가
  • Custom metric cardinality를 주기적으로 모니터링하는가

장애 대응 단계

  • APM alert → Service Map → Error Trace → Deploy Diff → Mitigation 플레이북이 문서화되어 있는가
  • Postmortem Notebook 템플릿이 있는가
  • Feature flag / deploy 이력이 Datadog에 연동되어 있는가
on this page
  • 01왜 Datadog인가
  • 021. Datadog 데이터 모델: 4개 제품의 범위와 한계
  • 032. Java/Spring 앱에 Datadog Agent + Tracer 붙이기
  • 043. Trace → Span → Service Map으로 Bottleneck 잡기
  • 054. Unified Service Tagging: DD_ENV / DD_SERVICE / DD_VERSION
  • 065. Custom Span: @Trace와 비즈니스 경계
  • 076. Log Correlation: trace_id 자동 주입
  • 087. Monitor / SLO / SLI: RED와 USE
  • 098. Dashboard / Notebook: 장애 초기 10분 판단용 뷰
  • 109. Continuous Profiler: CPU/Memory/Lock
  • 1110. 비용 관리: Cardinality, Sampling, Indexing
  • 1211. 장애 대응 플레이북
  • 1312. Bad vs Improved 예시
  • 1413. 로컬 실습 환경
  • 1514. 체크리스트

댓글 (0)