📍레디스(Redis)
Redis는 Remote(원격)에 위치하고 프로세스로 존재하는 In-Memory 기반의 Dictionary(key-value) 구조 데이터 관리 Server 시스템이다.
여기서 key-value 구조 데이터란, mysql 같은 관계형 데이터가 아닌 비 관계형 구조로서 데이터를 그저 '키-값' 형태로 단순하게 저장하는 구조를 말한다.
그래서 관계형 데이터베이스와 같이 쿼리 연산을 지원하지 않지만, 대신 데이터의 고속 읽기와 쓰기에 최적화 되어 있다.
그래서 Redis는 일종의 NoSQL 로 분류되기도 한다.
또한 Redis는 인 메모리(In-Memory) 솔루션으로도 분류되기도 하는데, 다양한 데이터 구조체를 지원함으 로써 DB, Cache, Message Queue, Shared Memory 용도로 사용될 수 있다.
일반 데이터베이스 같이 디스크(ssd)에 데이터를 쓰는 구조가 아니라 메모리(dram)에서 데이터를 처리하기 때문에 작업 속도가 상당히 빠르다.
즉, 외부 저장 장치를 사용했다면 메모리와 외부 저장 장치와의 병목 현상이 발생했겠지만, 메모리만 사용하기 때문에 데이터 저장 속도가 매우 빠르다.
더해서 Redis가 인기 있는 이유는 대부분의 프로그래밍 언어와 프레임워크에 대한 API를 제공하기 때문!
📍레디스(Redis) 캐시(Cache)로 활용하기
Cache란 한번 조회된 데이터를 미리 특정 공간에 저장해놓고, 똑같은 요청이 발생하게 되면 서버에게 다시 요청하지 말고 저장해놓은 데이터를 제공해서 빠르게 서비스를 제공해주는 것을 의미한다.
즉, 미리 결과를 저장하고 나중에 요청이 오면 그 요청에 대해서 DB 또는 API를 참조하지 않고 Cache를 접 근하여 요청을 처리하는 기법이다.
보통 DB에 부하가 오게 되면 스케일 인 혹은 스케일 아웃하는 방식으로 구현하지만, 캐시 서버도 검토하게 된다.
Redis Cache는 메모리 단에 위치하기 때문에 용량은 적지만 접근 속도가 빠르다.
‼️다만 저장하려는 데이터 셋이 주어진 메모리 크기보다 크면 디스크를 쓰는 것이 올바른 선택이다.
📍레디스(Redis) 캐시(Cache)구조의 패턴
- Look aside Cache 패턴
look aside cache는 캐시를 한 번 접근하여 데이터가 있는지 판단한 후, 있다면 캐시의 데이터를 사용하고 없으면 실제 DB 또는 API를 호출한다.
1. 클라이언트에서 데이터 요청
2. 서버에서 캐시에 데이터 존재 유무 확인
3. 데이터가 있다면 캐시의 데이터 사용 (빠른 조회)
4. 데이터가 없다면 실제 DB 데이터에 접근
5. 그리고 DB에서 가져 온 데이터를 캐시에 저장하고 클라이언트에 반환
- Write Back 패턴
write back은 주로 쓰기 작업이 굉장히 많아서, INSERT 쿼리를 일일이 날리지 않고 한꺼번에 배치 처리를 하기 위해 사용한다.
- 센서나 앱에서 엄청 많은 데이터를 계속 보내면, DB가 바로바로 저장하느라 힘들어진다.
- 그래서 먼저 빠른 메모리 저장소(캐시)에 잠깐 모아 둔다.
- 일정 시간(예: 5초)이나 개수(예: 1000개)가 되면 한 번에 DB에 저장한다.
- 이렇게 하면 DB가 쉴 틈 없이 바쁘지 않고, 훨씬 빨라진다.
- 이런 방식을 Write-back이라고 부른다.
하지만 단점도 있다.
DB에서 디스크를 접근하는 횟수가 줄어들기 때문에 성능 향상을 기대할 수 있지만, DB에 데이터를 저장하기 전에 캐시 서버가 죽으면 데이터가 유실된다는 문제점이 있다.
그래서 다시 재생 가능한 데이터나, 극단적으로 heavy 한 데이터에서 write back 방식을 많이 사용한다.
예를 들면 로그를 캐시에 저장하고 특정 시점에 DB 에 한번에 저장하는 경우가 있다.
우선 모든 데이터를 캐시에 싹 저장
캐시의 데이터를 일정 주기마다 DB에 한꺼번에 저장
그리고나선 DB에 저장했으니 잔존 데이터를 캐시에서 제거
📍레디스(Redis) 캐시(Cache) 활용 사례
- 세션 스토어(Session Store)
이슈는 서버 분산 처리 환경에서의 세션 불일치..
보통 실무에서는 트래픽 부하를 방지하기 위해 로드밸런서에 서버를 여러대 운영한다.
그러나 서버를 여러대를 운영하게 되면 클라이언트의 세션이 서로 서버마다 달라 서비스 이용에 지장을 줄 수 있다는 문제점을 가지게 된다.
이 문제를 아래와 같은 순서로 해결한다.
- 로그인 성공 시 서버가 세션 데이터를 Redis에 저장하고, 클라이언트에 세션 ID 쿠키만 내려줌.
- 이후 어떤 서버로 요청이 가도, 서버는 쿠키의 세션 ID로 Redis에서 사용자 정보를 조회함.
- 로그아웃/세션 만료도 Redis에서 처리되므로 모든 서버에 동시에 반영됨.
그럼 세션 스토리지로서 적합한 데이터 베이스는 무엇일까?
데이터베이스의 데이터가 어느 공간에 저장이 되는가에 따라서 In-memory DB와 Disk based DB로 분류된다.
쉽게 말해서 In-memeory DB는 지금까지 말한 Redis이고, Disk based DB는 관계형 DB인 MySQL과 같은 DB다.
전자는 데이터를 메모리에 저장하여 관리하고, 후자는 데이터를 디스크에 저장하여 관리한다.
이제 상황을 생각해보자.
인가된 사용자만 접근할 수 있는 요청을 처리할때마다 매번 세션 저장소에서 해당 로그인 세션이 존재하는지 확인하는 작업을 진행해야 하기 때문에 성능에 악영향을 주지 않도록 빠르게 세션 정보를 찾아서 제공해야 한다.
Disk based DB는 디스크에서 데이터를 찾아 페이지 단위로 버퍼로 전송하는 시간이 발생하기 때문에 여러 I/O 작업 처리에 있어서 병목 현상이 발생하게 된다.
이러한 이유로 세션이 존재하는지 확인하는 작업을 여러번 반복해야 하는 서비스에서 세션 스토리지로 Disk based DB를 사용하는것은 성능적인 측면에서 올바른 선택이 아니라고 생각된다.
반면, In-memory DB 는 애초에 모든 데이터를 메모리에 저장하기 때문에 Disk I/O 작업이 발생할 일이 없다. 따라서 디스크 기반의 데이터베이스에서 발생하는 병목 현상을 피할 수 있다.
거기다 세션에 데이터 유실로 인해 발생하는 피해가 다른 데이터에 비해 적다.(대표적으로 지금 로그인한 유저만 기록해 둠) 세션 저장소의 데이터가 유실된다면 사용자는 재 로그인만 진행하면 된다.
그래서 세션 스토리지로 In-memory DB의 사용이 적절하다.
- 동시성 제어
웹 서비스에서 특정 기능을 동시에 여러 사용자가 실행하면, 중복 처리나 데이터 꼬임 같은 문제가 발생할 수 있다.
이걸 바로 동시성 문제(Concurrency Issue) 라고 부른다.
예를 들어,
- 쇼핑몰에서 남은 재고가 1개인데 두 명이 동시에 결제 버튼을 누르면 → 재고가 마이너스(-1) 되는 상황
- 이벤트 페이지에서 "선착순 100명 쿠폰"을 발급하는데, 동시에 여러 명이 요청해서 105명에게 발급되는 상황
이런 문제를 해결하려면 "잠깐, 나 먼저 처리하고 그다음에 해!" 라는 락(Lock) 개념이 필요하게 되며, 이 락을 빠르게 처리할 수 있는 도구가 바로 Redis.
정확한 방법으로는 분산 락(Distributed Lock)을 사용한다.
Redis는 SETNX(SET if Not Exists) 명령으로 "이 키가 없을 때만 값을 설정"할 수 있습니다.
이를 이용하면 하나의 자원에 대해서 오직 한 명만 락을 가질 수 있게 된다.
- 결제 요청이 들어오면 Redis에 lock:product123 키를 생성.
- 키가 이미 있으면 → 다른 요청은 "잠시 대기"하거나 "실패" 처리.
- 작업이 끝나면 키를 삭제 → 다음 요청이 락을 획득 가능.
- 또한 서버가 오류나서 락이 영원히 안 풀리는 상황을 막기 위해 TTL도 설정해 준다. ex) 3초
이 외에도 메세지 브로커/큐 시스템과 실시간 순위표(레디스 내 자동 정렬이 매우 빠름) 등등 다양한 활용 사례가 있다.
📍Redis vs Memcached
메모리 디비로는 Redis 와 Memcached 가 있다.
두 개의 공통점으로는 In Memory 저장소 라는 점과 Key-value 의 저장 방식을 가지고 있다는 점이다.
- Redis: 단순 캐시뿐 아니라 세션, 큐, 동시성 제어, 랭킹 등 기능이 다양해야 할 때.
- Memcached: 아주 단순한 Key-Value 캐시, 초고속 읽기/쓰기만 필요할 때.
잘 선택하서 사용하자.
📍Redis의 문제점 (주의해야 할점)
- 시간 복잡도
레디스는 자바스크립트와 같이 싱글 쓰레드 기반으로 돌아간다.
그래서 한 번에 딱 하나의 명령어만 실행하기 때문에, 긴 처리시간이 필요한 명령어를 쓰면 불리하고 요청 건을 처리하기 전까지 다른 서비스 요청을 받아들일수 없고 서버가 다운 되는 현상이 일어 날 수 있다.
따라서 전체 데이터를 다루는 시간복잡도를 가진 O(N) 명령어 keys flush getall 는 주의해서 사용할 필요가 있다.
- 메모리 파편화
메모리를 쓰다 지우다 반복하면, 사용하지 않는 작은 틈(빈 공간)이 메모리 여기저기에 흩어진다.
이 틈들이 합치면 꽤 큰 공간인데, 물리적으로 붙어 있지 않아서 Redis가 재활용을 잘 못 하는 현상을 말한다.
쓰기 연산이 copy on wirte 방식으로 동작하기 때문에 최대 메모리를 2배 이상까지 사용하기도 한다.
그래서 redis를 사용할때 메모리를 적당히 여유있게 사용하는 것이 좋다.
- 캐시 스템피드(Cache Stampede)
캐시 데이터가 만료되었을 때, 다수의 요청이 동시에 DB로 몰려가서 부하가 폭증하는 현상이다.
인기 상품 상세 페이지 캐시의 TTL이 10분으로 설정되어 있는데, 만료 시각에 맞춰 수천 명의 사용자가 동시에 요청을 한다.
캐시에 데이터가 없으니 모든 요청이 DB로 향해서 DB가 순간적으로 감당 못 하고 응답 지연 또는 다운되는 경우다.
TTL이 끝나기 전에 백그라운드에서 미리 새 데이터를 캐시에 저장하는 방법과 분산락을 잠시 걸어서 해결할 수 있다.