Prometheus와 Grafana 기반으로 커스텀 메트릭 모니터링 고도화하기
Custom Metrics Monitoring in Spring Using Prometheus and Grafana
커스텀 메트릭은 애플리케이션의 성능을 정밀하게 모니터링하고 개선하는 데 필수적입니다. Prometheus와 Grafana를 이용하면 이러한 메트릭을 효과적으로 수집하고 시각화할 수 있습니다. 이를 지원하기 위해 Spring은 Micrometer를 통해 AOP(Aspect-Oriented Programming) 기반의 사용자 정의 메트릭 추가 기능을 제공합니다. 이 기능을 이용하면, 사용자 정의 메트릭을 쉽게 추가할 수 있으며, 이러한 데이터를 Grafana 대시보드와 통합함으로써 모니터링 작업의 효율성을 크게 높일 수 있습니다.
Prerequisites
Prometheus 및 Grafana와의 연동 설정에 대한 자세한 내용은 이 글에서 다루지 않으며, 자세한 내용은 Prometheus & Grafana 도입하기에서 확인할 수 있습니다. Spring Boot 프로젝트를 생성하고 다음과 같은 종속성을 추가합니다:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-aop'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
runtimeOnly 'io.micrometer:micrometer-registry-prometheus'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
프로젝트 주요 구성은 다음과 같습니다:
- Java 21
- Spring Boot 3.3.0
- Gradle 8
- Dependencies:
- Prometheus: Micrometer에서 제공하는 라이브러리를 통해 Prometheus에 메트릭을 등록할 수 있습니다.
- Spring Boot Actuator: 애플리케이션의 다양한 상태 및 메트릭을 노출하는 데 사용됩니다.
- Spring Boot Starter AOP: AOP 기반의 메트릭을 설정하기 위해 필요한 종속성입니다. 만약, Spring Data JPA와 같이 이미 AOP를 사용하는 다른 라이브러리를 포함하고 있다면, 이 종속성을 별도로 추가할 필요가 없습니다.
- Spring Boot Starter Web: 간단한 REST API 구현을 위해 필요한 종속성입니다. 이를 통해 메트릭 테스트를 위한 엔드포인트를 쉽게 만들 수 있습니다.
- Lombok: 코드의 간결함을 위해 추가합니다.
Counter Metrics
Prometheus의 카운터(Counter) 메트릭은 오직 증가하는 값을 기록하는 데 사용되는 단순한 수집 도구입니다.
Micrometer에서 제공하는 @Counted
어노테이션은 카운터 메트릭을 간편하게 프로젝트에 AOP 기반으로 적용할 수 있도록 하는 기능을 제공합니다. 이 어노테이션은 메서드가 호출될 때마다 지정된 이름의 카운터 메트릭을 증가시킵니다. @Counted
어노테이션이 동작하도록 하기 위해서는 MeterRegistry
객체를 주입받은 CountedAspect
객체를 빈으로 등록해야 합니다.
@Slf4j
@Configuration
public class OrderConfig {
@Bean
public CountedAspect countedAspect(MeterRegistry registry) {
return new CountedAspect(registry);
}
}
CountedAspect
: 이 클래스는 AOP를 사용하여@Counted
가 적용된 메서드를 가로채고, 해당 메서드가 호출될 때마다 카운터 메트릭을 증가시킵니다.CountedAspect
는MeterRegistry
를 통해 Micrometer와 통합되어 메트릭을 관리합니다.MeterRegistry
: 이 객체는 메트릭 데이터를 수집하고 저장하는 중심 허브입니다. Micrometer의 핵심 구성 요소로, 다양한 모니터링 시스템(Prometheus, Graphite 등)과 통합되어 메트릭을 노출합니다.MeterRegistry
는 다양한 메트릭 타입을 지원하며, 각 메트릭을 효과적으로 관리할 수 있도록 도와줍니다.
Spring Bean으로 등록한 CountedAspect
를 활용하여 REST API에 카운터 메트릭을 연동하여 Grafana 패널을 새로 추가해보겠습니다. 먼저, 컨트롤러를 구현합니다. 이 컨트롤러는 재고를 줄이는 주문 API와 주문 취소로 재고를 다시 회수하는 API를 포함합니다. 단순히 서비스를 호출하고 문자열을 반환하도록 구성되었습니다:
@Slf4j
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService service;
@PostMapping("/orders/counted")
public String orderCounter() {
service.orderCounted();
return "order counter";
}
@DeleteMapping("/orders/counted/{orderId}")
public String cancelCounter(@PathVariable Long orderId) {
service.cancelCounted(orderId);
return "cancel counter";
}
}
다음은 컨트롤러에서 위임된 요청을 처리하는 비즈니스 로직을 구현합니다. 여기서는 AtomicInteger
를 활용하여 스레드 안전성을 보장하고, 재고 수량을 증감시킵니다. 그리고 해당 메서드에 카운터 메트릭을 수집하기 위해 @Counted(name)
어노테이션을 선언합니다:
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private AtomicInteger stock = new AtomicInteger(100);
@Counted("waves.order")
public void orderCounted() {
log.info("orderCounted: Successfully created order - stockCount={}", stock);
stock.decrementAndGet();
}
@Counted("waves.order")
public void cancelCounted(Long orderId) {
log.info("cancelCounted: Successfully canceled order - stockCount={}, orderId={}", stock, orderId);
stock.incrementAndGet();
}
}
@Counted
: 이 어노테이션은 메서드 호출 시마다 카운터 메트릭을 증가시킵니다. 메서드에@Counted
를 추가하면, 해당 메서드가 실행될 때마다 지정된 이름의 카운터가 1씩 증가합니다. 예를 들어, 어노테이션에서 지정한waves.order
라는 이름을 가진 카운터 메트릭이 생성되고,orderCounted()
메서드가 호출될 때마다 이 카운터의 값이 증가하게 됩니다. 이 메트릭은 주문이 얼마나 자주 생성되고 취소되는지를 추적하는 데 유용합니다.
이렇게 @Counted
어노테이션을 추가하기만 하면 카운터 메트릭 수집이 연동됩니다. 주문 생성 API를 요청하여 메트릭이 정상적으로 수집되는지 테스트합니다:
POST /orders/counted HTTP/1.1
Host: localhost:8080
Connection: close
User-Agent: RapidAPI/4.2.2 (Macintosh; OS X/14.5.0) GCDHTTPRequest
Content-Length: 0
메트릭 이름을 활용하여 수집된 데이터를 조회하면 다음과 같은 JSON 응답을 받을 수 있습니다:
{
"name": "waves.order",
"measurements": [
{
"statistic": "COUNT",
"value": 1.0
}
],
"availableTags": [
{
"tag": "result",
"values": [
"success"
]
},
{
"tag": "exception",
"values": [
"none"
]
},
{
"tag": "method",
"values": [
"orderCounted"
]
},
{
"tag": "class",
"values": [
"app.catsriding.prometheus.order.OrderService"
]
}
]
}
Prometheus 형식으로 변환된 카운터 메트릭에서 점은 언더스코어로 대체되며, counter
는 total
로 변환되어 **_total
형식의 이름을 갖게 됩니다.
이제 이 데이터를 Grafana의 패널로 시각화하여 실시간으로 모니터링할 수 있습니다. Grafana에서 Prometheus 데이터 소스를 추가한 후, 대시보드를 만들어 waves_order_total
메트릭을 시각화하면 됩니다. 이를 통해 주문 생성 및 취소 이벤트의 빈도를 한눈에 파악할 수 있습니다.
Prometheus에서 커스텀 메트릭을 수집하려면 해당 로직이 최소한 한 번은 실행되어야 합니다. 수집한 데이터를 효과적으로 분석하기 위해 PromQL 쿼리를 다음과 같이 작성할 수 있습니다:
increase(waves_order_total[1m])
이렇게 하면 지난 1분 동안의 주문 수를 증가분으로 보여줍니다. 이를 통해 주문이 얼마나 자주 발생하는지 실시간으로 모니터링할 수 있습니다. Grafana에서 해당 패널을 설정하여 실시간으로 데이터를 시각화하고 분석할 수 있습니다.
Histogram Metrics
Prometheus 히스토그램(Histogram) 메트릭은 요청의 수, 처리 시간 등의 분포를 기록하는 데 사용됩니다. 주로 요청의 처리 시간이나 응답 시간을 모니터링하는 데 유용합니다.
Micrometer에서 제공하는 @Timed
어노테이션은 히스토그램 메트릭을 간편하게 프로젝트에 AOP 기반으로 적용할 수 있도록 하는 기능을 제공합니다. 이 어노테이션은 메서드가 호출될 때마다 지정된 이름의 히스토그램 메트릭을 기록합니다. @Timed
어노테이션이 동작하도록 하기 위해서는 MeterRegistry
객체를 주입받은 TimedAspect
객체를 빈으로 등록해야 합니다.
@Slf4j
@Configuration
public class OrderConfig {
@Bean
public TimedAspect timedAspect(MeterRegistry registry) {
return new TimedAspect(registry);
}
}
TimedAspect
: 이 클래스는 AOP를 사용하여@Timed
가 적용된 메서드를 가로채고, 해당 메서드가 호출될 때마다 히스토그램 메트릭을 기록합니다.TimedAspect
는MeterRegistry
를 통해 Micrometer와 통합되어 메트릭을 관리합니다.
이번에는 Spring Bean으로 등록한 TimedAspect
를 활용하여 REST API에 히스토그램 메트릭을 연동하여 Grafana 패널을 직접 추가해보겠습니다. 먼저, 컨트롤러를 구현하는데 이전과 로직은 동일하며 API URL만 다르게 하여 충돌을 피합니다.
@Slf4j
@RestController
@RequiredArgsConstructor
public class OrderController {
private final OrderService service;
@PostMapping("/orders/timed")
public String orderHistogram() {
service.orderTimed();
return "order histogram";
}
@DeleteMapping("/orders/timed/{orderId}")
public String cancelHistogram(@PathVariable Long orderId) {
service.cancelTimed(orderId);
return "cancel histogram";
}
}
요청을 처리하는 비즈니스 로직은 처리 시간을 측정하기 위해 기존 로직에 적절하게 Thread.sleep()
을 통해 지연이 발생하도록 합니다. 그리고 해당 메서드에 히스토그램 메트릭을 수집하기 위해 @Timed(name)
어노테이션을 추가합니다:
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private AtomicInteger stock = new AtomicInteger(100);
@Timed("waves.order")
public void orderTimed() {
long start = System.currentTimeMillis();
stock.decrementAndGet();
sleep(500);
long end = System.currentTimeMillis();
log.info("orderTimed: Successfully created order - stockCount={}, executionTime={}ms", stock, end - start);
}
@Timed("waves.order")
public void cancelTimed(Long orderId) {
long start = System.currentTimeMillis();
stock.incrementAndGet();
sleep(800);
long end = System.currentTimeMillis();
log.info("cancelTimed: Successfully canceled order - stockCount={}, orderId={}, executionTime={}ms",
stock,
orderId,
end - start);
}
private static void sleep(int waitTimeBound) {
try {
Thread.sleep(new Random().nextInt(waitTimeBound));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
@Timed
: 이 어노테이션은 메서드의 호출마다 세 가지 값이 생성되는 메트릭을 만들어냅니다. 이 값들은 주문 처리 시간과 응답 시간을 모니터링하는데 효과적입니다.count
: 메서드가 호출된 총 횟수를 기록합니다.sum
: 메서드 호출에 소요된 총 시간을 기록합니다.max
: 이 메서드는 가장 오래 걸린 호출의 시간을 기록합니다. 이는 애플리케이션의 전체 실행 시간이 아니라, Prometheus가 특정 설정된 기간 동안의 호출 중에서 최고 시간을 나타내는 값입니다.
이렇게 @Timed
어노테이션을 추가하면 히스토그램 메트릭 수집이 연동됩니다. 주문 생성 API를 요청하여 메트릭이 정상적으로 수집되는지 테스트합니다:
POST /orders/timed HTTP/1.1
Host: localhost:8080
Connection: close
User-Agent: RapidAPI/4.2.2 (Macintosh; OS X/14.5.0) GCDHTTPRequest
Content-Length: 0
메트릭 이름을 활용하여 수집된 데이터를 조회하면 다음과 같이 수집 항목이 추가된 JSON 응답을 받을 수 있습니다:
{
"name": "waves.order",
"baseUnit": "seconds",
"measurements": [
{
"statistic": "COUNT",
"value": 101.0
},
{
"statistic": "TOTAL_TIME",
"value": 15.530242081
},
{
"statistic": "MAX",
"value": 0.0
}
],
"availableTags": [
{
"tag": "result",
"values": [
"success"
]
},
{
"tag": "exception",
"values": [
"none"
]
},
{
"tag": "method",
"values": [
"cancelCounted",
"orderTimed",
"orderCounted",
"cancelTimed"
]
},
{
"tag": "class",
"values": [
"app.catsriding.prometheus.order.OrderService"
]
}
]
}
Prometheus 형식으로 변환된 히스토그램 메트릭은 **_seconds_count
, **_seconds_sum
, **_seconds_max
와 같은 형식으로 변환됩니다.
이제 이 데이터를 Grafana의 패널로 시각화하여 실시간으로 모니터링할 수 있습니다. 새로운 패널을 추가하여 waves_order_seconds_max
메트릭을 시각화합니다. 이를 통해 호출 중 가장 오랜 시간이 걸린 호출의 시간을 확인할 수 있습니다.
PromQL 쿼리는 다음과 같이 작성할 수 있습니다:
waves_order_seconds_max
그리고 다음은 waves_order_seconds_sum
과 waves_order_seconds_count
두 개의 메트릭을 사용하여 메서드의 평균 처리 시간을 시각화합니다.
총 처리 시간을 호출 횟수로 나누어 평균을 구합니다. 이를 위한 PromQL 쿼리는 다음과 같이 작성하실 수 있습니다:
increase(waves_order_seconds_sum[1m]) / increase(waves_order_seconds_count[1m])
이를 통해 특정 메서드의 평균 처리 시간을 실시간으로 모니터링할 수 있습니다. 이 정보는 시스템 성능을 모니터링하고 백엔드 서버의 응답 시간 변화를 추적하는 데 매우 유용합니다. 이렇게 하면 시스템의 병목 현상을 식별하고 성능을 최적화하는 데 도움을 줄 수 있습니다.
Gauge Metrics
Prometheus 게이지(Gauge) 메트릭은 현재 상태를 나타내는 값으로, 증가와 감소가 모두 가능한 값을 기록하는 데 사용됩니다. 주로 시스템의 상태나 리소스 사용량을 추적하는 데 유용합니다.
현재 재고 수량을 추적하는 게이지 메트릭을 추가해보겠습니다. 먼저, OrderService
클래스에 checkStock()
메서드를 추가하여 현재 재고 수량을 반환하도록 합니다.
@Slf4j
@Service
@RequiredArgsConstructor
public class OrderService {
private AtomicInteger stock = new AtomicInteger(100);
public int checkStock() {
return stock.get();
}
...
}
그 다음, OrderConfig
클래스에서 게이지 메트릭을 등록하는 설정을 추가합니다.
@Slf4j
@Configuration
public class OrderConfig {
@Bean
public MeterBinder stockSize(OrderService service) {
return registry -> Gauge.builder("waves.stock", service, OrderService::checkStock)
.register(registry);
}
...
}
MeterBinder
: 이 인터페이스는 특정 메트릭을 등록하는 데 사용됩니다. 여기서는Gauge.builder
를 사용하여 게이지 메트릭을 생성하고,OrderService
의checkStock
메서드를 통해 현재 재고 수량을 추적합니다.MeterBinder
를 통해 등록된 메트릭은MeterRegistry
에 의해 관리됩니다.
이렇게 설정하면 waves.stock
이라는 이름의 게이지 메트릭이 등록됩니다. 이 메트릭은 OrderService
의 checkStock
메서드에서 반환된 재고 수량을 기반으로 현재 재고 상태를 나타냅니다.
이제 이 데이터를 Grafana의 패널로 시각화하여 실시간으로 모니터링할 수 있습니다. 새로운 패널을 추가하여 waves_stock
메트릭을 시각화합니다. 이를 통해 현재 재고 수량을 한눈에 확인할 수 있습니다.
PromQL 쿼리는 다음과 같이 작성할 수 있습니다:
waves_stock
게이지 메트릭은 시스템의 다양한 상태를 모니터링하고 성능을 최적화하는 데 중요한 역할을 합니다. 예를 들어, 메모리 사용량, CPU 사용률, 활성 사용자 수, 큐의 길이, 오픈 파일 디스크립터 수 등을 추적할 수 있습니다. Grafana에서 해당 패널을 설정하여 실시간으로 데이터를 시각화하고 분석할 수 있습니다.
- Spring
- Infrastructure
- Monitoring