Skip to content

Commit

Permalink
Merge pull request #33 from trip-draw/feature/#32
Browse files Browse the repository at this point in the history
[docs] Local Caching 적용하기 (#32)
  • Loading branch information
Combi153 authored Nov 8, 2023
2 parents f7cae80 + c53b2ce commit de446d6
Showing 1 changed file with 137 additions and 0 deletions.
137 changes: 137 additions & 0 deletions blog/Local Caching 적용하기.mdx
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 를 기록했습니다.

캐싱을 적용 후 조회 요청이 매우 빨라졌습니다!

0 comments on commit de446d6

Please sign in to comment.