티스토리 뷰
배경
사용자가 인증을 완료하면 서버는 Access 토큰과 Refresh 토큰을 발급한다. 이후 사용자가 로그아웃하면, 해당 토큰을 Blacklist에 등록하게 된다.
이후 사용자가 토큰을 포함해 서버에 요청을 보낼 경우, 서버는 해당 토큰이 유효한지 검증하고 동시에 Blacklist에 등록된 토큰인지도 확인하게 된다.
이러한 흐름에서 토큰의 Blacklist를 Redis와 같은 in-memory DB에서 관리해도 괜찮을지에 대한 의문이 들었고, 이에 대한 고민과 실험 내용을 정리해보았다.
본문
Redis는 휘발성이다?
Redis is the world’s fastest in-memory database.. (Redis 공식 문서)
Redis 공식 문서에서도 알 수 있듯이 Redis는 메모리에 데이터를 저장하는 in-memory DB이다. 덕분에 매우 빠른 데이터 접근 속도를 제공하지만, 설정에 따라 휘발성을 가질 수 있다는 점을 유의해야 한다.
만료기간(TTL)을 설정하지 않으면 영속적인 거 아냐?
Redis에서 키를 저장할 때 TTL(Time To Live)을 설정하지 않으면 만료되지 않고 메모리에 계속 남아 있을 수 있다.

하지만 그렇다고 해서 영속적이라고 보기는 어렵다.
TTL이 없더라도 Redis 서버가 재시작되면 메모리에 있던 데이터는 휘발되어 사라질 수 있다. 또한 Redis는 메모리 사용량이 임계치를 넘을 경우 설정된 eviction 정책에 따라 오래된 데이터를 자동으로 제거할 수 있다. 따라서 TTL이 없다고 해도 데이터가 보존된다는 보장은 없다.
Redis도 영속성을 제공할 수 있다?
많이 알려져 있듯이 Redis는 RDB 스냅샷이나 AOF(Append Only File)와 같은 옵션을 통해 데이터를 디스크에 저장하고, 서버 재시작 시 이를 복구할 수 있는 영속성 기능도 제공한다.
자세한 설정은 Redis 공식 문서 - Persistence를 참고하자.
그렇다면 RDBMS 대신 Redis만 쓰면 안 되는 걸까?
Redis는 빠른 속도와 일정 수준의 영속성을 동시에 제공할 수 있다. 그렇다면 MySQL 같은 RDBMS 대신 전부 Redis로 처리해도 되는 건 아닌가 하는 의문이 생길 수 있다.
결론부터 말하면, 그럴 수 없다. 두 시스템은 목적과 설계 철학이 다르기 때문이다.
Redis는 메모리 기반의 빠른 데이터 접근을 강점으로 하지만, 영속성은 Redis 재시작 시 복구를 보장하기 위한 보조 기능일 뿐이다. 이 과정에서 일부 데이터 유실이 발생할 수 있다.
반면 RDBMS는 ACID 특성을 기반으로 데이터의 무결성과 영속성 보장에 초점을 맞춘다. 또한 스키마 기반 설계, JOIN, 복잡한 트랜잭션 처리 등 Redis가 제공하지 않는 다양한 기능들을 지원한다.
결국 Redis는 RDBMS를 완전히 대체하기보다는, 특정 목적(캐싱, 세션 저장, 블랙리스트 관리 등)에 최적화된 보조 저장소로 쓰이는 경우가 많다.
Redis 메모리 증가량 테스트
토큰 블랙리스트는 여러 테이블에서 참조되지 않고, 단순히 데이터를 추가하고 조회만 하는 구조이기 때문에, 데이터 무결성보다는 빠른 접근 속도가 더 중요하다.
이러한 특성을 고려했을 때, RDBMS보다는 Redis를 사용하는 것이 트레이드오프 관점에서 더 적절해 보였다.
하지만 Redis를 블랙리스트 저장소로 사용할 경우, 키 개수 증가에 따른 메모리 사용량 변화가 시스템에 어떤 영향을 줄 수 있는지 사전에 파악할 필요가 있었다.
시스템의 안정성을 확보하려면, Redis가 감당할 수 있는 데이터 규모를 실험을 통해 예측해보는 것이 중요했다.
이를 위해 Docker 환경에서 Redis를 실행한 후, 초기 상태에서 약 216개의 데이터를 저장하고 메모리 사용량을 측정했다.

이때 메모리 사용량을 확인해보면 41.94Mib이다. 이는 약 44MB이다.

약 1000개의 더미 데이터를 추가한 뒤 메모리 증가량을 확인해보자.


0.06Mib가 증가한 것을 볼 수 있다. 약 0.063 MB가 증가한 것이다.
그렇다면 10,000개의 더미 데이터가 추가되면 어떨까?


