공부

고성능 JPA & Hibernate - Section 12. Caching 강의록

besomilk 2025. 9. 30. 22:18

작성중...

Database Caching

Cache

  • 캐시란, 빠른 저장소(메모리 등)에 자주 쓰는 데이터를 보관해서 응답시간을 줄이고 DB 부하를 낮추는 기술.
  • 효과: 읽기 지연(latency) 감소, DB I/O/CPU 부담 감소, 더 많은 동시사용자 처리 가능.
  • 트레이드오프: 데이터 일관성(정확성) 관리 필요, 메모리 비용, 복잡성 증가.

캐시 동기화 전략

  1. Cache-aside
    • application에서 직접 캐시 확인
    • 캐시 미스면 DB 조회 -> DB 결과를 캐시에 적재(보통 TTL 설정).
    • 쓰기(수정/삭제)는 DB에 먼저 쓰고 캐시 무효화(invalidate) 또는 갱신
  2. Read-through
    • 캐시 계층의 라이브러리가 캐시 미스 시 자동으로 DB 데이터 로드해 캐시에 넣어줌
    • 애플리케이션 코드는 단순히 캐시만 호출
    • 캐시 서버의 구현 복잡성이 올라가 솔루션 사용
    • 장애 시 캐시가 DB 접근의 실패 지점이 될 수 있음
      • DB 접근 로직이 캐시 안에 숨겨져 있음
      • SPoF(Single Point of Failure)
  3. Write-through
    • 애플리케이션이 쓰면 동시에 캐시와 DB에 쓰기
    • 쓰기 레이턴시가 DB 쓰기까지 기다려야 함
    • 동기화가 복잡함
  4. Write-invalidate
    • 데이터 변경 시 캐시의 해당 키 무효화
    • 다음 읽기 때 최신 값을 받아옴
  5. Write-behind
    • 쓰기는 먼저 캐시에 적용, DB에는 비동기적으로 배치해서 반영 (큐 사용)
    • 장애 시 캐시의 변경이 DB에 ㅂ나영되지 ㅇ낳을 수 있음
    • 성능이 중요할 때, 데이터 손실 리스크 완화 가능한 내구성 있는 영속화 큐 사용할 때

캐시 레이어별 특징

  • 애플리케이션 레벨 캐시: Redis, Memcached — 매우 빠름, 단일 키/오브젝트 단위 캐싱에 탁월. (세션, 객체, 계산 결과)
  • ORM/프레임워크 캐시: JPA/Hibernate, MyBatis 일부 플러그인 등에서 제공하는 application 내부 캐시
    • 1차 캐시: 동일한 트랜잭션 안에서 같은 객체 재사용
    • 2차 캐시: application 전역에서 캐싱
      • Hibernate 2차 캐시 등 — 엔티티/쿼리 레벨 캐싱
  • DB 엔진 캐시(Buffer Pool): DB 내부에서 데이터 블록 보관하는 자체 캐시가 있음
    • Oracle
      • SGA(System Global Area): 서버 프로세스들이 공유하는 메모리 영역 (DB Buffer Cache, Shared Pool(파싱된 SQL·실행계획) redo log buffer 등)
        • Buffer Pool 확인: V$BUFFER_POOL
        • AMM (Automatic Memory Management): memory_target으로 완전 AMM, 또는 pga_aggregate_target/sga_target으로 부분 AMM
      • PGA(Program Global Area): 각 세션/프로세스마다 개별로 할당되는 메모리 (정렬, 해시 조인 등 세션 작업 메모리)
        • 모니터링: V$PGASTAT에서 maximum PGA allocated, total PGA allocated, total PGA inuse
  • OS 페이지 캐시: 파일시스템 캐시
    • DBMS도 OS캐시 위에 자체 버퍼 캐시를 둠
  • 하드웨어 캐시: 디스크/SSD 레벨 캐시(헤드, 컨트롤러)

Application-Level Caching

데이터베이스 캐싱의 한계

  • 데이터베이스 자체의 캐싱 기능은 디스크 I/O를 줄여 빠르게 동작하도록 함
  • 하지만 CPU와 메모리를 많이 쓰는 작업의 경우 Buffer Pool이 도와줄 수 없음
  • e.g.
    • 정렬(Sorting)
    • 여러 테이블을 묶는 조인(Joining)
    • 통계, 분석용 함수 실행
    • 재귀 쿼리
    • JSON, 배열 처리애플리케이션 수준 캐싱의 장점캐시와 데이터베이스는 똑같은 데이터를 유지해야 함
    • 데이터 동기화

동기화 방식

  1. Cache-aside 패턴
    • DB 갱신 후 캐시도 갱신하거나 무효화
    • 문제점: 캐시와 DB 갱신이 동시에 일어나지 않을 수 있음
    • 해결방법
      • DB 롤백 시 캐시 무효화
      • 비동기 갱신 (DB 커밋 후 캐시 갱신, 짧은 시간 동안 캐시에서 오래된 데이터 읽을 수 있음)
  2. CDC (Change Data Capture) 패턴Debezium을 이용한 CDC
    • DB의 변경 내역 로그를 기반으로 캐시를 갱신
    • 방법
      1. 트리거
      2. Redo 로그
  • Debezium: CDC를 전문적으로 처리하는 오픈소스 플랫폼
  • DB 로그를 실시간으로 읽어서 변경 이벤트를 전달해 캐시를 자동 업데이트 가능

솔루션

  • Redis, Memcached → 빠르고 널리 쓰이는 인메모리 캐시
  • Hazelcast, Ehcache → Java 애플리케이션 친화적인 분산 캐시
  • Etcd → 주로 설정/메타데이터 저장에 사용
  • Aerospike → 고성능 NoSQL + 캐시 기능

계층 구조

애플리케이션 캐싱은 단일 계층뿐 아니라 다층 구조로 설계해 성능과 안정성을 균형 있게 함

Hibernate Second-Level Cache

아키텍처

  • 1차 캐시
    • 필수 (항상 켜져 있음)
    • 같은 트랜잭션 내에서만 유효함
  • 2차 캐시: DB를 거치지 않고 읽을 수 있도록 메모리에 저장해둠
    • SessionFactory 단위로 여러 세션 간 공유

Hibernate 네 가지 레벨의 2차 캐시 유형

  1. 엔티티 캐시
    • JPA의 @Cacheable 대신 Hibernate는 @Cache 어노테이션 사용
    • 변경이 거의 없는 데이터(예: 코드성 데이터, 지역 목록)에 적합
  2. 컬렉션 캐시
    • 연관관계(OneToMany 등)의 자식 ID 목록만 캐싱 (N+1 문제 방지)
    • 제 자식 데이터는 Entity Cache에서 불러옴
    • 항상 Read-Through
  3. 쿼리 캐시
    • 단순히 “결과 ID 목록”을 저장 → 실제 데이터는 Entity Cache에서 조회
    • SQL 문자열 차이가 나면 별도 캐시로 취급됨 (들여쓰기, alias 일관성 중요)
  4. 자연키 캐시
    • NaturalId로 지정한 컬럼(예: email, slug) 기반 조회를 캐싱
    • Entity Cache와 같이 쓰면 DB 접근 없이 엔티티 조회 가능

캐시 동시성 전략

ORM이 엔티티 변경될 때 캐시를 어떻게 반영할지에 관한 규칙

  1. READ_ONLY
    • 불변 데이터에만 사용 가능
    • 캐시 읽기만 허용, 업데이트는 금지
    • 예: “국가 코드 목록” 같은 절대 안 바뀌는 데이터
  2. READ_WRITE
    • 가장 일반적인 전략
    • 캐시를 업데이트할 때 잠깐 락을 걸어 DB와 캐시를 동기화
    • 트랜잭션 종료 시점에 캐시 반영
    • 보통 실무에서 가장 많이 씀
  3. NONSTRICT_READ_WRITE
    • 캐시 갱신을 느슨하게 처리
    • “조금 오래된 데이터 봐도 된다”면 사용
    • 예: 조회수, 좋아요 수 같이 약간의 지연 허용 가능한 데이터
  4. TRANSACTIONAL
    • JTA 환경에서만 사용 가능
    • DB 트랜잭션과 캐시를 완전히 동기화
    • 가장 무겁지만 일관성 보장 최고

정리

  • Entity Cache: 자주 읽히고 드물게 수정되는 데이터에만 사용.
  • Collection Cache: 자식 엔티티도 반드시 캐시해야 함.
  • Query Cache: 반복되는 동일 쿼리에만 적용.
  • Natural ID Cache: 자연 키 조회가 잦을 때 사용.
  • 분산 환경: 클러스터링 가능한 캐시 솔루션 사용 필요.
  • 모니터링: 캐시 히트율을 체크해서 효과 확인.

강의 출처: 인프런 고성능 JPA & Hibernate (High-Performance Java Persistence) - Vlad Mihalcea