캐시란 무엇인가?
캐시는 데이터를 저장하는 임시 저장소로 자주 조회되는 데이터에 빠르게 접근할 수 있도록 하기 위해 사용한다.
메모리 캐시는 DB에서 데이터를 조회하거나 파일 시스템에서 데이터를 가져오는 것보다 훨씬 빠르게 읽어올 수 있다.
서버는 요청을 받으면 데이터를 DB에서 가져오기 전에 캐시에 데이터가 있는지 확인을 먼저하고 데이터가 있다면 바로 캐시에 저장된 데이터를 반환한다. (Cache Hit)
반대로 캐시에 데이터가 없어 DB에 데이터를 요청하여 조회한 후 데이터를 반환한다면 Cache Miss다.
Spring/NestJS Cache 모듈
Spring이나 NestJS에서는 내장된 Cache 모듈을 이용하여 간단하게 캐시를 구현할 수 있다.
기본적으로는 메모리 캐시가 제공되며 Redis와 같은 외부 캐시 시스템과도 쉽게 통합할 수 있다.
Spring/NestJS Cache 사용이 적절한 경우
•
단일 서버 환경
◦
Spring Cache는 기본적으로 어플리캐이션 내에서 메모리 캐시를 제공하므로 단일 서버 환경에서 적절하게 사용할 수 있다.
•
간단한 캐시 적용
◦
@Cacheable, @CacheEvict 를 사용하여 캐시를 쉽게 저장하고 삭제할 수 있다.
◦
특정 메소드가 불릴 때 캐시를 삭제하고 새로 저장해야 하는 간단한 경우 적절한 사용이 될 수 있다.
•
메모리 내 처리
◦
Spring Cache의 경우 JVM 내에서 캐시를 처리하므로 간단한 데이터 캐싱에 있어 성능이 매우 좋다. 네트워크의 지연도 없고 에플리케이션 메모리에서 데이터를 직접 가져온다.
◦
ex) 자주 호출되지만 크기가 작고 자주 변경되지 않은 데이터
•
단순 CRUD 어플리케이션에서 조회 성능을 개선할 경우
Redis 캐시
Redis는 네트워크 기반 인메모리 데이터 저장소이다. 메모리 기반이기 때문에 데이터에 매우 빠르게 접근할 수 있어 캐시 메모리로 사용하기도 한다.
Redis 캐시는 주로 분산환경이나 대용량 트래픽을 처리하는 시스템에서 사용한다.
Redis 캐시 사용이 적절한 경우
•
분산 시스템
◦
여러 서버에서 동일한 캐시 데이터를 공유해야 하는 경우에 Redis는 네트워크 기반이므로 분산된 시스템에서도 동일한 캐시 데이터를 사용할 수 있다.
•
대용량 트래픽 처리
◦
Redis는 메모리 기반이기 때문에 높은 읽기/쓰기 성능을 가지고 있어 대규모의 동시 요청을 효율적으로 처리할 수 있다.
•
세션 관리
◦
Redis는 사용자의 세션 데이터를 저장하고 공유하는 데 매우 적합하다. 여러 서버 간 세션 데이터가 일관되게 유지되기 때문에 로그인 세션같은 정보를 여러 서버에서 공유해야 하는 상황에 적절하다.
•
지속성
◦
Redis는 데이터를 디스크에 저장할 수 있는 옵션이 있어 시스템이 재시작되더라도 캐시된 데이터가 복구될 수 있다.
▪
ex) 일부 중요한 캐시 데이터를 잃어서는 안되는 경우
•
복잡한 데이터 구조 캐싱
◦
Redis는 다양한 자료구조를 지원한다. List, Set, Hash 등 다양한 데이터 구조를 지원하므로 복잡한 데이터를 캐싱하는데 적합하다.
Redis캐시 vs Spring/NestJS 캐시 비교
항목 | Redis 캐시 | Spring Cache |
기본 저장소 | 네트워크 기반 인메모리 데이터 저장소 | JVM 메모리 (내부 캐시) |
데이터 공유 | 네트워크를 통해서 여러 서버간에 데이터 공유가 가능하다 | 서버 내에서만 캐시 사용이 가능하고 외부 캐시를 사용하지 않는 한 서버 간 공유 불가 |
데이터 구조 지원 | 문자열, List, Hash, Set 등 다양한 데이터 구조를 지원한다. | 기본적으로 객체를 캐싱하고 복잡한 데이터 구조를 지원하지 않는다. |
분산 환경 지원 | 네트워크 기반이므로 분산 환경에 적합하다 | 기본적으로는 단일 서버 환경에 적합하다. |
성능 | 메모리 기반이므로 속도는 매우 빠르나 네트워크를 통해 접근하므로 네트워크 지연이 발생할 수 있음. | 애플리케이션 메모리 내에서 캐시를 처리하므로 네트워크 지연이 발생하지 않는다. |
확장성 | 클러스터링을 통해 수평적 확장이 가능하다 | JVM메모리에 종속적이므로 확장성이 좋지 않음 |
TTL 설정 | TTL을 통해 캐시의 유효 기간을 설정할 수 있다. | 기본 메모리 캐시에서는 TTL 설정이 불가능하다. |
복구 기능 | 영속성을 지원하므로 데이터 복구가 가능하다. | JVM이 종료되면 복구할 수 없다. |
캐시 일관성 관리 | 캐시 무효화 전략, 분산 락을 통해 캐시의 일관성을 관리할 수 있다. | @CacheEvict 같은 어노테이션으로 캐시의 일관성을 간단하게 관리할 수 있다. |
사용 예시 | 대규모 분산 어플리케이션, 실시간 데이터 처리 | 단일 서버에서 간단하게 캐시를 적용, 어노테이션으로 메소드 레벨 캐싱을 하려는 경우 (@CacheEvict) |
Redis에 대한 추가적인 의문…
Redis는 메모리 기반이라 I/O성능이 좋다. 하지만 싱글 스레드 기반이고 네트워크 기반이기 때문에 대용량 트래픽이 있을 경우에 대해서 왜 적합한지 의문이 들었다.
왜 Redis는 대용량 트래픽 처리에 알맞을까?
1. 비동기 I/O 모델
Redis는 싱글 스레드 기반이지만 비동기 I/O 모델을 사용하여 빠르고 효율적으로 요청을 처리한다. 하드디스크나 데이터베이스의 입출력과 다르게 Redis는 모든 데이터를 메모리에 쓰고 읽기 때문에 속도가 매우 빠르다.
또한 Redis는 단일 스레드에서 이벤트 기반으로 클라이언트의 요청을 처리하기 때문에 각 요청은 매우 빠르게 처리되어 병렬 처리가 필요하지 않은 경우가 많다.
2. 싱글 스레드가 성능을 저하시킨다?
‘싱글 스레드’ 라는 단어를 들으면 병목 현상이 떠오르게 된다.
하지만 Redis의 싱글 스레드 아키텍쳐는 CPU 바운드 작업이 아닌 메모리 바운드 작업에 최적화 되어 있다.
복잡한 계산을 수행하는 것이 아니라 단순히 메모리에 읽고 쓰는 작업이 주를 이루기 때문에 대부분의 경우 CPU 사용량이 적다. 따라서 Redis는 싱글 스레드 기반이지만 고성능으로 동작할 수 있다.
3. 빠른 처리 속도
Redis는 초당 수십만 건 이상의 요청을 처리할 수 있으며, 대부분의 요청은 1ms 이하의 응답 시간을 보장한다.
이는 Redis가 싱글 스레드 기반이지만 고성능을 낼 수 있는 이유 중 하나이다.
Redis는 어떻게 대용량 트래픽을 처리할까?
1. Redis 클러스터링
Redis 클러스터는 데이터가 여러 노드에 분산되어 저장되어있기 때문에 수평적 확장을 지원한다.
이를 통해서 특정 노드에 부하가 집중되지 않고 Redis의 여러 인스턴스에 분산되어 처리되도록 할 수 있다.
2. 파이프라이닝
Redis는 매 요청마다 서버로 보내지 않고 여러 명령어들을 묶어서 서버로 한번에 보낼 수 있다.
이를 통해 네트워크 왕복을 줄여 성능을 향상시킬 수 있다. 이는 병철 처리와 유사한 효과를 내며 대용량 트래픽을 효율적으로 처리할 수 있다.
3. 복제 (Master-Slave)
Redis는 하나의 Master 서버가 데이터를 관리하고 Master 서버를 복제한 여러 대의 Slave 서버를 통해 읽기 요청을 처리한다.
이를 통해 읽기 요청 트래픽을 Slave 서버로 분산시킬 수 있어 읽기 성능을 극대화 할 수 있다.