Spring Boot 3.x 심화편: GraalVM Native Image & Micrometer Observability 실전 가이드
Backend

Spring Boot 3.x 심화편: GraalVM Native Image & Micrometer Observability 실전 가이드

· 7분 읽기

이 글은 [이전 포스팅: 구 시스템을 Spring Boot 3.x + Java 17로 업그레이드하기] 의 심화편입니다. 업그레이드를 완료했다면, 이제 Spring Boot 3.x만이 제공하는 두 가지 강력한 무기를 제대로 활용해봅시다.


🧠 Part 1. GraalVM Native Image

Native Image란 무엇인가?

기존 Java 애플리케이션은 JVM 위에서 JIT(Just-In-Time) 컴파일 방식으로 실행됩니다. 반면 GraalVM Native Image는 AOT(Ahead-Of-Time) 컴파일을 통해 JVM 없이 실행 가능한 네이티브 바이너리를 생성합니다.

[기존 JVM 방식]
Java 소스 → .class (바이트코드) → JVM 로드 → JIT 컴파일 → 실행
⏱ 기동 시간: 수 초 ~ 수십 초

[GraalVM Native Image]
Java 소스 → AOT 정적 분석 → 네이티브 바이너리 → 즉시 실행
⚡ 기동 시간: 수십 ms

Spring Boot 3.x는 AOT 처리 과정을 공식 지원하면서 GraalVM 연동이 훨씬 간편해졌습니다. docs.spring


Native Image 빌드 설정

Maven (pom.xml)

<plugin>
    <groupId>org.graalvm.buildtools</groupId>
    <artifactId>native-maven-plugin</artifactId>
</plugin>

Gradle (build.gradle)

plugins {
    id 'org.springframework.boot' version '3.3.0'
    id 'org.graalvm.buildtools.native' version '0.9.27' // AOT + Native 지원
}

빌드 명령어

# Maven
./mvnw -Pnative native:compile

# Gradle
./gradlew nativeCompile

# Docker Buildpack 방식 (GraalVM 미설치 환경에서도 가능)
./mvnw -Pnative spring-boot:build-image

성능 비교: JVM vs Native Image

항목 JVM 방식 Native Image
기동 시간 2~5초 50~100ms
메모리 사용량 높음 (JVM 오버헤드) 최대 10배 절감
최고 처리량(Throughput) JIT 최적화로 우수 JVM 대비 다소 낮음
빌드 시간 빠름 (수 초) 느림 (수 분)
리플렉션 지원 완전 지원 별도 힌트 필요

docs.spring

어디에 써야 할까?
  • 적합: AWS Lambda, Google Cloud Run 등 서버리스/컨테이너 환경, 빠른 스케일아웃이 필요한 마이크로서비스
  • 비적합: 장시간 실행되는 모놀리식 서비스 (JIT 최적화의 이점이 Native Image를 앞서는 경우)

⚠️ Native Image 적용 시 주의사항: Reflection 힌트

Native Image의 가장 큰 함정은 리플렉션(Reflection) 입니다. 빌드 시점에 정적 분석으로 처리되기 때문에, 런타임에 동적으로 클래스를 로드하는 코드는 별도로 힌트를 등록해야 합니다. docs.spring

// @RegisterReflectionForBinding 어노테이션으로 힌트 등록
@RegisterReflectionForBinding(UserResponse.class)
@Service
public class UserService {
    // ...
}

또는 reflect-config.json 파일로 직접 명시:

[
  {
    "name": "com.example.dto.UserResponse",
    "allDeclaredConstructors": true,
    "allDeclaredMethods": true
  }
]

Spring Boot 3.x의 RuntimeHintsRegistrar 인터페이스를 구현하면 프로그래밍 방식으로 힌트를 등록할 수도 있습니다. notavoid.tistory

@Component
public class MyRuntimeHints implements RuntimeHintsRegistrar {
    @Override
    public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
        hints.reflection().registerType(UserResponse.class,
            MemberCategory.INVOKE_DECLARED_CONSTRUCTORS,
            MemberCategory.INVOKE_DECLARED_METHODS);
    }
}

📡 Part 2. Micrometer Observability

Spring Boot 3.x의 관찰 가능성 혁신

Spring Boot 2.x에서는 메트릭(Micrometer)분산 추적(Spring Cloud Sleuth) 이 완전히 별개였습니다. Spring Boot 3.x에서는 이 둘이 Micrometer Observation API로 통합되었습니다. baeldung

[Spring Boot 2.x]
메트릭 → Micrometer
분산 추적 → Spring Cloud Sleuth (별도 라이브러리)

[Spring Boot 3.x]
메트릭 + 분산 추적 + 로그 → Micrometer Observation API (통합)
정보

Micrometer는 관찰 가능성의 SLF4J라고 이해하면 됩니다 — 구체적인 모니터링 벤더(Prometheus, Zipkin, Datadog 등)와 무관하게 동일한 API로 계측합니다. micrometer


