spring boot/actuator in spring boot

spring boot 3.x + actuator 파헤치기. 12. metrics 정리

Hello World Study 2023. 4. 14. 10:48

https://youtu.be/h3LqD0edGE0

이제까지 counter, gauge, timer 라는 metrics 에 대해 알아보았습니다. ( 그외에도 몇가지 metrics 가 있긴 합니다. https://micrometer.io/docs/concepts 참조 )

 

각각의 특성과 생성,등록 방법에 대해 정리해보도록 하겠습니다. 결론부터 말하자면 counter와 timer가 사용 패턴이 유사하고 gauge 만 다릅니다. 

 

annotation 지원

어노테이션이 지원되는 metric 은 counter 와 timer 입니다. 

아래처럼 측정하고 싶은 메서드 위에 @Counted 나 @Timer 를 적어주면 됩니다. 

@Counted
void method1() {}

@Timer
void method2() {}

AOP를 통해 counter 와 timer 가 동작하므로 둘 다 @Aspect와 @Around 관련 코드가 필요한데, 그걸 CountedAspect 와 TimedAspect 클래스에 구현되어 있습니다. 따라서 bean으로 아래처럼 등록해줘야 동작을 합니다. 

    @Bean
    public CountedAspect countedAspect(MeterRegistry meterRegistry) {
        return new CountedAspect(meterRegistry);
    }
    
    
    @Bean
    public TimedAspect timedAspect(MeterRegistry meterRegistry) {
        return new TimedAspect(meterRegistry);
    }

 

counter 나 timer 의 경우 메서드 호출 횟수, 메서드 수행 시간 과 같이 명확한 계산식이 있으므로 AOP 가 지원됩니다. 그러나 gauge 의 경우 cpu usage, 배송중인 물건갯수, 현 시점의 재고수량 과 같이 비지니스와 연관된 계산식이 필요하므로 AOP로 만들기가 어렵습니다.

 

 사용패턴 1 - metric에서 지원하는 측정 메서드호출

counter와 timer 는 builder 를 통해 meterResistry에 등록한후, increment() 나 record() 메서드를 통해 횟수 증가, 시간값 측정등을 할 수 있습니다.

Counter counter = Counter.builder("myHttpRequest").register(meterRegistry);
counter.increment();

Timer timer = Timer.builder("my.timer").register(meterRegistry);
timer.record(() -> {
		// do something
	});

increment() 나 record() 메서드 내부에는 횟수 증가, 시간값 측정 코드가 들어가 있습니다.

그러나, gauge 의 경우 위와 같이 사용할 수 없습니다. 앞서 언급했듯이 비지니스와 연관된 계산식이 필요하기 때문입니다.

 

사용패턴2 - app 에서 자체적으로 만든 계산식에 위임하기

위 사용패턴1은 측정하고 싶은 코드에 metric 코드를 넣어주는 방식입니다. 그런데 app 자체적으로 횟수, 시간 등을 계산하는 클래스가 있다고 가정해봅시다. 이럴 경우 app에서 자체적으로 제공하는 계산식에게 측정을 위임하고, 그 값을 metric에 등록만 하면 됩니다.

이를 위해 FunctionCounter 와 FunctionTimer 가 제공됩니다. Gauge 의 경우 계산식 자체가 비지니스와 연관되어 있기에 굳이 FunctionGauge 라는 타입을 추가로 제공할 필요없이 Gauge 자체가 계산식 위임하는 형식입니다.

아래 3가지 metric 코드를 보면 위임할 객체를 넘겨주고, 해당 객체의 특정 메서드를 호출해서 측정값을 가져오도록 되어 있습니다. 

FunctionCounter.builder("my.counter", manager, m -> {
                            return m.getCount();
                        })
                        .register(registry);
                        
                        
FunctionTimer functionTimer = FunctionTimer.builder("my.timer", myTimerManager,
                        m -> {
                            return m.getCount();
                        },
                        m -> {
                            return m.getTotalTime();
                        },
                        TimeUnit.SECONDS)
                		.register(registry);
                    
Gauge gauge = Gauge.builder("my.gauge", queueManager, queueManager -> {
                    return queueManager.getQueueSize();
                })
                .register(registry);

 

사용패턴3 - MeterBinder 이용

위의 사용패턴2 방식으로 metric 등록해도 되나 MeterBinder 를 이용해서도 등록이 가능합니다. MeterBinder 라는 인터페이스를 implement 해야 해서 코드가 약간 더 복잡할 뿐 위 사용패턴2와 거의 유사합니다.

    @Bean
    public MeterBinder myTimerWithMeterBinder(MyHttpRequestManagerWithoutMicrometer manager) {
        return new MeterBinder() {
            @Override
            public void bindTo(MeterRegistry registry) {
                FunctionCounter.builder(생략)
                        .register(registry);
            }
        };
    }
    
    @Bean
    public MeterBinder queueSize(QueueManager queueManager) {
        return new MeterBinder() {
            @Override
            public void bindTo(MeterRegistry registry) {
                Gauge.builder(생략)
                	.register(registry);
            }
        };
    }
    
    @Bean
    public MeterBinder myTimerWithMeterBinder(MyTimerManager myTimerManager) {
        return new MeterBinder() {
            @Override
            public void bindTo(MeterRegistry registry) {
                FunctionTimer functionTimer = FunctionTimer.builder(생략)
                        .register(registry);
            }
        };
    }

 

naming

Counter 나 Timer 등을 등록시에 my.counter 나 my.timer 등으로 이름을 지정해줬습니다.

FunctionCounter.builder("my.counter", 생략)
                        .register(registry);
                        
                        
FunctionTimer functionTimer = FunctionTimer.builder("my.timer", 생략)
                		.register(registry);
                    
Gauge gauge = Gauge.builder("my.gauge",생략)
                .register(registry);

my.counter 가 아닌 myCounter 와 같이 이름을 지정해도 됩니다. 그러나 일반적으로 java 패키지명처럼 naming을 하고 있습니다.

 

아래 그림처럼 diskFree 가 아닌 disk.free 와 같이 점(.)으로 구분을 해줍니다.

actuator 가 prometheus 와 연동시에는 prometheus에 맞는 포맷으로 json을 변경해주는데, 이때 점(.)을 특수문자로 인식해서 언더바(_) 로 변환해주기도 합니다.

따라서 실무에서는 아래처럼 점(.)으로 구분해서 네이밍하는걸 추천합니다.

my.test.queue.size,

my.test.queue.capacity, 

... 

 


metric 정리를 하니 머릿속이 좀 정리되고 자신감이 생기지 않나요?