옵저버빌리티 스택 도입 TDD

Background

PRD 참조. 4개 서비스를 OpenTelemetry로 계측해 단일 trace로 연결하고, 동일 텔레메트리를 SigNoz·Grafana 두 스택에 fan-out해 PoC 비교 후 ADR로 운영 스택을 확정한다.

Overview

  • 모든 서비스(backend·aggregator·ml·frontend)를 OTLP로 계측 — Spring 2종은 OTel Java agent 자동계측, ml은 opentelemetry-instrumentation, frontend는 @vercel/otel.
  • 단일 OpenTelemetry Collector(게이트웨이) 가 OTLP를 수신해 SigNoz와 Grafana(Tempo/Prometheus/Loki)로 fan-out.
  • 인프라 3종(MySQL·Kafka·Redis) 컨테이너 도입 + exporter(mysqld/kafka/redis) → Prometheus scrape 및 Collector 수집.
  • W3C Trace Context 전파로 서비스 간 trace 연결. Kafka는 Collector·agent가 메시지 헤더에 traceparent를 실어 produce→consume span 연결.
  • compose는 앱/Grafana/SigNoz/Collector를 별도 파일로 분리해 PoC 동안 두 스택을 독립 기동.
  • 임계치 알림은 Grafana contact point·SigNoz alert channel 양쪽에서 Discord webhook으로 검증.

Terminology

용어정의
OpenTelemetry(OTel)벤더 중립 텔레메트리 표준. trace·metric·log를 OTLP 프로토콜로 내보냄
OTLPOpenTelemetry Protocol. 서비스→Collector→백엔드 전송 규격
CollectorOTLP를 수신·처리·fan-out하는 게이트웨이
trace / span요청 한 건의 전체 경로(trace) / 그 안의 단위 작업(span)
W3C Trace Contexttraceparent 헤더로 서비스 간 trace를 잇는 전파 표준
LGTMLoki(로그)·Grafana(UI)·Tempo(trace)·Mimir/Prometheus(metric) 조합
SigNozOTel-native 단일 앱 옵저버빌리티 플랫폼(ClickHouse 백엔드)
exporter인프라(MySQL/Kafka/Redis) 메트릭을 Prometheus 포맷으로 노출하는 사이드카
fan-out한 입력을 둘 이상의 백엔드로 동시 전송

Define Problem

AS-IS

[browser] → frontend(Next.js) → aggregator(:8090) → backend(:8080) → ml(:8000)
                                                      └→ MySQL(:3308)

- 계측 없음: 메트릭 0, trace 0
- 인프라: MySQL 컨테이너만 존재 (Kafka·Redis 미도입)
- 가시성: 로그 grep + 추측에 의존

TO-BE

서비스(4종) ──OTLP──▶ OTel Collector ──┬──▶ SigNoz (ClickHouse) ── SigNoz UI
                                        └──▶ Tempo/Prometheus/Loki ── Grafana UI
                                              ▲
infra exporter(mysqld/kafka/redis) ──scrape──┘ (Prometheus + Collector)

- 단일 trace: frontend→aggregator→backend→ml + Kafka consume span 연결
- 앱 메트릭(OTLP push) + 인프라 메트릭(exporter scrape) 통합
- 동일 텔레메트리가 두 스택에 동시 유입 → PoC 공정 비교 → ADR 확정

Possible Solutions

과거 결정 참조

방안설명채택 여부
OTel + 중앙 Collector fan-out서비스는 OTLP만 알고 Collector가 두 백엔드로 분기. 두 스택이 동일 입력을 받아 공정 비교 가능채택 — 벤더 중립 + PoC 공정성 동시 충족 (ADR-003)
서비스별 dual-export각 서비스가 SigNoz·Grafana로 직접 2중 전송미채택 — 서비스가 백엔드를 알게 됨, 설정 중복, 스택 교체 비용 큼
벤더 네이티브 에이전트(Datadog 등)단일 벤더 SDK로 계측미채택 — 벤더 종속, OSS 비교 PoC 불가, 로컬 비용 (ADR-002)
Micrometer Tracing + OTLPSpring에 라이브러리 직접 추가미채택(Spring) — 코드 변경·의존성 추가 필요, Java agent가 무코드 자동계측 우위 (ADR-004)

Detail Design

Component Diagram

