마이크로미터: Micrometer

xellos·2023년 4월 7일
0

성능관리

목록 보기
3/4

들어가기 전: 이 글은 [인프런] 에서 '김영한님의 스프링 부트 - 핵심 원리와 활용'을 수강하고 일부를 요약한 글입니다.

https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%ED%95%B5%EC%8B%AC%EC%9B%90%EB%A6%AC-%ED%99%9C%EC%9A%A9/dashboard

1. 마이크로미터 역할

  • 마이크로미터는 애플리케이션 메트릭 퍼사드라고 불리는데, 애플리케이션의 메트릭(측정 지표)를 마이크로미터가 정한 표준 방법으로 모아서 제공해준다.
  • 즉, 마이크로미터가 추상화를 통해 구현체를 쉽게 갈아끼울 수 있도록 해두었다.
  • 보통은 스프링이 이런 추상화를 직접 만들어서 제공하지만, 마이크로미터라는 이미 잘 만들어진 추상화가 있기 때문에 스프링은 이것을 활용한다.
  • 개발자는 마이크로미터가 정한 표준 방법으로 메트릭을 전달하면 된다. 그리고 사용하는 모니터링 툴에 맞는 구현체를 선택하면 된다. 이후에 모니터링 툴이 변경되어도 해당 구현체만 변경하면 된다.

메트릭 확인

개발자가 애플리케이션에서 지표를 수집해서 이를 마이크로미터가 제공한느 표준방법에 따라 등록하면 actuator 에서 확인할 수 있다.

  • 마이크로미터는 다양한 지표 수집 기능을 만들어서 제공한다.
  • 스프링 부트 actuator 는 마이크로미터가 제공하는 지표 수집을 @AutoConfiguration 을 통해 자동으로 등록해준다.

1) metrics 엔드포인트

{
  "names": [ 
      "application.ready.time",
      "application.started.time",
      "disk.free",
      "disk.total",
      "hikaricp.connections",
      "hikaricp.connections.acquire",
      "hikaricp.connections.active",
      "hikaricp.connections.idle",
      "hikaricp.connections.max",
      "hikaricp.connections.usage",
      "http.server.requests",
      "http.server.requests.active",
      "jdbc.connections.active",
      "jdbc.connections.idle",
      "jdbc.connections.max",
      "jdbc.connections.min",
      "jvm.buffer.count",
      "jvm.buffer.memory.used",
      "jvm.memory.used",
      "jvm.memory.max",
      "logback.events",
      ...
  ] 
} 

2) 메트릭 확인

기본정보 확인

{
  "name": "jvm.memory.used",
  "description": "The amount of used memory",
  "baseUnit": "bytes",
  "measurements": [
    {
      "statistic": "VALUE",
      "value": 131172848
    } 
  ], 
  "availableTags": [
    {
      "tag": "area",
      "values": [
        "heap", 
        "nonheap" 
      ] 
    }, 
    {
      "tag": "id",
      "values": [
        "G1 Survivor Space",
        "Compressed Class Space",
        "Metaspace",
        "CodeCache",
        "G1 Old Gen", 
        "G1 Eden Space"
      ] 
    } 
  ]
}

상세정보 확인

위에서 다음과 같은 항목을 찾아볼 수 있다.

  • tag:area, values[heap, nonheap]
  • tag:id, values[G1 Survivor Space, ...]

그렇다면 이번에는 metrics 내부에 Tag 정보를 필터링해서 정보를 확인해보자.

tag 를 사용하면 힙 메모리, 힙이 아닌 메모리로 분류해서 데이터를 확인할 수 있다.


3. 다양한 메트릭

마이크로미터와 actuator 가 기본적으로 제공하는 다양한 메트릭을 확인해보자.

JVM 메트릭

JVM 관련 메트릭을 제공한다. jvm. 으로 시작한다.

  • 메모리 및 버퍼 풀 세부 정보
  • 가비지 수집 관련 통계
  • 쓰레드 활용
  • 로드 및 언로드된 클래스 수
  • JVM 버전 정보
  • JIT 컴파일 시간

