[spring] cache를 사용하여 불필요한 트래픽을 줄여보자!

sujin·2023년 7월 15일
1

spring

목록 보기
9/13

어떤 경우에 cache를 사용해야할까

cache를 사용하는 이유는 데이터 베이스에서 읽기 성능 개선을 할 수 있기 때문이다. cache는 cpu가 database 메모리에 접근을 하지 않아도 되도록 중간 버퍼의 역할을 한다. cache에 값을 저장해놓고 "읽는다"면 database까지 가서 조회하지 않을 것이다!

그렇다는 것은 cache에 저장한다는 것은 database에는 저장하지 않는다는 것이다. (물론 시간을 정해두고 update를 하겠지만 말이다!) 그러면 database에 안전하게 보관하거나 데이터가 여기저기 연관관계가 얽혀있는 것이라면 cache를 사용해도 될까?

예를 들어서, 고객이 송금을 했다. 그러면 송금한 고객의 잔액을 차감하고 입금받은 사용자의 계좌에서 돈을 늘려놔야, 간편송금 시스템에서의 '간편'이 의미가 있을 것이다. 캐시에 저장해두고 1시간 뒤에 다른 고객들의 정보와 함께 update를 한다? 그것은 너무 느리고 돈이 걸린 중요한 정보를 날릴 수 있게 되는 불안요소가 있다.

결론은, 실시간적으로 중요한 데이터라면 데이터베이스에서 관리하는게 맞고, 데이터베이스에는 늦게 반영해도 되는 것!은 cache에 저장하고 조회를 하는 시스템을 가져가자!라고 생각했다.

조회수

사용자의 통계 분석을 하려고했다. 이때, 어떤 카테고리의 페이지가 가장 조회수가 많은 것인지 알아보려고한다. 그래서 page entity에 view라는 조회수 컬럼을 넣어서 관리를 하려고했다. api를 클릭할 때마다 조회수를 증가시키고 그것을 매번 database에 저장해뒀다가 update될때마다 database에서 가져와야하는가? cache를 사용하자!
왜냐? 우리 서비스에서는 조회수를 사용자에게 보여줄 필요는 없다. 그리고 사용자 통계 분석을 한다고 했듯이 그 전에만 반영되어있으면 되고 전체적인 흐름을 볼 것이기 때문이다.
그렇기때문에 실시간성이 중요하지 않다고 생각했다. 그리고 사라지면 안되는 엄청나게 큰 영향이 있는지도 의문이였다.


spring-boot-starter-cache 를 소개한다

물론 redis처럼 인메모리를 사용할 때 cache데이터와 같은 것을 사용하기 좋으니,,,,고려를 해봤다. 하지만 아직은 사용자 통계 분석을 위한 view table을 생성하기 위해서 page view count데이터 적용을 고려하는 단계이기에 간단한 spring-boot-starter를 사용하도록한다.

dependency

 implementation("org.springframework.boot:spring-boot-starter-cache")

spring에서 기본으로 제공하는 spring-boot-starter-cache를 사용하려면? dependency에 등록을 해줘야한다.

bean 등록

Cache Manager를 통해서 cache를 등록하고 사용할 수 있게 된다.
cache를 key, value를 통해서 등록할 수 있는데 이때 바구니를 만드는 과정이 필요할 것이다.
그 바구니는 여러개가 될 수 있다. 바구니의 이름을 지정하면서 cache를 생성하면 된다고 생각하면 좋을 것인데 그 이름을 등록할 수 있도록 ConcurrentMapCacheManager가 역할을 해준다.

cacheManager를 생성할 때

concurrentMap위의 method를 사용한다. 이는 CacheManager와 BeanClassLoaderAware를 상속받아서 만들어진 public class에서 정의된 method이다. 코드에서 볼 수 있듯이 이름을 넘기면 된다.

Construct a static ConcurrentMapCacheManager, managing caches for the specified cache names only.

라고 설명되어있는데, 아래의 이미지를 보면 더 이해가 쉬울것이다.

처음에는 16개의 공간으로 바구니 16개가 존재한다. 그렇지만, 이름이 부여되지 않았다. 여기에 이름을 부여하기 위해서 ConcurrentMapCacheManager의 생성자를 사용해서 정의를 해준다. 이때 setCacheNames를 사용하는데