flowchart LR
    subgraph Services["서비스(계측)"]
        FE["frontend\n@vercel/otel"]
        AGG["aggregator\nOTel Java agent"]
        BE["backend\nOTel Java agent"]
        ML["ml\nopentelemetry"]
    end
    subgraph Infra["인프라 + exporter"]
        MYSQL["MySQL + mysqld-exporter"]
        KAFKA["Kafka + kafka-exporter"]
        REDIS["Redis + redis-exporter"]
    end
    subgraph Pipeline["수집"]
        COL["OTel Collector\n(게이트웨이)"]
        PROM["Prometheus"]
    end
    subgraph Backends["백엔드 스택(PoC)"]
        SIGNOZ["SigNoz"]
        GRAF["Grafana\nTempo/Loki"]
    end
    FE --> COL
    AGG --> COL
    BE --> COL
    ML --> COL
    Infra --> PROM
    PROM --> GRAF
    COL --> SIGNOZ
    COL --> GRAF
    COL --> PROM

Sequence Diagram — 단일 trace 전파 (정상 흐름)

sequenceDiagram
    participant FE as frontend
    participant AGG as aggregator
    participant BE as backend
    participant ML as ml
    participant COL as Collector
    FE->>AGG: HTTP (traceparent 주입)
    AGG->>BE: HTTP (traceparent 전파)
    AGG->>ML: HTTP (traceparent 전파)
    BE-->>AGG: response + span export
    ML-->>AGG: response + span export
    AGG-->>FE: response
    FE->>COL: OTLP span
    AGG->>COL: OTLP span
    BE->>COL: OTLP span
    ML->>COL: OTLP span
    COL->>COL: 동일 trace_id로 span 병합 후 fan-out

Sequence Diagram — Kafka consume trace (샘플 검증 경로)

sequenceDiagram
    participant P as Producer(backend)
    participant K as Kafka
    participant C as Consumer(backend)
    participant COL as Collector
    P->>K: produce(traceparent 헤더 주입)
    K->>C: deliver(헤더 보존)
    C->>C: consume span을 부모 trace에 연결
    P->>COL: producer span (OTLP)
    C->>COL: consumer span (OTLP)

ERD

해당 없음 — 본 과제는 텔레메트리 수집·시각화 인프라이며 신규 비즈니스 테이블이 없다. (Kafka·Redis는 메트릭 대상 인프라로만 도입)

Testing Plan

  • 단일 trace 연결: frontend→aggregator→backend→ml 요청 1건이 동일 trace_id로 양 스택 UI에 표시되는지 검증.
  • Kafka consume span: 샘플 produce→consume 1건이 동일 trace에 producer·consumer span으로 연결되는지 검증.
  • 앱 메트릭: Spring(JVM·HTTP·HikariCP), ml(요청 처리량·지연)이 양 스택에 노출되는지 검증.
  • 인프라 메트릭: mysqld/kafka/redis exporter 메트릭이 Prometheus·Collector 경유로 수집되는지 검증.
  • fan-out 동등성: 같은 부하에서 SigNoz·Grafana의 trace/메트릭 건수가 일치(±오차)하는지 검증.
  • 알림: 임계치 위반 시 Discord webhook 수신 검증(양 스택).
  • 예외: Collector 다운 시 서비스가 graceful degrade(앱 동작 무영향)하는지 검증.

Release Scenario

  1. 인프라 확장 — Kafka·Redis + exporter 컨테이너 기동(STK-OBS-01).
  2. 백엔드 스택 기동 — Grafana LGTM(STK-OBS-02)·SigNoz(STK-OBS-03) 각각 별도 compose로 기동.
  3. Collector 기동 — fan-out 라우팅 적용(STK-OBS-04).
  4. 서비스 계측 적용 — backend·aggregator·ml·frontend(STK-OBS-05~08).
  5. 검증·알림 — Kafka 샘플 trace(STK-OBS-09)·Discord 알림(STK-OBS-10).
  6. PoC 비교·ADR 확정(STK-OBS-11) 후 운영 스택 1개로 정리.
  7. 롤백: 옵저버빌리티 compose 파일만 docker compose down. 앱 계측은 OTel env 비활성(OTEL_SDK_DISABLED=true)으로 즉시 OFF, 코드 변경 불요.

Project Information

  • 저장소: stock-application/ (루트 compose + 각 서비스 디렉토리)
  • 포트: Grafana 3000 / SigNoz 3301 / Collector OTLP 4317·4318 / Prometheus 9090 / exporters 9104·9308·9121 (기존 3306·3307·3308 회피)
  • 티켓 prefix: STK-OBS-NN