본문 바로가기

Spring Boot/Cache

캐시 모니터링과 문제 해결 : Spring Boot Actuator를 활용한 모니터링

 - Spring Boot Actuator를 사용하여 애플리케이션의 캐시 상태를 실시간으로 모니터링 하는법

 - 캐시 관련 엔드포인트(/actuator/caches, /actuator/metrics/cache.*) 의 활용법??

 - Micrometer를 이용해 캐시 통계를 수집하고 분석하는법

 - 커스텀 지표(Metrics)를 정의하여 비즈니스 맞춤형 모니터링 구성하기

 - 실무에서 캐시 문제를 조기에 탐지하고 효율적으로 관리할 수 있는 법

 


Spring Boot Actuator를 활용한 모니터링

Spring Boot Actuator는 운영 환경에서의 시스템 상태를 실시간으로 관찰할 수 있도록 도와주는 강력한 모니터링 도구

Actuator는 캐시의 Hit/Miss 비율, 캐시 크기, 만료 정책 등의 캐시 통계 정보까지 노출할 수 있음

 

 

1. Actuator의 개념

Spring Actuator

Actuator는 Micrometer를 기반으로 작동하며, 다양한 모니터링 툴과 연동할 수 있음

이를 통해 캐시 동작을 포함한 애플리케이션의 내부 상태를 시각화할 수 있음

 

 

2. Actuator 활성화

Spring Boot 프로젝트에서 Actuator를 사용하려면 의존성을 추가해야함

build.gradle

    // spring actuator
    implementation 'org.springframework.boot:spring-boot-starter-actuator'

    // 선택 : Prometheus 연동 시
    implementation 'io.micrometer:micrometer-registry-prometheus'

 

이후 application.yml에 캐시 관련 엔드포인트를 활성화함

application.yml

spring:
  application:
    name: actuatorTest

management:
  endpoints:
    web:
      exposure:
        include: health, info, metrics, caches
  endpoint:
    caches:
      enabled: true # 곧 사용하지 않는 프로퍼티임
  metrics:
    enable:
      cache: true # 자동입력 안됨

 

include: caches를 추가해줘야 /actuator/caches 엔드포인트가 노출된다고 함

 


캐시 관련 엔드포인트

Spring Boot Actuator는 캐시와 관련된 엔드포인트들을 제공하고 있음

엔드포인트 설명
/actuator/caches 현재 등록된 모든 캐시 이름과 상태 정보를 조회함
/actuator/caches/{cacheName} 특정 캐시의 내부 상태를 확인함
/actuator/metrics/cache.gets 캐시 Hit/Miss 통계를 조회함
/actuator/metrics/cache.puts 캐시 저장(Put) 이벤트 통계를 조회함
/actuator/metrics/cache.evictions 캐시 항목 제거(Eviction) 횟수를 조회함

 

 

1. /actuator/caches 예시 응답

json 형태임

http://localhost:8080/actuator/caches

{
    "cacheManagers": {
        "caffeineCacheManager": {
            "caches": {
                "productCache": {
                    "target": "com.github.benmanes.caffeine.cache.BoundedLocalCache$BoundedLocalManualCache"
                },
                "userCache": {
                    "target": "com.github.benmanes.caffeine.cache.BoundedLocalCache$BoundedLocalManualCache"
                }
            }
        }
    }
}

 

이 결과는 현재 등록된 캐시(userCache, productCache)의 이름과 타입을 보여줌

 


캐시 통계 수집

Spring Boot Actuator는 Micrometer랑 결합되어 캐시 관련 통계를 자동으로 수집하게 해줌

 

build.gradle

    // spring actuator
    implementation 'org.springframework.boot:spring-boot-starter-actuator'

    // 선택 : Prometheus 연동 시
    implementation 'io.micrometer:micrometer-registry-prometheus'

    // cache
    implementation 'org.springframework.boot:spring-boot-starter-cache'

    // caffeine
    implementation 'com.github.ben-manes.caffeine:caffeine'

 

 

1. Caffeine Cache에서 통계 활성화

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class CacheConfig {

    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("userCache, productCache");
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .maximumSize(1000)
                .recordStats()
                .expireAfterWrite(10, TimeUnit.MINUTES));
        return cacheManager;
    }
}

 

recordStats()를 활성화하면 Hit / Miss, Eviction 등 캐시 관련 통계가 자동으로 수집됨

 

 

2. 캐시 통계 조회 예시

/actuator/metrics/cache.gets 요청시 결과를 얻을 수 있다고 함

물론 json 형태

{
    "name": "cache.gets",
    "description": "The number of times cache lookup methods have returned a cached (hit) or uncached (newly loaded or null) value (miss).",
    "measurements": [
        {
            "statistic": "COUNT",
            "value": 0.0
        }
    ],
    "availableTags": [
        {
            "tag": "result",
            "values": [
                "hit",
                "miss"
            ]
        },
        {
            "tag": "cache.manager",
            "values": [
                "caffeine"
            ]
        },
        {
            "tag": "cache",
            "values": [
                "userCache, productCache"
            ]
        },
        {
            "tag": "name",
            "values": [
                "userCache, productCache"
            ]
        }
    ]
}

 

