Spring

모니터링 메트릭 활용

경딩 2025. 3. 25. 14:28
  • 마이크로미터를 사용해서 메트릭을 직접 등록하는 방법을 알아보자

 

MeterRegistry

마이크로미터 기능을 제공하는 핵심 컴포넌트

스프링을 통해서 주입 받아서 사용하고, 이곳을 통해서 카운터, 게이지 등을 등록한다.

 

카운터

  • 단조롭게 증가하는 단일 누적 측정항목
    • 단일값
    • 보통 하나씩 증가
    • 누적이므로 전체 값을 포함(total)
    • 프로메테우스에서는 일반적으로 카운터의 이름 마지막에 _tatal 을 붙여서 my_intersetstock_tatol 과 같이 표현함
  • 값을 증가하거나 0으로 초기화 하는 것만 가능
  • 예 ) HTTP 요청수

 

메트릭 등록 - 예제 만들기

관심 주식 등록 서비스에 카운터 메트릭을 적용해보자!

package com.flab.mars.domain.service;

import com.flab.mars.db.entity.InterestStockEntity;
import com.flab.mars.db.entity.PriceDataEntity;
import com.flab.mars.db.entity.StockInfoEntity;
import com.flab.mars.db.repository.InterestStockRepository;
import com.flab.mars.db.repository.PriceDataRepository;
import com.flab.mars.db.repository.StockInfoRepository;
import com.flab.mars.domain.StockCodeValidator;
import com.flab.mars.domain.vo.TokenInfoVO;
import com.flab.mars.domain.vo.response.InterestStockVO;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class InterestStockService {

    private final MeterRegistry registry;

    private final InterestStockRepository interestStockRepository;
    private final StockInfoRepository stockInfoRepository;
    private final PriceDataRepository priceDataRepository;
    private final StockCodeValidator stockCodeValidator;

    @Transactional
    public Long registerInterestStock(Long userId, String stockCode, TokenInfoVO token) {

        Counter.builder("my.interestStock")
                .tag("class", this.getClass().getName())
                .tag("method", "register")
                .description("registerInterestStock")
                .register(registry).increment();

        // DB 에 해당 주식 코드 정보가 없는 경우
        StockInfoEntity stockInfoEntity = stockInfoRepository.findByStockCode(stockCode)
                .orElseGet(() -> {
                    String stockName = stockCodeValidator.validateAndGetStockName(stockCode, token);
                    // 해당 주식이 저장되어 있지 않은 경우 insert
                    return stockInfoRepository.save(new StockInfoEntity(stockCode, stockName));
        });

        // 중복 관심 종목 등록 방지
        Optional<InterestStockEntity>  existingInterestStock  = interestStockRepository.findByMemberIdAndStockInfo(userId, stockInfoEntity);
        if (existingInterestStock.isPresent()) {
            return existingInterestStock.get().getId(); // 중복된 관심 종목 엔티티의 아이디를 리턴
        }
        InterestStockEntity interestStockEntity = InterestStockEntity.builder()
                .memberId(userId)
                .stockInfo(stockInfoEntity)
                .build();

        interestStockRepository.save(interestStockEntity);

        return interestStockEntity.getId();
    }

}

 

실행

http://localhost:8080/api/interest-stocks

(각각 실행해야 메트릭이 등록된다.)

 

 

액츄에이터 메트릭 확인

 

최소 한번은 호출을 해야 메트릭 목록에 나타난다

 

 

프로메테우스 포멧 메트릭 확인

localhost:8080/actuator/prometheus

프로메테우스 포맷으로 확인해보자.

  • 카운터이기 때문에 마지막에 _total 이 붙은 것을 확인할 수 있다.
    • 프로메테우스는 . -> _ 로 변경한다.
    • 메트릭 이름이 my.interestStock ->  my_ interestStock _total 로 변경된 것을 확인할 수 있다.
    • method  라는 tag, 레이블을 기준으로 데이터가 분류되어 있다.

그라파나 등록 - 관심 주식 등록 수

등록 수 그래프를 추가해보자.

 

  • Panel options 
    • Title: 관심 주식 등록 수
  • PromQL 
    • increase(my_interestStock_total{method="register"}[1m])

 

참고 : 카운터는 계속 증가하기 때문에 특정 시간에 얼마나 증가했는지 확인하려면  increase() , rate() 같은 함수와 함께 사용하는 것이 좋다.

 

 


 

메트릭 등록 2 - Counted

코드 개선

InterestStockService 에 메트릭을 계산하는 코드가 추가되었다.

Service 는 비즈니스 로직만 남겨두는 것이 좋다. 어떻게 해결할 수 있을까?

스프링에서 자주 쓰이는 aop 를 활용해보자

직접 필요한 AOP 를 만들어서 적용하여도 좋지만 마이크로미터는 이런 상황에 맞추어 필요한 AOP 구성 요소를 이미 다 만들어 두었다.

 

 @Counted("my.interestStock")
    @Transactional
    public Long registerInterestStock(Long userId, String stockCode, TokenInfoVO token) {

        // DB 에 해당 주식 코드 정보가 없는 경우
        StockInfoEntity stockInfoEntity = stockInfoRepository.findByStockCode(stockCode)
                .orElseGet(() -> {
                    String stockName = stockCodeValidator.validateAndGetStockName(stockCode, token);
                    // 해당 주식이 저장되어 있지 않은 경우 insert
                    return stockInfoRepository.save(new StockInfoEntity(stockCode, stockName));
        });

        // 중복 관심 종목 등록 방지
        Optional<InterestStockEntity>  existingInterestStock  = interestStockRepository.findByMemberIdAndStockInfo(userId, stockInfoEntity);
        if (existingInterestStock.isPresent()) {
            return existingInterestStock.get().getId(); // 중복된 관심 종목 엔티티의 아이디를 리턴
        }
        InterestStockEntity interestStockEntity = InterestStockEntity.builder()
                .memberId(userId)
                .stockInfo(stockInfoEntity)
                .build();

        interestStockRepository.save(interestStockEntity);

        return interestStockEntity.getId();
    }

` @Counted ` 애노테이션을 측정을 원하는 메서드에 적용한다. 관심 주식 등록에 적용했다.

그리고 메트릭 이름을 지정하면 된다. 여기서는   my.intereststock 를 적용했다.

참고로 이렇게 사용하면 tag 에 method 를 기준으로 분류해서 적용한다.

 

package com.flab.mars.config;


import io.micrometer.core.aop.CountedAspect;
import io.micrometer.core.instrument.MeterRegistry;

@Configuration
public class RootConfig {

    @Bean
    public CountedAspect countedAspect(MeterRegistry registry) {
        return new CountedAspect(registry); // CountedAspect 를 등록하면  @Counted 를 인지해서 Counter 를 사용하는 AOP 를 적용한다.
    }
}
  • CountedAspect ` 를 등록하면 ` @Counted 를 인지해서  Counter 를 사용하는 AOP를 적용한다
  • CountedAspect 를 빈으로 등록하지 않으면 @Counted 관련 AOP 가 동작하지 않는다.

 

액츄에이터 메트릭 확인

http://127.0.0.1:8080/actuator/metrics/my.intereststock

프로메테우스 포멧 메트릭 확인

http://127.0.0.1:8080/actuator/prometheus

 

그라파냐 대시보드 확인

메트릭 이름과 tag 가 기존과 같으므로 같은 대시보드에서 확인할 수 있다.