"Grafana 대시보드를 보는 것과, Java Agent가 바이트코드를 조작해 어떻게 메트릭을 수집하는지 아는 것은 다르다"
"
@Observed붙이면 트레이싱 되겠지 — 와 — Span이 내부에서 어떻게 생성되고 HTTP 헤더를 타고 마이크로서비스를 건너가는지, Prometheus가 수십억 시계열 데이터를 어떻게 1.37비트/샘플로 압축하는지 아는 것의 차이를 만드는 레포"
시스템이 복잡해질수록 "지금 무슨 일이 일어나고 있는가" 를 아는 것이 가장 중요한 엔지니어링 스킬이 된다
Observability의 세 기둥인 메트릭 / 로그 / 트레이스가 내부에서 어떻게 동작하는지, 왜 이렇게 설계됐는가라는 질문으로 끝까지 파헤칩니다
Observability에 관한 자료는 넘쳐납니다. 하지만 대부분은 "어떤 도구를 켜는가" 에서 멈춥니다.
| 일반 자료 | 이 레포 |
|---|---|
"-javaagent 붙이면 자동으로 계측됩니다" |
premain() → ClassFileTransformer → ByteBuddy ASM 변환 → 원본 execute() 에 Span 생성 코드가 삽입되는 전 과정 |
"@Observed 붙이면 트레이싱 됩니다" |
ObservationRegistry → ObservationHandler → Tracer.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) 환경 |
각 챕터의 첫 문서부터 바로 학습을 시작하세요!
💡 각 섹션을 클릭하면 상세 문서 목록이 펼쳐집니다
핵심 질문: 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 라이브러리 계측(지표 직접 정의), 각 방법의 오버헤드·가시성·제어 수준 트레이드오프 |
핵심 질문:
-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 내부 | MeterRegistry가 Counter / 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 설정으로 오버헤드 조절 |
핵심 질문: 하나의 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 필터링에 활용하는 방법 |
핵심 질문: 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 |
핵심 질문: 구조화 로그는 왜 텍스트 로그보다 검색에 유리한가? 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로 전환하는 패턴, 변경 후 자동 복원 전략, 로그 레벨에 따른 성능 영향 측정 |
핵심 질문: 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 라우팅 규칙(route → receiver)과 그룹핑으로 알림 폭풍을 방지하는 방법, silence와 inhibit으로 노이즈 줄이기, CPU 사용률(원인) 대신 응답 시간(증상) 기반 알림이 더 유용한 이유, SLO 위반 알림 설계 |
| 04. RED 방법론과 USE 방법론 — 진단 프레임워크 | RED(Rate / Error / Duration)로 서비스 이상을 감지하는 PromQL 3개, USE(Utilization / Saturation / Error)로 인프라 리소스를 진단하는 방법, 두 방법론을 조합한 "증상 → 원인" 진단 순서, Google SRE의 Four Golden Signals와 비교 |
핵심 질문: 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 어노테이션이 ObservationAspect → ObservationRegistry → ObservationHandler 체인으로 처리되는 흐름, 커스텀 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 동작 원리에 익숙할 때 더 깊이 연결됩니다.
- Distributed Systems Observability — Cindy Sridharan
- OpenTelemetry 공식 문서
- Prometheus 공식 문서
- Grafana 공식 문서
- Gorilla: Fast, Scalable, Reliable Time Series Database — Facebook (2015)
- Micrometer 공식 문서
- OpenTelemetry Java Agent GitHub
- W3C TraceContext 표준
- ByteBuddy 공식 문서
- Google SRE Book — Monitoring Distributed Systems
⭐️ 도움이 되셨다면 Star를 눌러주세요!
Made with ❤️ by Dev Book Lab
"Grafana 대시보드를 보는 것과, Java Agent가 바이트코드를 조작해 어떻게 메트릭을 수집하는지 아는 것은 다르다"