티스토리 뷰
Goal
캐시를 적용하여 같은 키워드 사용하여 검색 시 캐시에 저장된 쿼리문과 엔티티를 사용하도록 하여 검색 속도 개선
캐싱 적용하기 위한 준비
* 환경
- Spring Boot 3.x
- Hibernate 6.x
우선 Hibernate 의 버전을 확인하고 버전에 맞는 EhCache 의존성을 build.gradle 에 추가해주었다. 출처의 첫 번째 링크를 참조해서 JCache 와 EhCache 의 의존성을 모두 추가 후 yml 파일을 설정해 1. L2 캐시를 사용하며 2. 콘솔 로그에서 L2 캐시가 적용이 올바르게 되었는지 확인하겠다고 명시했다.
아래는 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
캐싱 적용 이전과 이후 비교
이전 상태 파악
쿼리문 개수: 17개
속도: 6062900 + 8172700 + 484749600 = 498,985,200
* 같은 키워드로 검색할 때마다 같은 쿼리문 개수와 비슷한 속도를 낸다.
* 세 번 같은 키워드로 검색하였다고 가정하자.
세 번을 하든 몇 번을 하든 평균 쿼리문 개수는 17개, 평균 속도는 소수점 첫째자리에서 반올림하여 499ms 이다.
L2 캐시를 엔티티에만 적용한 후 상태 파악
다음과 같이 2차 캐시만 엔티티들에 적용을 한 후에 다시 실행해보았다.
검색을 한 결과 처음에는 캐시 (세션 단위로 캐시를 저장함) 에 저장된 것이 없어서 같은 속도를 냈지만, 같은 키워드로 한 번 더 검색했을 때 결과가 달라졌다.
처음:
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
두 번째:
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% 감소이다.
쿼리 캐시까지 적용한 후 상태 파악
이제 쿼리문을 만들어 실행하는 엔티티 매니저인 레포지토리에 캐싱을 적용해보자.
위의 코드처럼 검색을 통해 조회하는 모든 엔티티의 레포지토리들의 메서드에 같은 쿼리문이 실행되면 새롭게 만들지 말고 캐시에 저장되어 있는 쿼리문을 실행하라고 명시해주었다.
다시 세 번 실행해 보았다.
첫 번째:
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
* 엔티티에 더해 쿼리문들까지 캐시에 넣으면서 처음에는 속도가 다소 증가하였다.
두 번째:
쿼리가 아무것도 생성되지 않았다. 조회는 모두 되어서 검색 결과는 창에 올바르게 뜨고 있다.
쿼리문 개수: 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/69858533/replacement-for-hibernates-deprecated-type-annotation
https://www.baeldung.com/hibernate-second-level-cache#cacheConcurrencyStrategy
'Project' 카테고리의 다른 글
@@sql_mode 변경 방법 (0) | 2024.05.23 |
---|---|
plugin org.asciidoctor.convert was not found 에러 해결 (0) | 2023.05.05 |
[Refactoring] 전략 패턴 적용하기 (1) (0) | 2023.02.22 |
[Spring Boot] Could not load or find main class 에러 (0) | 2023.02.22 |
[개발일지] Spotify Web API 사용기 (1) (0) | 2023.01.31 |
- Total
- Today
- Yesterday
- json web token
- docker
- @RequestBody
- DeSerialization
- 코테
- DTO
- Jackson
- gitlab
- JPA
- 도커
- google cloud
- Java Data Types
- 지연 로딩
- 가상 서버
- 알고리즘
- 역직렬화
- LazyInitializationException
- ci/cd
- Spring Boot
- 깃랩
- Firebase
- 프로그래머스
- JPQL
- 인증/인가
- 기지국 설치
- N+1
- FCM
- spring
- JOIN FETCH
- 실시간데이터
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |