옵저버빌리티 스택 도입 PRD
Background
sports-application backend는 계측이 완전히 없는 상태가 아니다 — build.gradle.kts에 micrometer-registry-datadog 의존성이 있고, application.yml에 management.datadog.metrics.export.uri(Datadog API 엔드포인트)와 management.metrics.tags.env: ${APP_ENV:local} 태그가 이미 설정돼 있으며, management.endpoints.web.exposure.include: health,info,prometheus,metrics로 actuator의 /actuator/prometheus·/actuator/metrics 엔드포인트도 이미 노출돼 있다. 즉 Micrometer 계측 자체와 env 태그 체계는 이미 존재하며, 노출 채널이 Datadog(유료 SaaS) 전용으로 향해 있다는 것이 실제 공백이다. 반면 분산 추적(OpenTelemetry)은 전혀 없다 — API 요청이 여러 컴포넌트(Controller→UseCase→DomainService→외부 Gateway)를 거치지만 하나의 trace로 엮이지 않는다.
application.yml에 MySQL·MongoDB·Redis·Kafka가 localhost 기본값으로 참조돼 있으나, 이들을 함께 기동하는 docker-compose.yml이 없어(현재는 minio·mock-pg·mock-servers·mailhog만 포함) 인프라 전체를 한 번에 관측할 방법 자체가 없다. Kafka는 KafkaConsumerConfig/ProducerConfig·KafkaDomainEventPublisher가 존재하고 NotificationEventWorker가 실제 소비 중이라, 메시징 지연·lag을 관측할 실질적 필요가 이미 있다.
요구사항은 Grafana·Prometheus·Loki·Tempo(LGTM) 스택 전부와 OpenTelemetry(OTel) 기반 전 요청 분산 추적, MySQL·Redis·Kafka·Spring 앱 4종 인프라 메트릭, Kafka UI 도입을 명시한다. 이는 현재의 Datadog(유료 SaaS) 지향 설정과 방향이 다르므로, 본 과제는 **micrometer-registry-datadog을 Prometheus registry로 대체(제거)**하고 OTel을 신규 도입한다. 또한 [배포 파이프라인·환경 분리](../배포 파이프라인·환경 분리/PRD.md) 과제로 dev·prod 두 환경이 분리되므로, 이 스택은 두 환경 모두를 계측 대상으로 하되 관측 목적이 다르다 — dev는 배포 직후 회귀 확인, prod는 [상시 트래픽 시뮬레이터](../상시 트래픽 시뮬레이터/PRD.md)가 만드는 실질 트래픽에 대한 실시간 운영 관측이다.
Problem Definition
- Micrometer 계측·
env태그·actuator prometheus 엔드포인트는 이미 존재하지만, 실제로 이를 수집·시각화하는 Prometheus·Grafana가 없다 — registry가 Datadog 전용이라 무료 로컬 스택(LGTM)과 연결되지 않는다. - 분산 추적이 없다 — trace가 하나로 엮이지 않아, 느린 요청의 병목이 어느 구간인지 추측에 의존한다.
- 인프라 메트릭이 없다 — MySQL·Redis·Kafka가 실행 중이어도 CPU·메모리·슬로우 쿼리·lag을 볼 방법이 없다.
- dev/prod 환경이 아직 분리돼 있지 않아(← [배포 파이프라인·환경 분리](../배포 파이프라인·환경 분리/PRD.md) 과제 선행 필요),
env태그 값 체계(local/dev/prod)가 실제로 무엇을 의미하는지 확정돼 있지 않다.
Goals / Non-Goals
Goals
micrometer-registry-datadog의존성을 제거하고 Prometheus registry(micrometer-registry-prometheus)로 전환한다 — 기존 actuator의/actuator/prometheus엔드포인트는 그대로 재사용한다(신규 엔드포인트 추가 불필요).- backend(Spring Boot)에 OpenTelemetry 계측을 추가하고 OTLP로 Collector에 전송한다.
- 단일 OTel Collector가 trace는 Tempo로, 메트릭은 Prometheus로, 로그는 Loki로 라우팅한다.
- MySQL·Redis·Kafka 각각의 exporter로 인프라 메트릭을 수집하고, Kafka UI를 추가한다.
- Spring 앱 메트릭(CPU, Memory, GC, Heap, Tomcat 스레드풀 active/max, Async 스레드풀 active/max)을 기존 Micrometer 계측으로 노출한다(신규 계측 코드 불필요, registry 전환만 필요).
- Grafana 대시보드로 MySQL·Redis·Kafka·Spring 앱 4종 패널을 구성한다.
- 기존
env태그(management.metrics.tags.env: ${APP_ENV:local})를 그대로 재사용해 dev·prod를 구분한다 — 새 라벨 키를 도입하지 않는다.env값 체계(local/dev/prod)는 [배포 파이프라인·환경 분리](../배포 파이프라인·환경 분리/PRD.md)에서 정의한다.
Non-Goals
- 프로덕션(원격 클라우드 서버) 옵저버빌리티 배포 — 본 과제는 [배포 파이프라인·환경 분리](../배포 파이프라인·환경 분리/PRD.md)에서 정의하는 로컬 docker-compose 기준 dev·prod 두 환경에 한정한다.
- 모바일(React Native) 클라이언트의 RUM(Real User Monitoring) 풀 계측 — 서버 사이드 trace 합류까지만 다룬다.
- 로그 장기 보존·비용 최적화 정책 — 개인 프로젝트 규모에서는 다루지 않는다.
- P95 레이턴시 임계치 기반 Discord 알림 — 이 과제는 데이터 소스(Prometheus/Loki/Tempo) 제공까지이며, 알림 자체는 [지능형 장애 알림](../지능형 장애 알림/PRD.md) 소관이다.
- MongoDB 인프라 메트릭 — 요구사항이 명시한 인프라는 MySQL·Redis·Kafka·Spring 4종이며, MongoDB는 범위 외로 제외한다.
User Scenarios
페르소나: 운영자(본인).
- 느린 요청의 병목 찾기: [상시 트래픽 시뮬레이터](../상시 트래픽 시뮬레이터/PRD.md)가 prod에 트래픽을 발생시키는 중 특정 API가 느려지면, 운영자가 Grafana에서 해당 trace 하나를 열어 어느 span(DB 쿼리, 외부 Gateway 호출 등)이 병목인지 즉시 식별한다.
- 인프라 메트릭 점검: 운영자가 대시보드에서 MySQL 커넥션 수, Kafka consumer lag, Redis 메모리 사용량을 한 화면에서 확인한다.
- 환경별 비교: dev 배포 직후와 prod 상시 트래픽 상황의 메트릭을 같은 대시보드에서 기존
env태그로 구분해 비교한다. - 예외 — Collector 장애: OTel Collector가 다운되면 메트릭·trace 수집이 중단됨을 Prometheus 자체 헬스체크(self-scrape)로 감지한다.
Benchmarking
| 제품/사례 | 카테고리 | 참조 패턴 | URL |
|---|---|---|---|
| 사람인(Saramin) OpenTelemetry + SigNoz | 채용 플랫폼 옵저버빌리티 도입 사례 | OTel 도입으로 MSA 전환 후 서비스 간 호출 복잡도를 시각화하고 병목·장애 전파 경로를 데이터 기반으로 식별해 MTTR을 50% 이상 단축한 사례 — “trace 연결이 병목 식별의 핵심”이라는 본 과제의 핵심 가설을 뒷받침 | OpenTelemetry 도입기 — 사람인 기술블로그 |
| 우아한형제들(Woowa Brothers) 로그 시스템 전환 | 배달 플랫폼 로그 인프라 | ELK Stack에서 Grafana Loki로 전환하며 OTel·Tempo 파일럿을 병행 검토한 사례 — 로그·트레이스 연속성을 하나의 스택(LGTM)으로 통합하는 방향성이 본 과제와 일치 | 따끈따끈한 전사 로그 시스템 전환기 — 우아한형제들 기술블로그 |
Functional Requirements
| ID | 요구사항 | 우선순위 |
|---|---|---|
| FR-1 | micrometer-registry-datadog 의존성을 제거하고 micrometer-registry-prometheus로 전환한다. 기존 actuator /actuator/prometheus 엔드포인트 노출 설정은 그대로 재사용한다 | P0 |
| FR-2 | backend에 OpenTelemetry 계측(Java Agent 또는 Micrometer Tracing + OTel Bridge)을 추가하고 OTLP로 전송한다 | P0 |
| FR-3 | OTel Collector 1개를 도입해 trace→Tempo, 메트릭→Prometheus, 로그→Loki로 라우팅한다 | P0 |
| FR-4 | Prometheus가 MySQL(mysqld_exporter), Redis(redis_exporter), Kafka(kafka_exporter 또는 JMX exporter) 메트릭을 수집한다 | P0 |
| FR-5 | Spring 앱 메트릭(CPU, Memory, GC, Heap, Tomcat 스레드풀 active/max, Async 스레드풀 active/max)을 기존 Micrometer 계측으로 노출한다(신규 계측 코드 불필요) | P0 |
| FR-6 | Kafka UI 컨테이너를 추가해 lag·partition을 조회할 수 있게 한다 | P0 |
| FR-7 | Grafana 대시보드로 MySQL·Redis·Kafka·Spring 앱 4종 패널을 구성한다 | P0 |
| FR-8 | 기존 env 태그(management.metrics.tags.env)를 모든 메트릭·trace·로그에 일관되게 부여해 dev·prod 환경별 필터링을 지원한다. 새 라벨 키를 도입하지 않는다 | P0 |
| FR-9 | docker-compose에 MySQL·Redis·Kafka·MongoDB를 명시적으로 포함해 인프라+옵저버빌리티 스택을 단일 기동으로 띄울 수 있게 한다 | P1 |
| FR-10 | FE(web) 요청도 가능한 범위에서 trace에 합류시킨다(Next.js 서버 런타임 한정) | P1 |
| FR-11 | Loki 로그와 Tempo trace를 trace_id 라벨로 상호 연결한다(exemplar) | P2 |
Non-Functional Requirements
- 모든 API 요청의 trace 연결율 99% 이상(샘플링은 100%로 설정하며, 1%의 여유는 Collector 재시작 등 인프라 순간 장애를 허용하기 위함이다 — Success Metrics도 동일 기준 99%로 통일한다).
- Grafana 대시보드 로딩 P95 3초 이내.
- 로컬 docker-compose 전체 스택(앱+인프라+옵저버빌리티) 메모리 사용량 8GB 이내(개인 개발 머신 제약).
- FR-8의
env태그 누락 데이터 0건.
Operations
이 과제 자체가 운영 관측 체계 구축이므로, “운영”은 이 스택을 운영하는 방법을 의미한다.
- Grafana 대시보드 URL·접근 방법을 문서화한다.
- 대시보드 패널: MySQL(CPU/Memory/슬로우쿼리/커넥션), Redis(CPU/Memory), Kafka(CPU/Memory/lag/partition), Spring 앱(CPU/Memory/GC/Heap/스레드풀).
- [지능형 장애 알림](../지능형 장애 알림/PRD.md) 과제가 이 스택의 Prometheus·Loki·Tempo 데이터를 알림 트리거·원인 분석의 데이터 소스로 사용한다.
Success Metrics
- 4대 인프라(MySQL/Redis/Kafka/Spring) 메트릭 대시보드 노출 완료.
- 임의 요청 10건을 샘플링해 trace 연결율 99% 이상 확인(10건 중 최소 9건 이상 연결).
- Kafka UI에서 consumer lag을 실시간 조회 가능함을 확인.
- dev·prod 두 환경의 메트릭이 동일 대시보드에서 기존
env태그로 구분됨을 확인.
Milestones
- M1: registry 전환(Datadog→Prometheus) + OTel 계측 + Collector + Tempo(FR-1~FR-3).
- M2: Prometheus + exporter 3종(FR-4).
- M3: Loki(FR-3 로그 경로).
- M4: Grafana 대시보드 4종 +
env태그 일관 적용(FR-5~FR-8). - M5: Kafka UI, docker-compose 인프라 편입(FR-6, FR-9).
의존: [배포 파이프라인·환경 분리](../배포 파이프라인·환경 분리/PRD.md)에서 env 값 체계(local/dev/prod)와 dev·prod 환경 구조(포트·네임스페이스)가 먼저 정의돼야 FR-8이 확정된다. 이 과제 완료는 [지능형 장애 알림](../지능형 장애 알림/PRD.md)의 선행 조건이다.
Open Questions
- docker-compose에 MySQL·Redis·Kafka·MongoDB를 추가하면 기존에 개발자가 로컬에서 별도로 띄워둔 인스턴스와 포트가 충돌할 수 있다 — 마이그레이션 안내가 필요하다.
- dev 환경의 옵저버빌리티 스택은 prod와 동일 수준으로 상시 구동할지, 배포 직후 일정 시간만 구동할지(리소스 절약) 결정이 필요하다.
Document History
| 날짜 | 변경 내용 |
|---|---|
| 2026-07-03 | 최초 작성 (dev/prod 환경 분리 요구사항 반영) |
| 2026-07-03 | 재검수 1차 반영: AS-IS 정정(micrometer-registry-datadog+actuator+env 태그가 이미 존재, greenfield 프레이밍 제거), FR을 “registry 전환+OTel 신규 도입”으로 재기술(FR-1), 신규 environment 라벨 대신 기존 env 태그 재사용으로 통일(FR-8), FR-8(구 FR-8) 우선순위 P1→P0 재분류, trace 목표 100%/99% 불일치를 99%로 단일화, Non-Goals에 “P95 알림은 ⑥ 소관”·“MongoDB 메트릭 제외” 추가 |