public void setCacheNames(@Nullable Collection<String> cacheNames) {
		if (cacheNames != null) {
			for (String name : cacheNames) {
				this.cacheMap.put(name, createConcurrentMapCache(name));
			}
			this.dynamic = false;
		}
		else {
			this.dynamic = true;
		}
	}

다음과 같다. 이름과 cahce를 매핑시켜주는 작업이라고 생각하면 된다.
아까 key, value를 저장할 바구니를 만든다고 했는데 그 이름을 정해주면 된다고 했다.

this.cacheMap.put(name, createConcurrentMapCache(name));

이거를 보면 이해가 쉬울 것이다. createConcurrentMapCache를 생성하는데 결국 key, value를 저장가능한 ConcurrentHashMap을 만들어주는 것이다. 그래서 여기에 cache data를 채워나가면 된다.

돌고돌아 다시 bean!


@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties::class)
class CacheConfig {

    @Bean
    fun cacheManager(properties: CacheProperties): CacheManager{
        return ConcurrentMapCacheManager(properties.cacheName)
    }

}

이렇게 등록하면 된다. 나는 아직 cache 바구니 하나만 필요해서 다음과 같이 작성했지만, 여러개 필요하면 여러개 넘기면 된다.

cache사용하기

CachePut과 Cacheable

  • @CachePut
    cache에 key에 대해서 value가 있는지 없는지 여부에 관계없이 key를 정해서 value를 update한다.
  • @Cacheable
    cache에서 key값이 이미 존재한다면 value가 존재할 때 그 값을 불러온다. 이미 존재한다면 그것을 읽어오고 존재하지 않는다면 넣어준다.

처음 사용해봐서 cachePut, cacheable 중에서 무엇을 어떨때 사용해야하나?에 대해서 위주로 찾아봤다.
cachePut은 데이터를 update할 때 사용한다.
내가 cahce를 사용하는 것은 조회수를 위해서였다. 그러려면 페이지 id를 key로 설정하고 value를 view값으로 하려고 했다. 그렇다는 것은 value가 계속 바껴야한다는 것이다. 그러면 cachePut을 update하는 로직에서 사용하고, cache를 database에 저장했다면 모든 key,value가 제거 됐을 것이고 그때 cacheable을 사용해서 key의 value를 0으로 setting해주는 역할로 사용하여야겠다고 생각했다.

delete

delete는 역시 key로 특정 view에 대해서 삭제가 가능한데, 모든 key에 대해서도 삭제가 가능하다.
나는 cacheManager를 사용해서 cache name 기반으로 모두 삭제를 해줬다.

스케줄러를 사용한 DB 업데이트

cron을 사용해서 특정 주기마다 스케줄러에 의해서 thread pool에서 cache의 값을 database에 update하도록 해줬다.

그래서 기존의 thread pool에는 이로인한 영향을 주지 않는 선에서 update를 시킬 수 있었고 cache의 data를 key를 통해서 모두 읽어와서 update시켰다.

그런데 이때 있을법한 method를 내가 찾지 못해서인지,,,직접 정의를 해줄 필요가 있었다. 언제였냐면 key를 넘기지 않아도 value를 가져온다거나 혹은 key만 다 가져오는 것이다. key, value값이 Cache 객체 안에서 저장되어 Cache를 출력할때 보이긴하는데 그안의 값을 가져오지 못했다.
그래서 string으로 바꿔서 직접 key, value 쌍을 map으로 반환해주는 utils를 만들어서 사용했다.

이때, CacheManager에서 getCache method를 사용했는데 name을 넘겨주면 된다.

마무리

이후, 레디스를 도입해서 cache관리를 하려고하는데 미리 공부를 해봐야겠다!!
그리고 이번에 조회수 카운팅 로직에 대해서 생각해보니 사실 cache에 어떤 고객이 어떤 페이지를 많이 클릭했는지 보려면 사용자 id도 같이 담아놓는것도 방법이다. 이것은 또 어떻게 처리해야할까..에 대해서 더 깊게 알아봐야겠다고 생각했다!
기술 블로그...를 뒤져보자 하하하,,,

0개의 댓글