[SpringBoot - Ehcache]Spring 서비스에서 Ehcache를 도입하며 고민했던 것들 – 캐시는 성능이 아니라 운영 문제

2026. 1. 2. 00:48·ServerDev/SpringBoot

서비스를 운영하다 보면 자연스럽게 “이 요청, 굳이 매번 DB를 타야 할까?”라는 질문을 하게 됩니다.

특히 공지사항 목록이나 질문 게시판처럼 조회 빈도는 높지만 변경은 상대적으로 적은 리소스의 경우, 요청이 몰릴수록 DB 부하가 눈에 띄게 증가하고 응답 시간도 점점 늘어나는 문제가 있었습니다.

 

초기에는 트래픽 자체가 많지 않았기 때문에 크게 체감하지 못했지만, 실제로 운영 환경에서 여러 서버가 동시에 요청을 보내기 시작하니 DB 쪽에서 병목이 생기는 지점이 보이기 시작했습니다.

이때 가장 먼저 떠오른 해결책이 캐시였습니다.

 

다만, 캐시를 도입할 때부터 “무조건 Redis” 같은 선택을 하지는 않았습니다.

현재 단계에서는 분산 캐시를 도입해 관리 포인트를 늘리기보다는, 서비스 내부에서 제어 가능한 범위의 캐시부터 적용해보는 것이 낫겠다고 판단했습니다.

그래서 선택한 것이 Ehcache였습니다.

 

Ehcache를 선택한 이유는 단순했습니다.

이미 Spring 생태계 안에서 안정적으로 쓰이고 있고, 별도의 인프라 없이도 빠르게 적용할 수 있으며, 무엇보다 운영 초기에 캐시가 어떤 영향을 미치는지 관찰하기에 적합한 도구였기 때문입니다.

 

캐시 설계에서 가장 먼저 고민한 것은 “무엇을 캐시할 것인가”였습니다.

모든 데이터를 캐시하는 것은 오히려 독이 될 수 있기 때문에, 초기 단계에서는 리스트 조회 캐시만 적용하기로 했습니다.

대표적으로 공지사항 목록, 질문 목록처럼 읽기 요청이 많고, 페이지 단위로 나뉘어 있으며, 약간의 지연이 사용자 경험에 치명적이지 않은 데이터들입니다.

 

캐시 설정은 ehcache.xml에서 리전 단위로 분리해 관리했습니다.

각 리전마다 TTL과 최대 엔트리 수를 명시적으로 지정해두고, 캐시가 무한정 쌓이거나 예상치 못하게 오래 유지되지 않도록 제어했습니다.

이 과정에서 느낀 점은, 캐시는 “빠르게 만드는 도구”라기보다는 행동 범위를 명확히 제한해야 하는 자원이라는 점이었습니다.

 

실제 적용은 서비스 레이어에서 이루어졌습니다.

목록 조회 메서드에는 @Cacheable을 적용해 페이지 번호를 키로 캐시되도록 했고, 데이터 변경이 발생하는 지점에서는 반드시 캐시를 무효화하도록 설계했습니다.

 

@Cacheable(cacheNames = "noticeList", key = "#page")
public List<Notice> getNotices(int page) {
    return noticeRepository.findRecent(page);
}

@CacheEvict(cacheNames = "noticeList", allEntries = true)
public void createNotice(NoticeCreate dto) {
    noticeRepository.save(dto.toEntity());
}

 

이 방식은 구현 자체는 단순했지만, 실제로 운영하면서 가장 많이 신경 쓴 부분은 정합성이었습니다.

캐시는 결국 “실제 데이터의 사본”일 뿐이고, 사본이 원본보다 오래 살아남는 순간 문제가 되기 시작합니다.

 

실제로 캐시를 처음 적용했을 때, 공지를 새로 등록했음에도 불구하고 목록 화면에는 이전 데이터가 계속 내려오는 문제가 발생했습니다.

원인을 추적해보니, 쓰기 경로 중 일부에서 캐시 무효화가 누락되어 있었고, 이로 인해 DB와 캐시 사이의 불일치가 발생하고 있었습니다.

 

이 경험을 통해 다시 한 번 정리하게 된 원칙은 명확했습니다.

캐시는 정합성의 주체가 되어서는 안 되고, 언제든지 버려져도 괜찮은 존재여야 한다는 점입니다.

그래서 이후에는 데이터 변경이 발생하는 모든 경로에서 캐시 제거가 확실히 일어나도록 규칙을 정했고, 코드 리뷰 시에도 캐시 무효화 여부를 반드시 확인하도록 했습니다.

 

TTL 설정 역시 단순한 숫자 문제가 아니었습니다.

