Skip to content

iq-dev-lab/observability-deep-dive

Repository files navigation

🔭 Observability Deep Dive

"Grafana 대시보드를 보는 것과, Java Agent가 바이트코드를 조작해 어떻게 메트릭을 수집하는지 아는 것은 다르다"


"@Observed 붙이면 트레이싱 되겠지 — 와 — Span이 내부에서 어떻게 생성되고 HTTP 헤더를 타고 마이크로서비스를 건너가는지, Prometheus가 수십억 시계열 데이터를 어떻게 1.37비트/샘플로 압축하는지 아는 것의 차이를 만드는 레포"

시스템이 복잡해질수록 "지금 무슨 일이 일어나고 있는가" 를 아는 것이 가장 중요한 엔지니어링 스킬이 된다
Observability의 세 기둥인 메트릭 / 로그 / 트레이스가 내부에서 어떻게 동작하는지, 왜 이렇게 설계됐는가라는 질문으로 끝까지 파헤칩니다


GitHub OTel Prometheus Grafana Docs License


🎯 이 레포에 대하여

Observability에 관한 자료는 넘쳐납니다. 하지만 대부분은 "어떤 도구를 켜는가" 에서 멈춥니다.

일반 자료 이 레포
"-javaagent 붙이면 자동으로 계측됩니다" premain()ClassFileTransformer → ByteBuddy ASM 변환 → 원본 execute() 에 Span 생성 코드가 삽입되는 전 과정
"@Observed 붙이면 트레이싱 됩니다" ObservationRegistryObservationHandlerTracer.spanBuilder() → Context 전파 — AOP가 실제로 무엇을 하는가
"traceparent 헤더로 전파됩니다" W3C TraceContext 포맷(00-traceId-spanId-flags), Propagator가 헤더를 추출/주입하는 방식, CompletableFuture에서 Context가 유실되는 원인과 Executor 래핑 해결법
"Prometheus로 메트릭을 수집하세요" Pull vs Push 설계 결정, TSDB Head Block → Compaction → Block 구조, Gorilla 알고리즘(Delta-of-Delta + XOR)으로 64비트 타임스탬프를 1~2비트로 압축하는 원리
"PromQL로 쿼리하세요" Instant Vector vs Range Vector 내부 연산, rate()가 첫/마지막 샘플로 기울기를 계산하는 방식, Recording Rule이 고비용 쿼리를 사전 계산하는 이유
"Grafana에서 대시보드 만드세요" Exemplar로 p99 스파이크 시점 메트릭 → Trace → Loki 로그 3단계 드릴다운, RED/USE 방법론으로 증상에서 원인으로
도구 사용법 나열 실행 가능한 ByteBuddy Agent 구현 + curl -H "traceparent: ..." 직접 실험 + Docker Compose(Prometheus + Grafana + Loki + Tempo + OTel Collector) 환경

🚀 빠른 시작

각 챕터의 첫 문서부터 바로 학습을 시작하세요!

Foundations Agent Tracing Prometheus Logging Grafana Spring


📚 전체 학습 지도

💡 각 섹션을 클릭하면 상세 문서 목록이 펼쳐집니다


🔹 Chapter 1: Observability 기초 — 세 기둥과 설계 원칙

핵심 질문: Observability와 Monitoring은 무엇이 다른가? 메트릭/로그/트레이스는 각각 어떤 질문에 답하고, 세 기둥을 연결했을 때 어떤 진단이 가능해지는가?

Known Unknowns와 Unknown Unknowns부터 OpenTelemetry 표준화까지 (4개 문서)
문서 다루는 내용
01. Observability vs Monitoring — 알려지지 않은 미지수 "알려진 미지수(Known Unknowns)" vs "알려지지 않은 미지수(Unknown Unknowns)", 임계값 기반 모니터링이 복잡한 분산 시스템에서 부족한 이유, Observability가 카디널리티 높은 데이터로 임의의 질문에 답할 수 있어야 하는 이유
02. 메트릭/로그/트레이스 — 세 기둥이 답하는 질문 메트릭(무슨 일이 일어나고 있는가), 로그(구체적으로 어디서 무슨 일이 있었는가), 트레이스(요청이 어떤 경로로 흘렀는가) 각각의 역할, Exemplar로 메트릭 → 트레이스 → 로그를 연결하는 3단계 드릴다운
03. OpenTelemetry 표준화 — 벤더 종속 탈출 OTel 이전 Zipkin/Jaeger/Prometheus 각각의 SDK를 쓰던 문제, Signal/Tracer/Meter/Logger 추상화 계층, SDK → Exporter → Collector 파이프라인, OTLP(OpenTelemetry Protocol) 표준 포맷
04. 계측(Instrumentation) 방법론 — 자동 vs 수동 vs 라이브러리 Java Agent 자동 계측(코드 변경 없음), @Observed / @NewSpan 수동 계측(명시적 경계), Micrometer 라이브러리 계측(지표 직접 정의), 각 방법의 오버헤드·가시성·제어 수준 트레이드오프

