-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
[docs] Local Caching 적용하기 (#32)
- Loading branch information
Showing
1 changed file
with
137 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
--- | ||
title: Local Caching 적용하기 | ||
slug: Local Caching | ||
authors: [ huchu ] | ||
tags: [ Caching, Spring cache, caffeine ] | ||
--- | ||
|
||
## 들어가기 | ||
|
||
[트립드로우](https://play.google.com/store/apps/details?id=com.teamtripdraw.android) 팀 프로젝트를 진행하고 있습니다. | ||
|
||
트립드로우는 사용자에게 여행과 감상 기록에 대해 검색 기능을 제공합니다. | ||
|
||
![](https://velog.velcdn.com/images/153plane/post/3b52c75a-2eef-4323-b82e-ecf53c980972/image.png) | ||
|
||
검색을 할 때는 주소, 시간 등을 조건으로 지정할 수 있습니다. | ||
|
||
주소를 지정할 때는 `시/도`, `시/군/구`, `읍/면/동` 데이터를 기반으로 합니다. | ||
|
||
이러한 데이터는 서버 DB에 Area 데이터로 저장되어 있으며, 클라이언트는 사용자에게 주소 조건 검색 기능을 제공할 때 `전체 Area 조회 요청`을 보냅니다. | ||
|
||
이 요청을 통해 총 3518개 Area 데이터가 반환됩니다. | ||
|
||
백엔드는 `전체 Area 조회 요청`에 대해 캐싱 작업을 진행했습니다. | ||
|
||
왜 캐싱을 했는지, 어떻게 캐싱을 했는지, 그래서 무엇이 달라졌는지 하나씩 살펴보겠습니다. | ||
|
||
# Caching | ||
|
||
## 왜 | ||
캐싱(Caching)이란 **캐시**라는 작업을 하는 행위를 의미합니다. | ||
|
||
> **캐시(cache)**는 컴퓨터 과학에서 **데이터나 값을 미리 복사해 놓는 임시 장소**를 가리킨다. 캐시는 캐시의 접근 시간에 비해 원래 데이터를 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우에 사용한다. 캐시에 데이터를 미리 복사해 놓으면 계산이나 접근 시간없이 **더 빠른 속도로 데이터에 접근**할 수 있다. [wiki](https://ko.wikipedia.org/wiki/%EC%BA%90%EC%8B%9C) | ||
캐싱을 하면 데이터가 미리 복사되어 있어, 더 빠른 속도로 접근할 수 있습니다. | ||
|
||
`전체 Area 조회 요청` 을 통해 반환되는 Area 데이터는 다음과 같은 특징을 갖습니다. | ||
|
||
- 데이터의 변화 주기가 깁니다. 즉, 한번 저장하면 변화할 가능성이 적습니다. | ||
- 여러 사용자가 동일한 데이터를 조회할 일이 빈번합니다. | ||
|
||
이러한 측면에서 캐싱을 적용하는 것이 유리하다고 판단했습니다. | ||
|
||
### Local Caching vs Global Caching | ||
|
||
캐싱은 크게 Local Caching 와 Global Caching 으로 나눌 수 있습니다. | ||
|
||
두 캐싱 방법은 아래와 같은 특징을 갖습니다. | ||
|
||
Local Cachcing | ||
- WAS의 메모리에 데이터를 저장한다. | ||
- 별도의 infrastructure 가 필요하지 않다. | ||
- 서버를 scale out 할 경우, 서버마다 캐싱된 데이터가 달라질 수 있다. | ||
- 캐싱 데이터가 크다면 메모리 사용량이 많아진다. | ||
|
||
Global Cachcing | ||
- Redis 등을 활용한 방법이다. | ||
- WAS를 scale out 하더라도, 캐싱 데이터가 동일하다. 즉 확장에 유리하다. | ||
- 별도의 infrastructure가 필요하다. | ||
- 네트워크 비용이 발생하기 때문에 LocalCaching 보다 느릴 수 있다. | ||
|
||
Local Caching을 선택했을 때 서버 확장에 따른 데이터 정합성 문제는 상대적으로 해결 방법이 쉽게 떠올랐습니다. | ||
|
||
현재 Area 데이터는 [외부 API](https://sgis.kostat.go.kr/developer/html/newOpenApi/api/dataApi/addressBoundary.html#stage) 를 사용해 DB에 저장하고 있습니다. | ||
|
||
주소가 변경되었을 때 Area 데이터를 새로 받아와 DB에 저장하고, 그때 Cache를 비우면 될 것으로 판단했습니다. | ||
|
||
한편, 단순히 `전체 Area 조회 요청`에 캐싱을 적용하기 위해 별도의 infra를 구축하는 것이 과도하다고 판단했습니다. infra 작업에 소요될 시간과 학습비용이 크다고 예상했기 때문입니다. | ||
|
||
|
||
따라서 Local Caching을 적용하기로 선택했습니다. | ||
|
||
## 어떻게 | ||
|
||
### Spring의 Cache Abstraction | ||
|
||
Spring의 [공식문서](https://docs.spring.io/spring-framework/reference/integration/cache.html)를 살펴보면, Spring은 개발자가 특정 기술에 종속되지 않고 캐시를 구현할 수 있도록 추상화된 인터페이스 등을 제공합니다. | ||
|
||
```gradle | ||
implementation 'org.springframework.boot:spring-boot-starter-cache' | ||
``` | ||
`spring-boot-starter-cache` 의존성을 추가해서 캐싱 기능을 사용하면 됩니다! | ||
|
||
### EhCache vs Caffeine | ||
|
||
캐싱을 어떻게 구현할지 고민하면서 여러 라이브러리를 살펴보았습니다. | ||
|
||
선택 시 고민했던 선택지는 EhCache 와 Caffeine 두 가지입니다. | ||
|
||
EhCache 는 오랜 기간 많은 사람들에게 사용된 정석같은 라이브러리 입니다. Caffeine은 비교적 최근에 떠오르는 신입 라이브러리입니다. | ||
|
||
두 라이브러리를 살펴보면서 다음과 같은 근거를 찾았습니다. | ||
|
||
- EhCache가 많은 기능을 제공합니다. 하지만 Caffeine에 비해 적용하기 불편합니다. EhCache가 많은 기능을 제공하는 만큼 설정할 내용이 많다고 느꼈습니다. | ||
- EhCache는 xml 파일을 사용합니다. 이것이 불편합니다. xml은 가독성이 좋지 않습니다. | ||
- Caffeine의 성능이 더 우수하다고 보았습니다. (참고)[https://github.com/ben-manes/caffeine/wiki/Benchmarks] | ||
|
||
현재 팀 프로젝트에는 EhCache의 다양한 기능을 필요로 하지 않습니다. 또한 필요한 설정에 대해 xml 파일을 추가하기보다 코드 단에서 설정하고 싶습니다. | ||
|
||
이러한 의사결정 끝에 Caffeine을 적용하기로 했습니다. | ||
|
||
Spring cache와 Caffeine의 조합으로 캐싱을 진행했습니다. | ||
|
||
## 적용 | ||
### 코드 | ||
Caffeine을 사용하기 위해 의존성을 추가해주었습니다. | ||
|
||
```gradle | ||
implementation 'com.github.ben-manes.caffeine:caffeine' | ||
``` | ||
|
||
![](https://velog.velcdn.com/images/153plane/post/4328e700-4e3c-437b-9b2d-511447c863e8/image.png) | ||
|
||
`@Configuration` 을 통해 CaffeineCache를 포함한 CacheManager를 Bean 으로 등록합니다. | ||
|
||
![](https://velog.velcdn.com/images/153plane/post/063996fa-aa94-4fe1-be00-c53ec00f803a/image.png) | ||
|
||
`@Cacheable`, `@CacheEvict`은 Spring Cache 에서 제공하는 애노테이션입니다. | ||
|
||
`@Transactional`과 같이 AOP 개념이 적용되었으며, 다음과 같이 동작합니다. | ||
|
||
-`@Cacheable` : 메서드에 반환값을 캐싱합니다. 캐시에 데이터가 있을 경우 메서드를 실행하지 않고 캐시의 데이터를 반환합니다. cacheName을 기준으로 캐싱합니다. | ||
-`@CacheEvict` : 메서드가 호출될 때 cacheName에 해당하는 캐싱 데이터를 삭제합니다. | ||
|
||
AreaService 에서 readAll 메서드를 실행 시 데이터를 캐시할 수 있도록 설정했습니다.(@Cacheable) | ||
|
||
만약 새로운 Area 데이터가 생성될 경우 캐시를 삭제하도록 했습니다.(@CacheEvict) | ||
|
||
### 성능 | ||
|
||
![image](https://github.com/woowacourse-teams/2023-trip-draw/assets/106813090/24a0ab43-43ec-4ce4-93d3-0afe836e1b7d) | ||
캐싱을 하기 전 `전체 Area 조회 요청`을 처리하는 시간은 100 ~ 120ms 이 걸렸습니다. | ||
|
||
![image](https://github.com/woowacourse-teams/2023-trip-draw/assets/106813090/911f9031-11cb-4614-a417-18af86dd476e) | ||
캐싱을 적용한 후에는 처리 시간이 14 ~ 18ms 를 기록했습니다. | ||
|
||
캐싱을 적용 후 조회 요청이 매우 빨라졌습니다! |