시스템 메트릭

시스템 메트릭을 제공한다. system., process., disk. 으로 시작한다.

  • CPU 지표
  • 파일 디스크립터 메트릭
  • 가동 시간 메트릭
  • 사용 가능한 디스크 공간

애플리케이션 시작 메트릭

애플리케이션 시작 시간 메트릭을 제공한다.

  • application.started.time
  • application.ready.time

스프링은 내부에 여러 초기화 단계가 있고 각 단계별로 내부에서 애플리케이션 이벤트를 발행한다.

  • ApplicationStartedEvent: 스프링 컨테이너가 완전히 실행된 상태이다. 이후에 커맨드라인 러너가 호출된다.
  • ApplicationReadyEvent: 커맨드라인 러너가 실행된 이후에 호출된다.

스프링 MVC 메트릭

스프링 MVC 컨트롤러가 처리하는 모든 요청을 다룬다.
메트릭 이름: http.server.requests

TAG 를 사용해서 다음 정보를 분류해서 확인할 수 있다.

  • uri
  • method
  • status
  • exception
  • outcome

데이터 소스 메트릭

커넥션 풀에 관한 메트릭을 확인할 수 있따.

  • jdbc.connections 으로 시작한다.
  • 최대 커넥션
  • 최소 커넥션
  • 활성 커넥션
  • 대기 커넥션 수 등을 확인할 수 있다.

로그 메트릭

logback.events: logback 로그에 대한 메트릭을 확인할 수 있다.

  • trace, debug,info, warn, error 각각의 로그 레벨에 따른 로그 수를 확인할 수 있다.
  • 예를 들어, error 로그 수가 급격히 높아진다면 위험한 신호로 받아들일 수 있다.

기타

  • HTTP 클라이언트 메트릭
  • 캐시 메트릭
  • 작업 실행과 스케줄 메트릭
  • 스프링 데이터 리포지토리 메트릭
  • 몽고 DB 메트릭
  • 레디스 메트릭

참고: https://docs.spring.io/spring-boot/docs/current/reference/html/actuator.html#actuator.metrics.supported


4. 사용자 정의 메트릭: 애노테이션 방식

마이크로미터를 사용하면, 애플리케이션 내에서 비즈니스 모니터링에 알맞은 메트릭을 자유롭게 추가하고 actuator/metrics 에서 확인할 수 있다.

사용자가 정의할 수 있는 메트릭은 3가지로 다음과 같다.

  • Counter: 수가 절대로 줄지 않는 지표 수집에 적합하다. (ex. 요청수)
  • Timer: 특정 로직의 작동 시간을 수집할 수 있다. 메서드와 클래스 모두에 적용할 수 있다.
  • Gauge: 특정 범위 내에서 늘어나거나 줄어드는 지표를 수집하는데 적합하다. (ex. CPU 사용율)

1) @Counted: Counter

@Counted 를 사용하면 result, exception, method, class 같은 다양한 tag 를 자동으로 적용한다.

애노테이션 적용

  • @Counted 애노테이션 측정을 원하는 메서드에 적용한다.
  • 그리고 메트릭 이름을 지정하면 된다.
  • 참고로 이렇게 사용하면 tagmethod 를 기준으로 분류해서 적용한다.
@slf4j
public class OrderService implements OrderService {
	private AtomicInteger stock = new AtomicInteger(100);
    
    @Override
    @Counted("my.order")
    public void order() {
    	log.info("주문");
        stock.decrementAndGet();
    }
    
    @Override
    @Counted("my.order")
    public void cancel() {
    	log.info("취소");
        stock.incrementAndGet();
    }
    
    @Override
    public AtomicInteger getStock() {
    	return stock;
    }
}

설정 클래스 구성

  • CountedAspect 를 등록하면 @Counted 를 인지해서 Counter를 사용하는 AOP 를 적용한다.
  • 주의: CountedAspect 를 빈으로 등록하지 않으면 @Counted 관련 AOP 가 동작하지 않는다.
@Configuration
public class OrderConfig {

	@Bean
    public CountedAspect countedAspect(MeterRegistry registry) {
    	return new CountedAspect(registry);
    }
}

2) @Timed: Timer

시간을 측정하는데 사용된다. 카운터와 유사한데, Timer 를 사용하면 실행시간도 함께 측정할 수 있다. 따라서, 다음과 같은 내용을 한 반에 측정할 수 있다.

  • seconds_count: 누적 실행 수

  • seconds_sum: 실행시간의 합

  • seconds_max: 최대 실행 시간(가장 오래 걸린 실행시간 (가장 오래걸린 실행시간). 내부에 타임윈도우라는 개념이 있어서 1 ~ 3분마다 최대 실행시간이 다시 계산된다.

  • 애노테이션 적용
    class 에 적용하면 해당 타임의 모든 public 메서드 타이머가 적용된다.

@Slf4j
@Timed("my.order")
public class OrderService implements OrderService {
	private AtomicInteger stock = new AtomicInteger(100);
    
    @Override
    public void order() {
    	log.info("주문");
        stock.decrementAndGet();
        sleep(500);
    }
    
    @Override
    public void cancel() {
    	log.info("취소");
        stock.incrementAndGet();
        sleep(200);
    }
    
    private static void sleep(int l) {
    	try {
        	Thread.sleep(l + new Random().nextInt(200));
        } catch (InterruptedException e) {
        	throw new RuntimeException(e);
        }
    }
    
    @Override
    public AtomicInteger getStock() {
    	return stock;
    }
}
  • 설정 클래스 구성
@Configuration
public class ORderConfig {

	@Bean
    public TimedAspect timedAspect(MeterRegistry registry) {
    	return new TimedAspect(registry);
    }
}

3) Gauge

특징

  • 게이지는 임의로 오르내릴 수 있는 단일 숫자 값을 나타내느 메트릭
  • 값의 현재 상태를 보는데 사용
  • 값이 증가하거나 감소할 수 있음

참고: 카운터와 게이지를 구분할 때는 값이 감소할 수 있는가를 고민해보면 도움이 된다.


코드

  • 적용 1
    --- 카운터와 다르게 게이지는 무언가를 누적할 필요도 없고, 딱 현재 시점의 값을 보여주면 된다. 따라서 측정 시점에 현재 값을 반환한다.
    --- 애플리케이션을 실행시키면 stock gauge call 로그가 주기적으로 남는것을 확인할 수 있다.
    --- 게이지를 확인하는 함수는 외부에서 메트릭을 확인할 때 호출된다.
@Configuration
public class StockConfig {

	@Bean
    public MyStockMetric myStockMetric(
    	OrderServcie orderService,
        MeterRegistry registry
    ) {
    	return new MyStockMetric(orderService.getStock(), registry);
    }
    
    @Slf4j
    static class MyStockMetric {
    	private OrderService orderService;
        private MeterREgistry registry;
        
        public MyStockMetric(
        	OrderService orderService, 
            MeterRegistry registry
        ) {
        	this.orderService = orderService;
            this.registry = registry;
        }
        
        @PostContstruct
        public void init() {
        	Gauge.builder("my.stock", orderService, serivce -> {
            	log.info("stock gauge call");
                return service.getStock().get();
            }).registry(registry);
        }
    }
    
}

  • 적용 2: 간단한 방법
@Slf4j
@Configuration
public class StockConfig {
	
    @Bean
    public MeterBuilder stockSize(OrderService orderService) {
    	return registry -> Gauge.bulider("my.stock", orderService, service -> {
        	log.info("stock gauge call");
            return service.getStock().get();
        }).registry(registry):
    }
}

0개의 댓글