📖 Table of Contents
포스팅의 목차입니다.
- 캐시는 무엇인가요?
- LOCAL 캐시와 GLOBAL 캐시
- ElastiCache 무엇인가요??
- 캐시 적용하기!
안녕하세요. 올디브에서 Back-End 개발 업무를 맡은 핸들러입니다. 현재 올디브에서는 다양한 프로젝트를 스쿼드 단위로 진행하고 있습니다.
그중에서 저는 무형상품을 판매하기 위한 서비스를 준비 중인 다한다 스쿼드에 속해있는데요.!
이 글에서는 무형상품 서비스에 어떻게 캐시를 적용했는지 설명해 드리고자 합니다.
그럼 먼저 캐시가 무엇인지에 대해 알아볼까요~!? 😃 🫢
캐시는 무엇인가요?
위키백과에서는 캐시를 다음과 같이 설명합니다.
캐시(cache, 문화어: 캐쉬, 고속완충기, 고속완충기억기)는 컴퓨터 과학에서 데이터나 값을 미리 복사해 놓는 임시 장소를 가리킨다. 캐시는 캐시의 접근 시간에 비해 원래 데이터를 접근하는 시간이 오래 걸리는 경우나 값을 다시 계산하는 시간을 절약하고 싶은 경우에 사용한다. 캐시에 데이터를 미리 복사해 놓으면 계산이나 접근 시간 없이 더 빠른 속도로 데이터에 접근할 수 있다. 캐시는 시스템의 효율성을 위해 여러 분야에서 두루 쓰이고 있다.
정의는 항상 어려운 것 같습니다. 조금 더 쉽게 설명해 드리자면 캐시는 일시적인 특징이 있는 데이터나 값을 복사하여 저장하는 저장소를 가리킵니다.
일반적으로 데이터를 영구적으로 저장하는 값싸고 큰 용량의 기억장치에서 특별히 선별한 몇몇 데이터를 가져와 RAM(RANDOM ACCESS MEMORY)와 같이
빠르게 접근할 수 있는 기억장치에 저장해놓고 데이터 검색 속도를 높여 시스템의 성능을 개선하는 데 목적이 있습니다~
아래 그림은 흔히 볼 수 있는 데이터 저장공간의 계층 구조입니다.
데이터 저장공간의 계층을 살펴보면, 위로 올라갈수록 기억 용량이 작고 가격이 비싼 대신 빠른 성능을 보이는 것을 알 수 있습니다.
아무리 성능이 좋아도 많은 데이터를 메모리에 저장하여 조회하는 것은 비효율적이고 너무나도 큰 비용이 들기 마련입니다. (가성비가.. ㅠㅠ)
따라서! 저장할 수 있는 공간이 작고 가격이 비싸기 때문에 특별한 성격을 가진 데이터를 선별하여 캐시에 저장하는 것이
캐시 적중률(HIT)을 높일 수 있어 성공적인 캐싱이라 할 수 있습니다.
Tip
- Cache HIT : 요청하는 데이터가 캐시 저장소에 존재하는 경우
- Cache MISS : 요청하는 데이터가 캐시 저장소에 존재하지 않은 경우
LOCAL 캐시와 GLOBAL 캐시
로컬 캐시와 글로벌 캐시는 저장소의 위치를 구분하기 위한 방법으로 표현됩니다.
먼저 로컬 캐시는 가용되는 여러 서버마다 캐시를 저장합니다. 각 서버의 자원(Memory, Disk)를 사용합니다.
글로벌 캐시는 하나의 캐시 서버를 구축하여 캐시를 저장합니다. 가용되는 여러 서버마다 하나의 캐시 서버에 접근하여 캐시 데이터를 조회합니다.
다음 표는 중요한 특징만 간단하게 정리한 것입니다.
LOCAL 캐시 | GLOBAL 캐시 | |
---|---|---|
저장소 | 각 서버의 메모리 | 하나의 캐시 서버 |
속도 | 해당 서버의 데이터를 사용하기 때문에 빠름 | 네트워크 트래픽이 발생하므로 상대적으로 LOCAL 캐시보다 느림 |
데이터 공유 | 각 서버 마다 데이터가 저장되기 때문에 데이터 공유가 힘들다 | 별도의 캐시 서버로 존재하기 때문에 데이터 공유가 쉽다 |
ElastiCache는 무엇인가요??
LOCAL 캐시와 GLOBAL 캐시에 대해 알아보았는데요. 우리 무형상품은 사용자 요청의 트래픽을 분산시키기 위해 다중 서버를 구성합니다.
이런 상황에서 만약 LOCAL 캐시를 선택한다면, 서버마다 캐시저장소를 가지고 있으므로 서버 간 캐시 데이터를 동기화해야 하는 추가적인 Replication 작업이
필요할 수 있고 서버마다 동일한 캐시 데이터를 저장할 수 있으므로 비효율적인데요. 그래서 우리는 사용자 트래픽에 따라 Auto Scaling을 하기 위해 여러 서버
에서 캐시 서버에 접근하여 데이터를 참조할 수 있도록 GLOBAL CACHE를 사용하기로 하였습니다.
그래서 선택한 것이 바로! AWS에서 제공하는 Elaticache입니다. AWS에서는 Elaticache를 다음과 같이 설명합니다.
Amazon ElastiCache를 사용하면 AWS 클라우드에서 분산 인메모리 캐시 환경을 쉽게 설정, 관리 및 확장할 수 있습니다. 고성능, 크기 조정 가능 및 비용 효율적인 인메모리 캐시를 제공하는 동시에 분산 캐시 환경 배포 및 관리와 관련된 복잡성을 제거합니다. ElastiCache는 Redis 및 Memcached 엔진 모두에서 작동합니다. Redis용 Amazon ElastiCache는 인터넷 규모의 실시간 애플리케이션을 지원할 수 있도록 1밀리초 미만의 지연 시간을 제공하는 놀랍도록 빠른 인 메모리 데이터 스토어입니다. 오픈 소스 Redis를 기반으로 구축되고 Redis API와 호환되는 Redis용 ElastiCache는 Redis 클라이언트와 연동되며 개방형 Redis 데이터 형식을 사용하여 데이터를 저장합니다.
쉽게 설명해 드리자면 Redis 및 Memcached 엔진을 지원하는 IN MEMORY 기반의 데이터 저장소 환경을 쉽게 설정하고 관리할 수 있는
완전 관리형 서비스입니다. 무형상품에서는 캐시 외 다른 기능에서도 다양한 자료구조와 트랜잭션을 적용하여 IN MEMORY 데이터 저장소를
활용해야 하므로 REDIS 엔진을 선택하기로 했습니다.!
캐시 적용하기!
본격적인 캐시 적용을 시작하기에 앞서 우리가 사용하려는 look-aside(cache-aside) 캐싱 전략을 시퀀스 다이어그램으로 표현하면 다음과 같습니다.
클라이언트의 요청을 받으면 해당 데이터가 캐시저장소에 있는지 먼저 검사하고 그 결과에 따라 다음 작업이 결정됩니다.
우리 무형상품 서비스는 Spring Boot 2와 kotlin을 사용하고 있습니다. 위와 같은 구조를 구현하기 위해서는 캐싱을 적용하려는 서비스에
매번 캐시가 있는지 먼저 확인하고 있으면 그 값을 반환, 그렇지 않으면 영속성 데이터를 조회하여 캐시저장소에 저장해야 합니다. (반복되는 코드 ㅠㅠ)
하지만! 스프링 프레임워크의 버전 3.1부터 캐시 추상화를 위한 org.springframework.cache.annotation @Cacheable
어노테이션을 제공합니다.
위 어노테이션이 선언된 메소드는 그 결과를 캐싱할 수 있음을 의미합니다. 그럼 코드를 살펴볼까요?
- 우선 의존성을 추가해야 합니다!
implementation("org.springframework.boot:spring-boot-starter-data-redis")
- 다음으로는 CacheManager를 구현해야 합니다. 위에서 설명해 드렸듯이,
@Cacheable
은 캐시 추상화를 위한 어노테이션이라고 말씀드렸는데요.
ElastiCache for redis를 사용하기 위한 RedisCacheManager 구현체를 만들어 스프링 컨테이너의 빈으로 지정해야 합니다.
RedisCacheManager는 RedisCacheConfiguration.defaultCacheConfig()
를 사용하여 캐싱에 적용되는
NULL 값 캐싱 유무, 캐싱 키 prefix, 이진 직렬화, 캐시 만료시간(TTL) 등과 같은 부가적인 설정을 커스터마이징 할 수 있습니다.
다음은 간략한 RedisCacheManager의 구현 코드입니다.
@Configuration
@EnableCaching
class RedisCacheConfig (
val redisClusterConnectionFactory : RedisConnectionFactory //커넥션 정보를 담고 있는 RedisConnectionFactory
) {
...
@Bean
fun goodsCacheManager(): RedisCacheManager {
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisClusterConnectionFactory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig() ...(생략) ) //RedisCacheConfiguration ...
.build()
}
...
}
@Cacheable
어노테이션 선언
이제 캐시를 적용할 메소드에 @Cacheable
어노테이션을 지정하면 됩니다. 우리는 자주 사용하고 변경이 많지 않은 상품 데이터를 캐싱하기 위해
상품 조회 메소드에 지정하겠습니다. @Cacheable
어노테이션의 속성 및 코드는 다음과 같습니다.
@Cacheable(value = ["goodsCache"], cacheManager = "goodsCacheManager", key = "#goodsCode")
override fun findGood(goodsCode: String): Goods { ... 영속성 데이터 조회 ... }
Element | Discription |
---|---|
value | 캐시가 저장되는 키 값의 이름 |
cacheManager | 사용자가 직접 지정한 cacheManager |
key | 캐싱 대상을 지정하는 키 값을 동적으로 사용하기 위한 SpEL(Spring Expression Language) 표현식 |
실제로 캐시를 조회하고 반환 그리고 저장하는 코드는 찾아볼 수 없습니다. @Cacheable
어노테이션이 선언된 퍼블릭 메소드는
자주 볼 수 있는 @Transaction
어노테이션이 선언된 메소드와 유사하게 작동합니다. 스프링 AOP proxy 기반으로써 메소드를 호출할 때 proxy 객체가
호출을 가로채어 캐시를 조회하고 반환 그리고 저장하게 됩니다. 위 시퀀스 다이어그램에서 proxy 객체가 담당하고 있는 영역을 표시하면 다음과 같습니다.
마무리
오늘은 ElastiCache와 SpringBoot2를 활용한 캐시 적용하기를 포스팅 해봤습니다. 다음번엔 더 알차고 재미있는 이야기를 공유해
드리도록 하겠습니다. 긴 글 읽어주셔서 감사합니다. 😃