[Server] Spring Boot 메트릭 수집 모니터링 구축 (Railway + Better Stack OTLP push)

easyone·2026년 7월 6일

SERVER

목록 보기
4/6
post-thumbnail

Spring Boot 메트릭은 어떻게 전달되는지..?

이번에 모니터링을 평소 하던 AWS가 아닌 Railway와 연결해서 하면서, 어떤 모니터링 방식으로 메트릭 수집을 하면 좋을지 생각해보게 되었다.

메트릭은 매 이벤트를 전송하는 게 아니라, 애플리케이션 메모리 안에서 숫자를 계속 누적하거나 갱신하다가 일정 주기마다 스냅샷을 보내는 구조였다.

이번 글에서는 Spring Boot Actuator, Micrometer, OTLP, Grafana까지 메트릭이 흘러가는 과정을 한 번에 정리한다.

전체 흐름

Spring Boot 애플리케이션에서 JVM 메트릭이 Grafana 같은 백엔드까지 가는 흐름은 대략 이렇다.

JVM 내부 상태
-> Micrometer 계측/집계
-> OTLP registry가 주기적으로 전송
-> Grafana 같은 백엔드에서 저장/조회

중요한 점은 요청마다 외부로 메트릭을 보내지 않는다는 것이다.

애플리케이션 안의 MeterRegistry가 숫자를 관리하고, OtlpMeterRegistry 같은 registry가 설정된 주기마다 현재 상태를 묶어서 전송한다. 예를 들어 step이 30초라면 30초마다 한 번씩 스냅샷을 보낸다.

그래서 트래픽이 많아져도 메트릭 전송량이 요청 수만큼 폭증하지 않는다. 전송량은 주로 메트릭 개수, 라벨 조합 수, 전송 주기에 영향을 받는다.

JVM 상태

JVM은 내부 상태를 MXBean, 즉 JMX 기반 인터페이스로 노출한다.

대표적으로 이런 값들이 있다.

  • MemoryMXBean: 힙/논힙 메모리 사용량
  • GarbageCollectorMXBean: GC 횟수, 누적 시간
  • OperatingSystemMXBean: CPU 사용률
  • ThreadMXBean: 스레드 수

Spring Boot Actuator를 붙이면 기동 시 여러 MeterBinder가 자동으로 등록된다.

  • JvmMemoryMetrics
  • JvmGcMetrics
  • ProcessorMetrics
  • JvmThreadMetrics

이 Binder들이 JVM의 MXBean 값을 읽어서 Micrometer의 Meter로 연결한다. 그래서 애플리케이션에서 jvm.memory.used, process.cpu.usage, jvm.threads.live 같은 메트릭을 볼 수 있게 된다.

Micrometer로 값 관리

Micrometer는 계측 추상화 계층이다. 중심에는 MeterRegistry가 있고, 그 안에 여러 Meter가 등록된다.

대표적인 Meter는 세 가지다.

Meter의미예시
Gauge현재 순간 값힙 사용량, 스레드 수
Counter단조 증가 누적값이벤트 횟수, 누적 바이트
Timer호출 수, 총 시간, 최대값, 히스토그램HTTP 지연 시간, GC pause

여기서 헷갈리기 쉬운 점은 계측과 전송이 분리되어 있다는 것이다.

Micrometer는 앱 안에서 값을 관리한다.
외부로 보내는 일은 Prometheus registry, OTLP registry 같은 구현체가 맡는다.

OTLP는 어떻게 전송하는지?

OTLP 방식에서 OtlpMeterRegistry는 push 방식으로 동작한다.

앱이 시작되면 백그라운드 스레드가 돌고, 설정된 step마다 publish 과정이 실행된다.

1. Registry의 모든 Meter를 확인한다.
2. Gauge는 지금 값을 읽는다.
3. Counter, Timer는 누적값과 히스토그램 상태를 읽는다.
4. OTLP protobuf 포맷으로 직렬화한다.
5. 설정된 endpoint로 HTTP POST를 보낸다.

설정에서는 보통 이런 값이 중요하다.

management:
  otlp:
    metrics:
      export:
        url: https://example-otlp-endpoint
        step: 30s
        headers:
          Authorization: Bearer ${OTLP_TOKEN}

실제 속성 이름은 Spring Boot와 Micrometer 버전에 따라 확인이 필요하지만, 개념적으로는 "어디로", "얼마나 자주", "어떤 인증으로" 보낼지가 핵심이다.

Prometheus pull과 OTLP push의 차이

메트릭을 수집하는 방식은 크게 pull과 push로 나눠서 볼 수 있다.

Prometheus pull

Prometheus 방식은 수집기가 앱으로 들어와서 값을 가져간다.

Prometheus -> /actuator/prometheus -> Spring Boot App

앱은 /actuator/prometheus 같은 엔드포인트에 현재 값을 노출하고, Prometheus가 주기적으로 scrape한다.

Docker Compose나 Kubernetes처럼 내부 네트워크와 service discovery가 잘 잡힌 환경에서는 자연스럽다. 하지만 Railway처럼 외부 수집기가 앱 주소로 접근하거나 target을 관리하기 애매한 환경에서는 설정이 번거로울 수 있다.

OTLP push

OTLP 방식은 앱이 외부 endpoint로 직접 보낸다.

Spring Boot App -> OTLP endpoint -> Grafana Cloud