🔹 Chapter 2: Java Agent와 바이트코드 조작 — 코드 없이 계측하기

핵심 질문: -javaagent 옵션 하나로 어떻게 내 코드에 몰래 타이머가 심어지는가? ASM, Javassist, ByteBuddy는 무엇이 다르고, OpenTelemetry Java Agent는 내부에서 어떤 클래스를 어떻게 변환하는가?

premain()부터 계측 오버헤드 측정까지 (6개 문서)
문서 다루는 내용
01. Java Agent 메커니즘 — premain()과 Instrumentation API -javaagent 옵션으로 JVM이 premain()main() 이전에 호출하는 방식, Instrumentation.addTransformer()ClassFileTransformer를 등록하는 구조, agentmain()으로 실행 중인 JVM에 동적 Attach하는 방법
02. 바이트코드 조작 도구 비교 — ASM / Javassist / ByteBuddy ASM(방문자 패턴 기반 저수준 bytecode 조작), Javassist(소스 코드 문자열로 메서드 수정), ByteBuddy(선언적 DSL + @Advice 어노테이션) 각각의 추상화 수준, 성능, 사용 사례 비교
03. ByteBuddy로 메서드 타이머 구현 — 손으로 만드는 Agent AgentBuilder.Default()로 계측 대상 선정(ElementMatcher), @Advice.OnMethodEnter / @Advice.OnMethodExit으로 진입/종료 시점 코드 삽입, @Advice.Origin으로 메서드 시그니처 접근, 실제 동작하는 TimingAgent 구현
04. OpenTelemetry Java Agent 내부 — InstrumentationModule 구조 OTel Agent가 RestTemplate / JDBC / Spring MVC / Kafka 등 라이브러리를 자동 계측하는 방식, InstrumentationModule + TypeInstrumentation 구조, 클래스 로딩 시점에 변환이 일어나는 과정, --otel.javaagent.debug 로그로 변환 확인
05. Micrometer의 계측 모델 — MeterRegistry 내부 MeterRegistryCounter / Timer / Gauge / DistributionSummary를 등록하고 Prometheus Exporter로 노출하는 방식, @Timed AOP가 TimedAspect를 통해 메서드 실행 시간을 측정하는 과정, percentile histogram 내부
06. 계측 오버헤드 측정 — 샘플링으로 오버헤드 제어 Java Agent 추가로 인한 실제 CPU(1~3%) / 메모리(수십 MB) 오버헤드 측정 방법, Head Sampling(앞단 결정) vs Tail Sampling(결과 기반 결정) 차이, OTEL_TRACES_SAMPLER=parentbased_traceidratio 설정으로 오버헤드 조절

🔹 Chapter 3: 분산 추적 — TraceContext가 서비스를 건너가는 방법

핵심 질문: 하나의 HTTP 요청이 10개의 마이크로서비스를 거칠 때 Trace는 어떻게 하나로 이어지는가? CompletableFuture와 Virtual Thread에서 TraceContext는 왜 유실되고, 어떻게 막는가?