처음 41.94MiB에서 43.09MiB가 되었다. MB로 로 바꾸만 약 1.21MB가 증가한 것이다.
대략 1개 키가 추가될 때 마다 121바이트가 증가한다.
이를 표로 정리하면 다음과 같다.
키 개수
|
예상 메모리 사용량 (바이트)
|
예상 메모리 사용량 (MB)
|
1,000
|
121,000
|
약 0.12MB
|
5,000
|
605,000
|
약 0.61MB
|
10,000
|
1,210,000
|
약 1.21MB
|
20,000
|
2,420,000
|
약 2.42MB
|
50,000
|
6,050,000
|
약 6.05MB
|
100,000
|
12,100,000
|
약 12.10MB
|
식으로 계산해보면 다음과 같다. 단순 계산을 위해 1MB를 10^6 바이트라고 가정했다.
이를 기반으로 그래프도 그려보았다. Y축은 메모리 사용량이고 X축은 키 개수이다.

Y축의 단위를 200MB로 확인해봤을 때 기울기가 매우 낮은 것을 확인할 수 있다.
그렇다면 Redis 사용량이 200MB 정도 된다면 몇개의 데이터가 Redis에 추가된걸까?
앞선 식을 역으로 풀면 다음과 같다.
즉, y가 200MB일 때 필요한 키 개수(또는 x값)는
약 165만개이다. 1MB를 10^6 바이트라고 가정하였기에 실제로는 165만개보다 더 많을 것이다.
정리
로그아웃을 한 번 할 때마다 두 개의 키가 블랙리스트에 추가되기 때문에, 이론적으로 약 82,500번의 로그아웃이 발생하면 Redis 사용량이 200MB를 초과하게 된다.
만약 로그아웃이나 토큰 만료 이벤트가 하루에 100번 발생한다고 가정하면, 약 825일 동안 서비스가 운영되었을 때 메모리 사용량이 200MB를 넘을 수 있다는 의미다.
물론 실제 서비스 운영에서는 Redis를 토큰 블랙리스트 외에도 다양한 용도로 사용하기 때문에, 전체 메모리 사용량은 이보다 더 빠르게 증가할 수 있다.
결론
토큰 블랙리스트를 Redis로 관리하는 것은 충분히 현실적인 선택이라고 판단했다.
직접 테스트해본 결과, Redis에 저장된 키 개수가 수만 개에 이르더라도 메모리 사용량의 증가폭은 매우 작았고, 계산상으로도 하루 100번의 로그아웃 이벤트가 발생한다고 가정했을 때 약 825일간은 200MB를 넘지 않는 수준이었다.
서비스 초기에는 사용자 수가 많지 않기 때문에 Redis에 블랙리스트를 영속적으로 저장해도 메모리 부담이 크지 않을 것으로 보인다. 또한 Redis는 빠른 조회 속도를 제공하기 때문에, 토큰 유효성 검사를 자주 해야 하는 블랙리스트 처리에 적합한 성능을 보장한다
.
물론 Redis는 설정에 따라 휘발성을 가질 수 있기 때문에, 필요 시 RDB나 AOF 설정을 통해 최소한의 영속성은 확보하고, 장기적으로는 메모리 사용량을 모니터링하면서 일정 기준을 초과할 경우 RDBMS 등 다른 저장소로의 이전도 고려할 수 있을 것이다.
따라서 현재로서는 Redis를 토큰 블랙리스트 저장소로 사용하고, 지속적인 모니터링을 통해 유연하게 대응하는 전략이 적절하다고 판단된다.
'개발 스토리' 카테고리의 다른 글
레디스 분산 락을 활용한 멀티 서버 환경 푸시 알림 중복 문제 해결 (1 / 2) (2) | 2025.04.10 |
---|---|
비동기 작업을 빠르게 도와주는 코루틴 (0) | 2025.04.05 |
Swap memory 설정법 및 장단점 (4) | 2025.03.18 |
MySQL 원격 연결 에러 및 해결 (feat. GCP 인바운드) (0) | 2025.03.09 |
sh 실행 시 문법 오류 해결법 (0) | 2025.03.09 |
- Total
- Today
- Yesterday
- 환경변수 관리
- 게임개발
- contextwith
- Assertions
- 우아한테크코스 후기
- 레디스
- 우아한테크코스 자소서
- 우테코 6기
- 토큰 블랙리스트
- 스왑 메모리 설정
- redis 메모리 사용량
- gcp 인바운드
- 6기
- 코루틴
- 스프링 api 테스트
- 파이썬
- 레디스 분산락
- 토큰
- 우테코 프리코스
- 우아한테크코스
- setnx
- 우테코 준비
- sh 문법 오류
- redis
- 자바
- 알고리즘
- 우아한테크코스 6기
- 우테코
- JWT
- 스왑 메모리 장단점
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |