티스토리 뷰

Goal
캐시를 적용하여 같은 키워드 사용하여 검색 시 캐시에 저장된 쿼리문과 엔티티를 사용하도록 하여 검색 속도 개선

캐싱 적용하기 위한 준비

* 환경
- Spring Boot 3.x
- Hibernate 6.x

 

우선 Hibernate 의 버전을 확인하고 버전에 맞는 EhCache 의존성을 build.gradle 에 추가해주었다. 출처의 첫 번째 링크를 참조해서 JCache 와 EhCache 의 의존성을 모두 추가 후 yml 파일을 설정해 1. L2 캐시를 사용하며 2. 콘솔 로그에서 L2 캐시가 적용이 올바르게 되었는지 확인하겠다고 명시했다.

프로젝트 run 하여 hibernate 버전 확인했다.

아래는 build.gradle 에 추가한 dependencies 이다.

implementation 'org.hibernate.orm:hibernate-jcache:6.1.6.Final'
implementation 'org.ehcache:ehcache:3.10.6'

// https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-core
compileOnly 'org.hibernate.orm:hibernate-core:6.1.7.Final'
// https://mvnrepository.com/artifact/org.jboss.logging/jboss-logging
compileOnly 'org.jboss.logging:jboss-logging:3.5.0.Final'
compileOnly 'javax.cache:cache-api:1.1.1'

아래는 application.yml 파일이다.

logging:
  level:
    org:
      hibernate:
        type: trace
spring:
  jpa:
    show-sql: true
    hibernate.ddl-auto: update
    properties:
      jakarta:
        persistence:
          sharedCache:
            mode: ENABLE_SELECTIVE
      hibernate:
        format_sql: false
        generate_statistics: true
        cache:
          use_second_level_cache: true
          use_query_cache: true
          region:
            factory_class: org.hibernate.cache.jcache.JCacheRegionFactory

캐싱 적용 이전과 이후 비교

이전 상태 파악

캐싱을 적용하지 않은 상태에서 데이터를 조회하면 위와 같이 모든 쿼리 (JDBC statements) 를 매번 모두 실행한다

쿼리문 개수: 17개

속도: 6062900 + 8172700 + 484749600 = 498,985,200

 

* 같은 키워드로 검색할 때마다 같은 쿼리문 개수와 비슷한 속도를 낸다.

* 세 번 같은 키워드로 검색하였다고 가정하자.

 

세 번을 하든 몇 번을 하든 평균 쿼리문 개수는 17개, 평균 속도는 소수점 첫째자리에서 반올림하여 499ms 이다.

 

L2 캐시를 엔티티에만 적용한 후 상태 파악

다음과 같이 2차 캐시만 엔티티들에 적용을 한 후에 다시 실행해보았다. 

recommend 를 포함하여 검색 시 조회되는 모든 엔티티의 클래스 레벨에 어노테이션을 추가했다.

검색을 한 결과 처음에는 캐시 (세션 단위로 캐시를 저장함) 에 저장된 것이 없어서 같은 속도를 냈지만, 같은 키워드로 한 번 더 검색했을 때 결과가 달라졌다.

 

처음:

17개의 SELECT 쿼리가 실행되었다.

2023-03-05T08:17:16.578+09:00  INFO 23392 --- [nio-8080-exec-1] i.StatisticalLoggingSessionEventListener : Session Metrics {
    6110200 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    9118300 nanoseconds spent preparing 17 JDBC statements;
    507282300 nanoseconds spent executing 17 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    48738500 nanoseconds spent performing 29 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    1591500 nanoseconds spent performing 12 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}

쿼리문 개수: 17개

속도: 6110200 + 9118300 + 507282300 + 48738500 + 1591500 = 572,840,800

 

 

두 번째:

5개의 쿼리가 실행되었다.

2023-03-05T08:18:16.448+09:00  INFO 23392 --- [io-8080-exec-10] i.StatisticalLoggingSessionEventListener : Session Metrics {
    6285000 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    6858700 nanoseconds spent preparing 5 JDBC statements;
    409067500 nanoseconds spent executing 5 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    1905200 nanoseconds spent performing 5 L2C puts;
    5427000 nanoseconds spent performing 24 L2C hits;
    0 nanoseconds spent performing 0 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}

쿼리문 개수: 5개

속도: 6285000 + 6858700 + 409067500 + 1905200 + 5427000 = 429,543,400

 

세 번째:

두 번째와 마찬가지로 5개의 쿼리문이 실행되었다.

속도는 6750900 + 7540200 + 418607800 + 1114700 + 3426800 = 437,440,400 이었다.

 

엔티티에만 L2 캐시를 적용한 후 세 번 검색 평균 쿼리문 개수는 (17 + 5 + 5) / 3 = 9 였고

속도는 평균 572,840,800 + 429,543,400 + 437,440,400 / 3 = 약 479,941,533 nanoseconds,

소수점 첫째자리에서 반올림하여 480ms 였다.

캐시 적용 전과 비교하면 약 4% 감소이다.

 

쿼리 캐시까지 적용한 후 상태 파악