result 부분에서

hit : 캐시에서 데이터를 성공적으로 가져온 횟수

miss : 캐시에 데이터가 없어 DB등 원본 데이터 소스에서 조회한 횟수

 

 


캐시 상태 모니터링 워크플로우

캐시 상태를 모니터링하는 워크플로우 예시

 

이러한 구조를 통해 실시간 캐시 상태 관찰 및 트렌드 분석이 가능함

 

 


커스텀 지표 구성

Actuator가 제공하는 기본 지표들 외에도, 비즈니스 로직에 맞춰 커스텀 캐시 지표를 추가할 수도 있음

import com.github.benmanes.caffeine.cache.Cache;
import io.micrometer.core.instrument.MeterRegistry;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.caffeine.CaffeineCache;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class CustomCacheMetrics {
    private final CaffeineCacheManager cacheManager;
    private final MeterRegistry registry; // micrometer import

    @PostConstruct
    public void registerCustomMetrics() {
        cacheManager.getCacheNames().forEach(cacheName -> {
            CaffeineCache cache = (CaffeineCache) cacheManager.getCache(cacheName);
            Cache<Object, Object> nativeCache = cache.getNativeCache();
                
            registry.gauge("custom.cache.size", nativeCache.estimatedSize()); // size
            registry.gauge("custom.cache.hitRate", nativeCache.stats().hitRate()); // hit
            registry.gauge("custom.cache.evictionCount", nativeCache.stats().evictionCount()); // evict
        });
    }
}

 

이런 형태의 커스텀 코드를 통해 /actuator/metrics/custom.cache.size 와 같은 지표를 직접 조회할 수 있다고 한다.

 

 

여기에서 주입한 부분을 보면,

private final CaffeineCacheManager cacheManager;

 

이렇게 되어있다. 즉 CaffeineCacheManager에 대한 Bean 등록이 되어있어야 Spring이 제대로 돌아간다

이 Bean 주입은 CacheConfig 부분에서 할 수 있었음

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("userCache", "productCache");
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .maximumSize(1000)
                .recordStats()
                .expireAfterWrite(10, TimeUnit.MINUTES));
        return cacheManager;
    }
}

 

여기에서,

    public CacheManager caffeineCacheManager()

 

여기 타입부분을

    public CaffeineCacheManager caffeineCacheManager()

 

이렇게 바꿔주면 CaffeineCacheManager에 대한 Bean 등록도 되고, 커스텀을 CaffeineCacheManager 형태로 사용할 수 있음

 

다만 예제에 맞게 CacheManager의 형태로 바꿔주자

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager caffeineCacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("userCache", "productCache");
        cacheManager.setCaffeine(Caffeine.newBuilder()
                .maximumSize(1000)
                .recordStats()
                .expireAfterWrite(10, TimeUnit.MINUTES));
        return cacheManager;
    }
}

 

이렇게!

 

그리고 Postman이나 브라우저 등으로 http://localhost:8080/actuator/metrics 를 get 요청해보면

{
    "names": [
        "application.ready.time",
        "application.started.time",
        "cache.eviction.weight",
        "cache.evictions",
        "cache.gets",
        "cache.puts",
        "cache.size",
        "custom.cache.evictionCount",
        "custom.cache.hitRate",
        "custom.cache.size",
        ...
    ]
}

 

이런 형태로 custom 등록한 json 형태가 잘 나오고, 이를 get 요청하여 볼 수 있게 된다

 

 

 

참고할 코드

MeterRegistry.class

    <T> Gauge gauge(Meter.Id id, @Nullable T obj, ToDoubleFunction<T> valueFunction) {
        return (Gauge)this.registerMeterIfNecessary(Gauge.class, id, (id2) -> {
            return this.newGauge(id2, obj, valueFunction);
        }, NoopGauge::new);
    }

 


* 운영 환경에서는 Prometheus 및 Grafana를 함께 사용하여 실시간 시각화를 구축할 수 있음

* 캐시 미적중률이 갑자기 증가할 경우, TTL(만료 시간)이나 키 정책 변경 여부를 우선 확인할 수 있음

* 비즈니스 중요도가 높은 캐시는 별도의 알림(Alert) 기준을 설정해두면 안정적이라고 한다

 


정리

항목 설명
/actuator/caches 등록된 캐시 목록 및 상태 확인
/actuator/metrics/cache.gets 캐시 Hit / Miss 통계 조회
/actuator/metrics/cache.evictions 캐시 항목 제거 횟수 추적하기
recordStats() 캐시 통계 수집 활성화
MeterRegistry 커스텀 지표 등록 및 관리하기