TTL을 짧게 가져가면 캐시 효과는 미미해지고, 길게 가져가면 데이터 최신성 문제가 발생합니다.

결국 이 부분은 업데이트 주기와 사용자 경험 사이에서 타협점을 찾는 문제였고, 초기에는 보수적으로 짧은 TTL을 두고 실제 운영 데이터를 보면서 점진적으로 조정하기로 했습니다.

 

아직 이 단계에서는 캐시 히트율이나 미스율 같은 메트릭을 별도로 수집하지는 않았습니다.

하지만 운영 환경이 안정화되면, 캐시 효율을 수치로 확인하고 조정할 수 있도록 모니터링 포인트를 추가할 계획입니다.

지금 단계에서는 “과도한 최적화”보다 안전한 범위 내에서의 개선이 더 중요하다고 판단했습니다.

 

이번 캐시 도입을 통해 느낀 점은, 캐시는 단순히 성능을 올리는 기술이 아니라 운영의 복잡도를 관리하는 선택이라는 것이었습니다.

DB 부하를 줄이는 것만큼이나, 캐시가 언제 어떻게 동작하는지 예측 가능하게 만드는 것이 중요했고, 그 예측 가능성이 결국 운영 안정성으로 이어진다는 것을 체감하게 되었습니다.

'ServerDev > SpringBoot' 카테고리의 다른 글

[SpringBoot] 로그에 찍힌 의문의 PROPFIND 요청, 해킹 공격일까? (feat. Spring Security)  (0) 2025.12.19
[SpringBoot - gRPC] 실시간 STT 스트리밍과 세션별 gRPC 스트림 분리 (Project: SpeakNote)  (0) 2025.12.13
[SpringBoot - WebSocket] 실시간 음성 스트리밍 서버 구조 (Project: SpeakNote)  (0) 2025.12.13
[Spring Boot 멀티모듈] domain-common 빌드 실패 원인과 해결 과정 정리 - Lombok, JPA @Entity 인식 안됨, Gradle 의존성 삽질기  (0) 2025.12.13
[Spring Boot] 사용자/관리자 API 분리를 위한 멀티모듈 설계 실전 가이드 (Common, User, Admin)  (0) 2025.12.13
'ServerDev/SpringBoot' 카테고리의 다른 글
  • [SpringBoot] 로그에 찍힌 의문의 PROPFIND 요청, 해킹 공격일까? (feat. Spring Security)
  • [SpringBoot - gRPC] 실시간 STT 스트리밍과 세션별 gRPC 스트림 분리 (Project: SpeakNote)
  • [SpringBoot - WebSocket] 실시간 음성 스트리밍 서버 구조 (Project: SpeakNote)
  • [Spring Boot 멀티모듈] domain-common 빌드 실패 원인과 해결 과정 정리 - Lombok, JPA @Entity 인식 안됨, Gradle 의존성 삽질기
yeseul-kim01
yeseul-kim01
  • yeseul-kim01
    슬 개발일지
    yeseul-kim01
  • 전체
    오늘
    어제
    • 분류 전체보기 (79)
      • 자격증 (1)
        • 정보보안기사 (0)
      • DevOps (17)
        • Docker (6)
        • Kubernetes (1)
        • GitHub Actions (0)
        • AWS (4)
        • Monitoring (1)
        • Nginx (1)
        • GCP (3)
      • ServerDev (34)
        • SpringBoot (13)
        • DJango (5)
        • FastAPI (14)
        • Next (0)
        • Flask (0)
        • Database (2)
      • Algorithm (2)
        • BFS (0)
        • DFS (1)
        • 다익스트라 (0)
      • CS (8)
      • Data Engineering (1)
      • AI&MLOps (2)
      • Architecture (6)
      • Software Engineering (0)
        • Library Packaging (0)
      • Project (5)
        • docx-generator (0)
        • speak-note (2)
        • ms-serving (1)
        • keyshield (2)
      • ProgrammingLanguages (3)
        • Python (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    트러블슈팅
    depends
    아키텍처
    Kubernetes
    SpringBoot
    실무일기-백엔드편
    백엔드
    실무일기-인프라편
    비동기처리
    FastAPI
    STT
    rag
    프로젝트기록-KeyShield
    실시간시스템
    KeyShield
    di
    하이브리드아키텍처
    SpeakNote
    multipartfile
    아키텍처설계
    MLops
    동시성제어
    프로젝트기록-speaknote
    멀티모듈
    grpc
    NLP부트캠프
    Django
    KServe
    docker
    FastAPI - CORS 마스터
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
yeseul-kim01
[SpringBoot - Ehcache]Spring 서비스에서 Ehcache를 도입하며 고민했던 것들 – 캐시는 성능이 아니라 운영 문제
상단으로

티스토리툴바