이제 쿼리문을 만들어 실행하는 엔티티 매니저인 레포지토리에 캐싱을 적용해보자.

쿼리문 생성 및 실행 메서드들의 위에 각각 캐싱을 적용하는 어노테이션을 붙인다.

위의 코드처럼 검색을 통해 조회하는 모든 엔티티의 레포지토리들의 메서드에 같은 쿼리문이 실행되면 새롭게 만들지 말고 캐시에 저장되어 있는 쿼리문을 실행하라고 명시해주었다.

 

다시 세 번 실행해 보았다.

 

첫 번째:

역시 이번에도 17개의 쿼리가 처음에는 실행된다.

2023-03-05T08:34:52.059+09:00  INFO 7544 --- [nio-8080-exec-1] i.StatisticalLoggingSessionEventListener : Session Metrics {
    6605500 nanoseconds spent acquiring 1 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    9632600 nanoseconds spent preparing 17 JDBC statements;
    513073796 nanoseconds spent executing 17 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    57180002 nanoseconds spent performing 34 L2C puts;
    0 nanoseconds spent performing 0 L2C hits;
    2458700 nanoseconds spent performing 17 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}

쿼리문 개수: 17개

속도: 6605500 + 9632600 + 513073796 + 57180002 + 2458700 = 588,950,598

 

* 엔티티에 더해 쿼리문들까지 캐시에 넣으면서 처음에는 속도가 다소 증가하였다.

 

두 번째:

시간대를 보면 위에 8시 34분에 실행하고 37분에 분명 다시 실행했다

쿼리가 아무것도 생성되지 않았다. 조회는 모두 되어서 검색 결과는 창에 올바르게 뜨고 있다.

결과는 잘 나온다

쿼리문 개수: 0개

속도: 12862302 + 271499 = 13,133,801

 

* 레포지토리에 캐싱을 적용하여 쿼리문들을 캐싱하니까 두 번째부터는 쿼리문 개수가 0이 되었다.

 

세 번째:

2023-03-05T08:41:47.358+09:00  INFO 7544 --- [nio-8080-exec-1] i.StatisticalLoggingSessionEventListener : Session Metrics {
    0 nanoseconds spent acquiring 0 JDBC connections;
    0 nanoseconds spent releasing 0 JDBC connections;
    0 nanoseconds spent preparing 0 JDBC statements;
    0 nanoseconds spent executing 0 JDBC statements;
    0 nanoseconds spent executing 0 JDBC batches;
    0 nanoseconds spent performing 0 L2C puts;
    5435599 nanoseconds spent performing 29 L2C hits;
    147899 nanoseconds spent performing 9 L2C misses;
    0 nanoseconds spent executing 0 flushes (flushing a total of 0 entities and 0 collections);
    0 nanoseconds spent executing 0 partial-flushes (flushing a total of 0 entities and 0 collections)
}

쿼리문 개수: 0개

속도: 5435599 + 147899 = 5,583,498

 

* 마찬가지로 생성된 쿼리문 개수는 0개였다.

 

엔티티와 쿼리 모두를 캐싱한 후 세 번 검색 평균 쿼리문 개수는 (17 + 0 + 0) / 3 = 약 6 이었고,

평균 속도는 588,950,598 + 13,133,801 + 5,583,498 / 3 = 약 202,555,966 nanoseconds,

소수점 첫째자리에서 반올림하여 203ms 이었다.

캐시 적용 전과 비교하면 약 60% 감소이다.

엔티티에만 캐싱을 적용했을 때와 비교하면 약 58% 감소이다.

추가 작업

만료 시간과 캐시에 저장되는 데이터의 크기의 제한을 걸어야 캐시 메모리의 과부하를 막을 수 있다.

결론

캐시를 적용하려면 쿼리 캐시까지 적용해야 유의미한 효과를 기대할 수 있다. 물론 세 번의 실행을 평균을 내서는 의미 있는 해석이 나올 수 없다고도 보여지지만, DB 에 데이터의 양이 많아질 수록 캐싱을 하면 많은 속도의 개선을 기대하여 볼 수도 있다는 생각이 들었다. 이번에 적용한 곳은 적은 데이터를 조회하는 부분이므로 프로젝트가 확장될 수록 더 많은 데이터에 적용되었을 때 어떤 결과 도출되는지 궁금하다.

출처

https://stackoverflow.com/questions/31585698/spring-boot-jpa2-hibernate-enable-second-level-cache?rq=1 

 

Spring Boot + JPA2 + Hibernate - enable second level cache

I'm using Spring Boot 1.2.5 with JPA2 to annotate entities (and hibernate as underlaying JPA implementation). I wanted to use second level cache in that setup, so entities were annotated with @javax.

stackoverflow.com

https://stackoverflow.com/questions/69858533/replacement-for-hibernates-deprecated-type-annotation

 

Replacement for Hibernate's deprecated Type annotation?

Upon upgrading to Hibernate 5.6.0.Final, I found that org.hibernate.annotations.Type is now deprecated. I use this annotation in many of my entities, including places where I leverage Hibernate's ...

stackoverflow.com

https://www.baeldung.com/hibernate-second-level-cache#cacheConcurrencyStrategy

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함