수집기가 앱으로 들어올 필요가 없다. 앱이 인증 정보를 들고 외부 endpoint로 POST를 보내면 된다.

그래서 PaaS 환경이나 외부 접근이 까다로운 환경에서는 push 방식이 더 단순할 수 있다.

Grafana에서는 어떻게 보이는지?

Grafana 같은 백엔드는 받은 숫자를 시계열로 저장한다.

시계열은 보통 다음 조합으로 구분된다.

메트릭 이름 + 라벨 조합

예를 들어 같은 jvm.memory.used라도 라벨이 다르면 다른 시계열이다.

  • area=heap
  • area=nonheap
  • id=G1 Eden Space
  • application=gerd

management.metrics.tags.application=gerd처럼 공통 라벨을 붙여두면 나중에 Grafana에서 특정 앱만 필터링하기 좋다.

PromQL에서는 이런 식으로 조회할 수 있다.

process_cpu_usage
rate(jvm_gc_pause_seconds_sum[5m])

미리 만들어진 JVM 대시보드를 가져오면 heap, GC pause, CPU, thread 같은 기본 패널을 빠르게 구성할 수 있다.

Better Stack이랑 다른점

Better Stack은 로그 수집, 업타임 모니터링, 온콜까지 한 번에 묶기 좋다. 별도로 Loki나 알림 시스템을 붙이지 않아도 시작하기 쉽다는 장점이 있다.

대신 메트릭 대시보드는 Grafana의 PromQL 대시보드를 그대로 쓰기보다 Better Stack의 SQL 방언에 맞춰 직접 패널을 구성해야 할 수 있다.

예를 들어 진단용으로는 이런 패널을 만들 수 있다.

  • Heap used vs max
  • Live threads
  • GC pause p50/p95/p99
  • CPU usage
  • Disk free
  • Logback events by level

Better Stack 쿼리에서는 게이지 값에 avgMerge(value_avg)를 쓰거나, 라벨을 label('level')처럼 꺼내는 식의 차이가 있다. 히스토그램 quantile도 저장소 방언에 맞춰 확인해야 한다.

여기서 중요한 건 쿼리를 외우는 게 아니라, 실제 저장된 metric name과 label을 먼저 확인하는 것이다. Micrometer에서 보던 이름, Prometheus 이름, OTLP 저장소에서 보이는 이름이 조금씩 다를 수 있다.

커스텀 대시보드 구성

Better Stack은 그라파냐처럼 spring boot 전용 세팅되어있는게 없다.
그래서 확인할 항목을 골라서 커스텀으로 쿼리를 입력해서 직접 만들어야 한다.

다음과 같은 순서로 제작했다.

1. Better Stack Source 만들기

Telemetry -> Sources 탭에 들어가서 Connect Source를 누른다.
Source를 만들면 Source token, url 이렇게 두개가 나온다.

curl -X POST \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer [token]' \
  -d '{"dt":"'"$(date -u +'%Y-%m-%d %T UTC')"'","message":"Hello from Better Stack!"}' \
  --insecure \
  https://[url]
management:
  endpoints:
    web:
      exposure:
        include: health,info,env,configprops  
  endpoint:
    health:
      show-details: never
      probes:
        enabled: true
  health:
    diskspace:
      enabled: true
    ping:
      enabled: true
    livenessstate:
      enabled: true
    readinessstate:
      enabled: true
  metrics:
    tags:
      application: ${spring.application.name}
  otlp:
    metrics:
      export:
        enabled: true
        url: ${BETTERSTACK_OTLP_URL}/v1/metrics
        headers:
          Authorization: Bearer ${BETTERSTACK_SOURCE_TOKEN}
        step: 30s

각각 OTLP, SOURCE_TOKEN으로 넣어준다!

2. 커스텀 대시보드 만들기

해당 탭에서 Create dashboard로 들어간다. 그러면 처음에는 아예 대시보드가 비어있다.

맨 오른쪽의 플러스 버튼을 누르면 차트를 만들 수 있다.

source를 아까 만들었던것으로 절정하고, Query를 입력하고 Run query를 하면 설정한 차트 타입에 따라 볼 수 있다.

나는 다음과 같은 항목을 추가해줬다.

패널확인하려는 것
Heap used vs max메모리 기준선과 누수 의심
GC pause p95/p99사용자 지연으로 이어질 수 있는 멈춤
CPU usage앱 CPU와 시스템 CPU 추세
Live threads스레드 증가나 고착 여부
Logback eventserror/warn 로그 급증
Disk free디스크 고갈 위험

이렇게 항목을 달아주면...

완성이된다~!

정리

메트릭 수집의 핵심은 이벤트 전송이 아니라 숫자 상태의 주기적 스냅샷이다.

  • JVM은 MXBean/JMX로 내부 상태를 노출한다.
  • Spring Boot Actuator의 MeterBinder가 JVM 값을 Micrometer Meter로 연결한다.
  • Micrometer는 앱 메모리의 MeterRegistry에서 값을 관리한다.
  • OTLP registry는 step마다 protobuf로 직렬화해서 HTTP POST한다.
  • Grafana나 Better Stack은 이를 시계열로 저장하고 label 기준으로 조회한다.

Prometheus pull이 좋은 환경도 있고, OTLP push가 더 단순한 환경도 있다. 지금 나같은 경우는 Railway를 사용하고 있기에 Better Stack을 사용했다.

profile
백엔드 개발자 지망 대학생

0개의 댓글