Trace/Span 모델부터 Virtual Thread 추적까지 (6개 문서)
문서 다루는 내용
01. Trace와 Span 모델 — 인과 관계 표현 하나의 Trace를 구성하는 Span 계층 구조(traceId, spanId, parentSpanId), Span의 시작/종료 시간으로 타임라인을 구성하는 방식, Tempo에서 waterfall 뷰로 시각화되는 원리
02. W3C TraceContext 표준 — traceparent 헤더 해부 traceparent: 00-{traceId}-{parentSpanId}-{flags} 포맷 완전 분해, tracestate 헤더로 벤더별 추가 정보 전달하는 방식, B3 단일 헤더와의 비교, curl -H "traceparent: ..." 직접 전파 실험
03. HTTP 경계에서의 전파 — Propagator 동작 방식 TextMapPropagator.inject() / extract()가 HTTP 헤더에서 TraceContext를 읽고 쓰는 방식, RestTemplate / WebClient / Feign에 자동 계측이 적용되는 원리, 서비스 간 경계에서 새 Span이 생성되는 과정
04. 비동기 컨텍스트 전파 — ThreadLocal의 함정 CompletableFuture.supplyAsync()에서 다른 스레드로 전환 시 ThreadLocal 기반 Context가 유실되는 원인, ContextSnapshot + ContextExecutorService로 Executor를 래핑해 Context를 전파하는 방법, @Async에서의 동일 문제 해결
05. Virtual Thread에서의 추적 — ScopedValue로의 전환 Virtual Thread 스케줄링 시 ThreadLocal 기반 Context 전파가 문제인 이유(캐리어 스레드 전환 시 Context 손실), Java 21 ScopedValue가 불변 값으로 Context를 전파하는 방식, Loom과 Micrometer Tracing의 통합 현황
06. Span 속성과 이벤트 — 의미 있는 Trace 만들기 span.setAttribute()로 DB 쿼리 / HTTP URL / 사용자 ID를 Span에 추가하는 방법, span.addEvent()로 특정 시점 이벤트를 기록하는 방식, SpanStatus.ERROR로 오류를 표현하고 Trace 필터링에 활용하는 방법

🔹 Chapter 4: 메트릭 — Prometheus TSDB 완전 분해

핵심 질문: Prometheus는 어떻게 Pull 방식으로 메트릭을 수집하고, 수십억 개의 시계열 데이터를 디스크에 어떻게 압축 저장하며, PromQL은 내부에서 어떤 연산을 수행하는가?

Pull/Push 모델부터 서비스 디스커버리까지 (6개 문서)
문서 다루는 내용
01. Pull vs Push 모델 — Prometheus의 설계 결정 Prometheus Pull 방식(Prometheus가 /metrics를 주기적으로 스크레이프)의 장단점, Push 방식 대비 서비스 디스커버리 용이성과 스크레이프 실패 감지, Pushgateway의 적합 사용 사례(배치 잡), OpenMetrics 표준 포맷
02. Prometheus 데이터 모델 — 시계열과 카디널리티 TimeSeries = {MetricName + LabelSet} → [(timestamp, value)] 구조, 레이블 조합 폭발로 인한 High Cardinality 문제(user_id, request_id 레이블 금지 이유), http_requests_total{method="GET", status="200"} 예시로 데이터 모델 완전 이해
03. TSDB 저장 구조 — Head Block에서 디스크 Compaction까지 최근 2시간 데이터를 메모리 Head Block에 저장하는 방식, Compaction으로 Head Block → 디스크 Block(2시간 단위)으로 플러시하는 과정, WAL(Write-Ahead Log)로 crash recovery 보장, --storage.tsdb.retention.time 설정
04. 압축 알고리즘 — Gorilla 알고리즘으로 46배 압축 Timestamp Delta-of-Delta 인코딩(균일한 간격이면 0으로 수렴 → VarInt 1~2비트 사용), Value XOR 인코딩(연속된 카운터 증분의 XOR 결과가 거의 0 → 평균 1.37비트/샘플), 실제 디스크 사용량 비교 실험
05. PromQL 내부 — rate(), histogram_quantile() 계산 원리 Instant Vector(특정 시점 단일 값)와 Range Vector([5m] 범위 샘플 목록)의 차이, rate()가 범위 내 첫/마지막 샘플로 초당 증분을 계산하는 방식, histogram_quantile(0.99, ...)이 버킷 경계를 선형 보간하는 방법, Recording Rule로 고비용 쿼리 사전 계산
06. 스크레이프와 서비스 디스커버리 — Kubernetes SD Kubernetes SD로 Pod / Service / Endpoints를 자동 발견하는 방식, relabeling으로 파드 어노테이션을 메트릭 레이블로 변환하는 방법, 스크레이프 타임아웃과 scrape_interval 설정, Prometheus Operator의 ServiceMonitor CRD

🔹 Chapter 5: 로그 — 구조화 로그와 중앙 수집

핵심 질문: 구조화 로그는 왜 텍스트 로그보다 검색에 유리한가? MDC로 어떻게 로그와 트레이스를 연결하고, Loki는 인덱스 없이 어떻게 로그를 저장하는가?