의존성 추가

<!-- Spring Boot Actuator (메트릭 노출) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- Micrometer Prometheus (메트릭 수집기) -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>

<!-- Micrometer Tracing + OpenZipkin Brave (분산 추적) -->
<dependency>
    <groupId>io.micrometer</groupId>
    <artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.reporter2</groupId>
    <artifactId>zipkin-reporter-brave</artifactId>
</dependency>

application.yml 기본 설정

management:
  endpoints:
    web:
      exposure:
        include: health, info, prometheus, metrics
  tracing:
    sampling:
      probability: 1.0  # 개발환경: 100% 샘플링 (운영환경은 0.1~0.3 권장)
  metrics:
    tags:
      application: ${spring.application.name}  # 모든 메트릭에 앱 이름 태깅

spring:
  application:
    name: my-service

Observation API 실전 활용

Spring Boot 3.x의 핵심은 @Observed 단 하나의 어노테이션으로 메트릭 + 트레이싱 + 로그를 동시에 계측할 수 있다는 점입니다. softwaremill

방법 1: @Observed 어노테이션 (가장 간단)

@Configuration
public class ObservationConfig {
    @Bean
    ObservedAspect observedAspect(ObservationRegistry registry) {
        return new ObservedAspect(registry); // AOP 기반 자동 계측
    }
}

@Service
public class OrderService {

    // 이 메서드 실행 시 자동으로 메트릭 + 트레이스 생성
    @Observed(name = "order.create", contextualName = "주문 생성")
    public Order createOrder(OrderRequest request) {
        // ...
    }
}

방법 2: ObservationRegistry 직접 사용 (세밀한 제어)

@Service
@RequiredArgsConstructor
public class PaymentService {

    private final ObservationRegistry registry;

    public PaymentResult processPayment(PaymentRequest request) {
        return Observation.createNotStarted("payment.process", registry)
            .lowCardinalityKeyValue("method", request.getMethod())   // 메트릭 태그
            .highCardinalityKeyValue("orderId", request.getOrderId()) // 트레이스 전용 태그
            .observe(() -> {
                // 실제 결제 처리 로직
                return doPayment(request);
            });
    }
}
lowCardinality VS highCardinality
  • lowCardinality: 값의 종류가 적은 태그 (예: method=CARD, status=SUCCESS) → 메트릭과 트레이스 모두에 추가

  • highCardinality: 값이 무한히 다양한 태그 (예: orderId=12345) → 트레이스에만 추가 (메트릭에 추가 시 카디널리티 폭발 위험)

  • 참고: spring


전체 관찰 가능성 스택 구성

Spring Boot 3.x + Micrometer를 운영 수준으로 활용하려면 아래 스택이 사실상 표준입니다: spring

[내 Spring Boot 앱]
  │
  ├── /actuator/prometheus ──→ [Prometheus] ──→ [Grafana] (메트릭 시각화)
  │
  └── Zipkin Reporter ────────→ [Zipkin / Tempo] (분산 트레이스)
                                      │
                               [Grafana에서 통합 대시보드]

Docker Compose로 로컬 모니터링 환경 구성:

version: '3.8'
services:
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml

  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin

  zipkin:
    image: openzipkin/zipkin
    ports:
      - "9411:9411"

prometheus.yml 스크레이프 설정:

scrape_configs:
  - job_name: 'spring-boot'
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['host.docker.internal:8080']

🗺️ 전체 아키텍처 조감도

[Spring Boot 3.x 앱]
  ├── GraalVM AOT
  │     └── 컨테이너 기동: ~80ms, 메모리 절감
  │
  └── Micrometer Observation
        ├── @Observed / ObservationRegistry
        │     ├── 메트릭 → Prometheus → Grafana
        │     └── 트레이스 → Zipkin/Tempo → Grafana
        └── MDC 자동 연동 → 로그에 traceId/spanId 자동 삽입
정보

MDC 자동 연동 덕분에 Grafana에서 메트릭 이상 감지 → 해당 트레이스 조회 → 연관 로그 검색까지 하나의 흐름으로 연결됩니다. baeldung


✅ 실전 적용 우선순위

단계 항목 난이도 효과
1 Actuator + Prometheus 메트릭 노출 즉시 모니터링 가능
2 @Observed로 핵심 비즈니스 메서드 계측 ⭐⭐ 서비스 병목 추적
3 Zipkin 분산 트레이스 연동 ⭐⭐ MSA 장애 원인 추적
4 Grafana 대시보드 구성 ⭐⭐⭐ 통합 관찰 환경 완성
5 GraalVM Native Image 전환 ⭐⭐⭐⭐ 서버리스/컨테이너 최적화
야근반장

야근반장

프로그래밍과 데이터 분석을 좋아하는 개발자입니다. 낮에도 밤에도 코딩하는 주경야근 라이프를 살고 있습니다.

GitHub