구조화 로그부터 Loki 내부까지 (5개 문서)
문서 다루는 내용
01. 구조화 로그 — JSON 로그가 필요한 이유 텍스트 로그(2024-01-01 ERROR ...)의 정규식 파싱 비용, JSON 로그의 필드별 인덱싱과 정확한 필터링, Logback JsonEncoder / Log4j2 JsonLayout 설정, 운영 환경에서 로그 레벨 선택 기준
02. MDC와 Trace ID 연결 — 로그-트레이스 상관관계 MDC.put("traceId", span.getTraceId())로 모든 로그에 Trace ID를 자동 삽입하는 방법, Micrometer Tracing이 MDC를 자동으로 채우는 방식, Loki에서 traceId로 필터링 후 Tempo로 직접 점프하는 연동 패턴
03. 로그 수집 아키텍처 — Filebeat와 Fluentd 비교 Filebeat가 파일을 tail -f 방식으로 읽고 오프셋을 추적하는 방식, Fluentd의 플러그인 에코시스템과 버퍼 / 백프레셔 처리, Kubernetes에서 DaemonSet으로 노드별 로그 수집기 배포하는 패턴, 중복 전송 방지
04. Loki — 인덱스 없는 로그 저장 Loki가 레이블({app="order-service"})만 인덱싱하고 로그 내용은 압축 청크로만 저장하는 설계, LogQL의 `
05. 로그 레벨과 동적 변경 — 운영 중 DEBUG 켜기 Spring Boot Actuator /actuator/loggers/{name} API로 런타임에 로그 레벨 변경하는 방법, 운영 장애 시 특정 패키지만 DEBUG로 전환하는 패턴, 변경 후 자동 복원 전략, 로그 레벨에 따른 성능 영향 측정

🔹 Chapter 6: Grafana와 시각화

핵심 질문: Exemplar는 어떻게 메트릭 스파이크 시점에서 Trace로 바로 이동하게 하는가? RED / USE 방법론으로 어떤 순서로 장애를 진단하는가?

DataSource 아키텍처부터 RED/USE 방법론까지 (4개 문서)
문서 다루는 내용
01. Grafana 데이터소스 아키텍처 — 플러그인 구조 Grafana가 Prometheus / Loki / Tempo를 각각 DataSource 플러그인으로 쿼리하는 방식, 패널(Panel) → 쿼리(Query) → DataSource → 백엔드 API 호출 흐름, Variables를 활용한 동적 대시보드
02. Exemplar — 메트릭에서 Trace로 점프 Prometheus Exemplar가 특정 샘플 시점의 traceId를 메트릭에 첨부하는 방식, Grafana에서 p99 스파이크 시점 점(dot)을 클릭해 Tempo Trace로 이동하는 흐름, Spring Boot에서 Exemplar를 활성화하는 설정
03. 알림(Alerting) 설계 — 증상 기반 알림 Alertmanager 라우팅 규칙(routereceiver)과 그룹핑으로 알림 폭풍을 방지하는 방법, silenceinhibit으로 노이즈 줄이기, CPU 사용률(원인) 대신 응답 시간(증상) 기반 알림이 더 유용한 이유, SLO 위반 알림 설계
04. RED 방법론과 USE 방법론 — 진단 프레임워크 RED(Rate / Error / Duration)로 서비스 이상을 감지하는 PromQL 3개, USE(Utilization / Saturation / Error)로 인프라 리소스를 진단하는 방법, 두 방법론을 조합한 "증상 → 원인" 진단 순서, Google SRE의 Four Golden Signals와 비교

🔹 Chapter 7: Spring과 Observability 통합

핵심 질문: Spring Boot Actuator / Micrometer / Micrometer Tracing이 내부에서 어떻게 연결되는가? WebFlux 리액티브 파이프라인에서 TraceContext는 어떻게 유지되고, Kubernetes에서 어떻게 배포하는가?

Spring Boot Actuator부터 실전 장애 진단까지 (6개 문서)
문서 다루는 내용
01. Spring Boot Actuator — MeterRegistry 자동 구성 /actuator/metrics 엔드포인트가 MeterRegistry에 등록된 모든 지표를 노출하는 방식, PrometheusMeterRegistry/actuator/prometheus에서 텍스트 포맷으로 직렬화하는 과정, management.metrics.* 자동 구성 클래스
02. Micrometer Tracing — Sleuth에서의 전환 Spring Cloud Sleuth가 Micrometer Tracing으로 통합된 배경, Tracer / Span / Observation API 계층 구조, OTel SDK를 Bridge로 연결하는 방식(micrometer-tracing-bridge-otel), Zipkin Exporter vs OTLP Exporter 선택 기준
03. @Observed와 ObservationRegistry — AOP 계측 @Observed 어노테이션이 ObservationAspectObservationRegistryObservationHandler 체인으로 처리되는 흐름, 커스텀 ObservationHandler로 Span 속성 추가하는 방법, Observation.start() / stop() 프로그래밍 방식 계측
04. Spring WebFlux와 트레이싱 — 리액티브 Context 전파 Project Reactor의 Context(ThreadLocal이 아닌 구독별 Context) 메커니즘, ReactorNettyHttpClientRequest에서 TraceContext를 Reactor Context로 전달하는 방식, 구독 경계를 넘어 TraceContext를 유지하는 contextCapture()
05. Kubernetes 환경 배포 — Prometheus Operator와 사이드카 Prometheus Operator의 ServiceMonitor CRD로 파드 자동 발견 및 스크레이프 설정, OpenTelemetry Operator의 auto-instrumentation CRD로 -javaagent 없이 Java Agent 주입, OTel Collector 사이드카 패턴
06. 실전 진단 시나리오 — 메트릭→트레이스→로그 연계 "주문 API p99 응답 시간이 3초로 급증" 시나리오에서 Grafana RED 대시보드 → Exemplar로 느린 Trace 선택 → DB Span에서 느린 쿼리 발견 → Loki에서 해당 traceId 로그 조회 → "Connection timeout" 에러 확인 3단계 완전 연계

🐳 실험 환경 빠른 시작

# docker/docker-compose.yml
services:
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - promdata:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--storage.tsdb.retention.time=15d'
      - '--enable-feature=exemplar-storage'
    ports:
      - "9090:9090"

  grafana:
    image: grafana/grafana:latest
    depends_on: [prometheus, loki, tempo]
    environment:
      - GF_AUTH_ANONYMOUS_ENABLED=true
      - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
    volumes:
      - ./grafana/datasources:/etc/grafana/provisioning/datasources
    ports:
      - "3000:3000"

  loki:
    image: grafana/loki:latest
    volumes:
      - ./loki-config.yaml:/etc/loki/local-config.yaml
    ports:
      - "3100:3100"

  promtail:
    image: grafana/promtail:latest
    volumes:
      - ./promtail-config.yaml:/etc/promtail/config.yaml
      - /var/log:/var/log:ro
    command: -config.file=/etc/promtail/config.yaml
    depends_on: [loki]

  tempo:
    image: grafana/tempo:latest
    command: ["-config.file=/etc/tempo.yaml"]
    volumes:
      - ./tempo.yaml:/etc/tempo.yaml
      - tempodata:/tmp/tempo
    ports:
      - "3200:3200"   # Tempo HTTP
      - "4317:4317"   # OTLP gRPC
      - "4318:4318"   # OTLP HTTP

  otel-collector:
    image: otel/opentelemetry-collector-contrib:latest
    command: ["--config=/etc/otelcol-contrib/otel-collector.yaml"]
    volumes:
      - ./otel-collector.yaml:/etc/otelcol-contrib/otel-collector.yaml
    ports:
      - "4319:4317"   # OTLP gRPC (앱 → Collector)
      - "4320:4318"   # OTLP HTTP
      - "8888:8888"   # Collector 내부 메트릭

volumes:
  promdata:
  tempodata:
# 실험 환경 시작
cd docker && docker compose up -d

# Grafana:    http://localhost:3000
# Prometheus: http://localhost:9090
# Loki:       http://localhost:3100
# Tempo:      http://localhost:3200

🔬 핵심 실험 명령어

# TraceContext 헤더 직접 전달 — 전파 동작 확인
curl -H "traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01" \
     http://localhost:8080/api/orders

# Prometheus 메트릭 원본 텍스트 포맷 확인
curl http://localhost:8080/actuator/prometheus | grep http_server_requests

# Trace 직접 조회 (Tempo HTTP API)
curl "http://localhost:3200/api/traces/{trace-id}"

# Java Agent 디버그 로그 — 어떤 클래스가 변환되는지 확인
java -javaagent:opentelemetry-javaagent.jar \
     -Dotel.javaagent.debug=true \
     -jar app.jar

# 클래스 로딩 + 변환 추적
java -XX:+TraceClassLoading \
     -javaagent:myagent.jar \
     -jar app.jar 2>&1 | grep "Loaded com.example"

# PromQL — RED 방법론 3개 핵심 쿼리
# Rate:
rate(http_server_requests_seconds_count[5m])
# Error Rate:
rate(http_server_requests_seconds_count{status=~"5.."}[5m])
  / rate(http_server_requests_seconds_count[5m])
# p99 Duration:
histogram_quantile(0.99, rate(http_server_requests_seconds_bucket[5m]))

🧩 핵심 동작 원리 요약

Java Agent 바이트코드 조작 흐름:

-javaagent:opentelemetry-javaagent.jar 실행
  │
  ▼ premain() 호출 (main() 이전)
    Instrumentation API 획득 → ClassFileTransformer 등록
  │
  ▼ RestTemplate 클래스 로딩 시 ByteBuddy 변환:
    변환 전:  return doExecute(url, method, ...);
    변환 후:  Span span = tracer.spanBuilder(url).startSpan();
              try (Scope scope = span.makeCurrent()) {
                inject(request, "traceparent", context);  // 헤더 삽입
                return doExecute(url, method, ...);
              } finally { span.end(); }

분산 추적 전파:

Service A                          Service B
  Span(traceId=abc, spanId=111)
  │
  ▼ RestTemplate.execute()
    Headers: traceparent: 00-abc-111-01
  ─────────────────────────────────→
                                     Span(traceId=abc, spanId=222, parent=111)
                                     → 같은 traceId, 이어진 Span

Tempo: traceId=abc → [Span(111,/order), Span(222,/product)] → waterfall 시각화

Prometheus TSDB Gorilla 압축:

Timestamp Delta-of-Delta:
  원본: [0, 15, 30, 45, 60]  (15초 간격 스크레이프)
  Delta: [15, 15, 15, 15]
  Delta-of-Delta: [0, 0, 0]  → VarInt 1~2비트 (64비트 대비 극적 압축)

Value XOR:
  연속 카운터 증분의 XOR → 거의 0 → 평균 1.37비트/샘플 (약 46배 압축)

메트릭 → 트레이스 → 로그 3단계 드릴다운:

  Grafana p99 스파이크 감지
    → Exemplar 클릭 (스파이크 시점 traceId 포함)
    → Tempo: 느린 DB Span(2.8초) 발견
    → Loki: traceId 필터링 → "Connection timeout to DB" 로그 확인

📖 각 문서 구성 방식

모든 문서는 동일한 구조로 작성됩니다.

섹션 설명
🎯 핵심 질문 이 문서를 읽고 나면 답할 수 있는 질문
🔍 왜 이 개념이 실무에서 중요한가 실무에서 마주치는 문제 상황과 이 개념의 연결
😱 흔한 실수 Before — 원리를 모를 때의 접근과 그 결과
올바른 접근 After — 원리를 알고 난 후의 설계/운영
🔬 내부 동작 원리 바이트코드 수준, 프로토콜 수준, 저장 구조 분석
💻 실전 실험 ByteBuddy Agent 구현, curl traceparent 실험, PromQL 쿼리
📊 성능/비용 비교 샘플링 비율별 오버헤드, TSDB 압축률, 직렬화 비교
⚖️ 트레이드오프 이 설계의 장단점, 언제 다른 접근을 택할 것인가
📌 핵심 정리 한 화면 요약
🤔 생각해볼 문제 개념을 더 깊이 이해하기 위한 질문 + 해설

🗺️ 추천 학습 경로

🟢 "APM 도구를 쓰지만 어떻게 동작하는지 모른다" — 긴급 이해 (3일)
Day 1  Ch1-01  Observability vs Monitoring → 왜 임계값 모니터링이 부족한가
       Ch1-02  메트릭/로그/트레이스 세 기둥 → 각각이 답하는 질문 구분
Day 2  Ch2-01  Java Agent 메커니즘 → -javaagent가 코드를 변경하는 원리
       Ch3-01  Trace와 Span 모델 → traceId/spanId 인과 관계 구조
Day 3  Ch3-02  W3C TraceContext 헤더 → traceparent 직접 전달 실험
       Ch6-04  RED/USE 방법론 → 장애 진단 프레임워크
🟡 "Prometheus와 분산 추적 내부를 이해하고 싶다" — 핵심 집중 (1주)
Day 1  Ch2-01  Java Agent 메커니즘
       Ch2-03  ByteBuddy 메서드 타이머 직접 구현
Day 2  Ch3-01  Trace/Span 모델
       Ch3-03  HTTP 경계 전파 — Propagator 동작
Day 3  Ch3-04  비동기 컨텍스트 전파 — ThreadLocal 함정
       Ch3-05  Virtual Thread 추적 — ScopedValue
Day 4  Ch4-02  Prometheus 데이터 모델 — 카디널리티 문제
       Ch4-03  TSDB 저장 구조 — Head Block → Block
Day 5  Ch4-04  Gorilla 압축 알고리즘
       Ch4-05  PromQL 내부 — rate(), histogram_quantile()
Day 6  Ch5-02  MDC와 Trace ID 연결
       Ch6-02  Exemplar — 메트릭→트레이스 점프
Day 7  Ch7-06  실전 진단 시나리오 — 3단계 드릴다운
🔴 "Observability 내부를 처음부터 끝까지 완전히 정복한다" — 전체 정복 (7주)
1주차  Chapter 1 전체 — 세 기둥과 OpenTelemetry 표준화
        → Grafana + Prometheus + Loki + Tempo Docker Compose 환경 구성

2주차  Chapter 2 전체 — Java Agent 바이트코드 조작
        → ByteBuddy로 직접 TimingAgent 구현 → 대상 메서드에 Span 심기

3주차  Chapter 3 전체 — 분산 추적 전파
        → curl traceparent 전달 실험 → CompletableFuture Context 유실 재현

4주차  Chapter 4 전체 — Prometheus TSDB
        → OBJECT ENCODING처럼 tsdb 파일 직접 분석 → PromQL Recording Rule 설정

5주차  Chapter 5 전체 — 구조화 로그 파이프라인
        → MDC traceId 삽입 → Loki에서 traceId 필터링 → Tempo 연동

6주차  Chapter 6 전체 — Grafana 시각화와 알림
        → RED 대시보드 구성 → Exemplar 활성화 → SLO 위반 알림 설계

7주차  Chapter 7 전체 — Spring 통합과 Kubernetes 배포
        → ServiceMonitor 설정 → auto-instrumentation CRD → 실전 장애 진단

🔗 연관 레포지토리

레포 주요 내용 연관 챕터
jvm-deep-dive Java Agent, 바이트코드 구조, 클래스로딩 Ch2-01(premain() 구조), Ch2-02(ASM/ByteBuddy 도구 선택), Ch2-04(OTel Agent 클래스 변환)
java-concurrency-deep-dive ThreadLocal, Virtual Thread, CompletableFuture Ch3-04(비동기 Context 전파), Ch3-05(Virtual Thread ScopedValue)
network-deep-dive HTTP 헤더, TCP, 프록시 Ch3-02(traceparent 헤더 포맷), Ch3-03(HTTP 경계 전파)
spring-core-deep-dive AOP Proxy, 인터셉터 체인 Ch7-03(@Observed AOP → ObservationHandler 체인)
spring-boot-internals Auto-configuration, Actuator 자동 구성 Ch7-01(PrometheusMeterRegistry 자동 구성 클래스)
kubernetes-deep-dive K8s Operator, 사이드카 패턴 Ch4-06(ServiceMonitor CRD), Ch7-05(OTel Operator auto-instrumentation)
database-internals InnoDB, WAL, 트랜잭션 Ch4-03(Prometheus WAL 비교 — 로그 선행 쓰기 공통 원리)

💡 이 레포는 Observability 도구의 내부 동작에 집중합니다. Spring을 모르더라도 Chapter 1~4로 OpenTelemetry / Prometheus / 분산 추적 원리를 학습할 수 있습니다. Chapter 7은 Spring Boot Auto-configuration과 Spring MVC / WebFlux 동작 원리에 익숙할 때 더 깊이 연결됩니다.


🙏 Reference


⭐️ 도움이 되셨다면 Star를 눌러주세요!

Made with ❤️ by Dev Book Lab


"Grafana 대시보드를 보는 것과, Java Agent가 바이트코드를 조작해 어떻게 메트릭을 수집하는지 아는 것은 다르다"

About

Grafana 대시보드를 보는 것과, Java Agent가 바이트코드를 조작해 어떻게 메트릭을 수집하는지 아는 것은 다르다

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors