coredot.today
ElastiCache와 Redis 완전 정복: '기억력이 좋은' 초고속 메모리 저장소
블로그로 돌아가기
ElastiCacheRedisMemcachedAWS캐시세션성능

ElastiCache와 Redis 완전 정복: '기억력이 좋은' 초고속 메모리 저장소

왜 어떤 페이지는 2초 만에 열리고, 어떤 페이지는 10초씩 걸릴까? 그 비밀은 '캐시'에 있다. Redis의 탄생부터 AWS ElastiCache의 실전 아키텍처, 캐시 전략, 그리고 2024년 라이선스 논쟁까지 — 클라우드 시대의 필수 기술을 완전 정복한다.

코어닷투데이2026-02-0178

들어가며: 이 페이지는 왜 2초 만에 열릴까?

당신이 쇼핑몰에서 인기 상품 목록을 클릭한다. 페이지가 0.3초 만에 뜬다. 그런데 같은 쇼핑몰에서 "6개월 전 구매 내역"을 조회하면 5초가 걸린다. 왜 그럴까?

비밀은 간단하다. 인기 상품 목록은 캐시(Cache)에 올라가 있고, 6개월 전 구매 내역은 디스크에 저장된 데이터베이스를 직접 뒤져야 하기 때문이다.

응답 시간 비교 (밀리초, 낮을수록 빠름)
Redis 캐시
~1 ms
RDS (인덱스)
~10 ms
RDS (풀스캔)
~500 ms
디스크 I/O
~5,000 ms

메모리에서 데이터를 읽는 속도는 디스크보다 수백~수천 배 빠르다. 이 간단한 물리 법칙 위에 구축된 기술이 바로 인메모리 캐시(In-Memory Cache)이고, 그 세계의 절대 강자가 Redis다. 그리고 AWS에서 Redis를 가장 쉽게 운영하는 방법이 ElastiCache다.

이 글에서는 캐시의 개념부터 Redis의 자료구조, AWS ElastiCache의 실전 아키텍처, 캐시 전략, 그리고 최근의 라이선스 논쟁까지 — 캐시의 모든 것을 다룬다.


1. 캐시란 무엇인가: 도서관 vs 책상 위

사무실에서 먼 창고까지 걸어가는 것과 바로 옆 서랍에서 꺼내는 것의 속도 차이를 비교하는 캐시 개념 일러스트

비유로 이해하기

캐싱을 이해하는 가장 좋은 비유는 도서관이다.

1F
데이터베이스 = 도서관 서고
수백만 권의 책이 있다. 무엇이든 찾을 수 있지만, 서고까지 걸어가서 책을 꺼내오려면 **시간이 걸린다.** 검색 카드를 뒤지고, 서가를 찾아가고, 책을 뽑아오는 전체 과정이 데이터베이스 쿼리다.
UP
캐시 = 내 책상 위
자주 보는 참고서 3~4권을 책상 위에 올려둔다. 필요할 때 **손만 뻗으면** 바로 펼칠 수 있다. 대신 책상 공간은 제한되어 있으니, 정말 자주 보는 책만 올려둬야 한다.
GO
결과 = 수천 배의 속도 차이
서고 왕복 5분 vs 책상 위 0.5초. 컴퓨터에서도 마찬가지다. 디스크(서고)에서 읽으면 밀리초~초 단위, 메모리(책상)에서 읽으면 **마이크로초** 단위다.

기술적 정의

캐시(Cache)는 자주 접근하는 데이터를 빠른 저장소에 임시로 복사해 두는 기법이다. 핵심 원리는 "데이터 지역성(Data Locality)" — 최근에 접근한 데이터는 다시 접근될 가능성이 높다는 통계적 사실에 기반한다.

캐시는 컴퓨터 과학 전반에 걸쳐 존재한다:

  • CPU L1/L2/L3 캐시 — 메인 메모리보다 빠른 SRAM 칩
  • 브라우저 캐시 — 한 번 받은 이미지/CSS를 로컬에 저장
  • CDN 캐시 — 전 세계 에지 서버에 콘텐츠 복사본 배치
  • 애플리케이션 캐시 — 바로 이 글의 주제. Redis, Memcached 같은 인메모리 저장소

이 글에서 다루는 캐시는 마지막 항목, 즉 애플리케이션 레벨의 인메모리 캐시다.


2. Redis: 이탈리아에서 태어난 메모리 저장소

String, List, Set, Sorted Set, Hash 등 Redis 자료구조를 각기 다른 모양의 귀여운 컨테이너로 표현한 일러스트

탄생 이야기

2009년, 이탈리아 시칠리아의 개발자 살바토레 산필리포(Salvatore Sanfilippo)는 자신이 만든 웹 분석 서비스 LLOOGG에서 성능 문제에 부딪혔다. MySQL로는 실시간 로그 집계가 너무 느렸다. 그는 생각했다 — "데이터를 전부 메모리에 올리면 어떨까?"

그렇게 만든 것이 Redis(Remote Dictionary Server)다. 이름에서 알 수 있듯이, 원래는 단순한 "원격 딕셔너리(키-값 저장소)"였다. 하지만 산필리포는 단순한 키-값 저장소에 만족하지 않았다. 그는 다양한 자료구조를 네이티브로 지원하는 방향으로 Redis를 발전시켰고, 이것이 Redis를 다른 캐시 솔루션과 근본적으로 차별화했다.

"나는 MySQL이 하는 모든 것을 메모리에서 하고 싶었다. 하지만 관계형 모델 대신, 개발자들이 실제로 코드에서 사용하는 자료구조를 그대로 제공하고 싶었다."
— Salvatore Sanfilippo

핵심 철학

Redis의 설계 철학은 명확하다:

  1. 모든 데이터는 메모리에 산다 — 디스크는 영속성을 위한 백업일 뿐
  2. 다양한 자료구조를 네이티브 지원 — 문자열만이 아니라 리스트, 셋, 해시 등
  3. 단순하고 빠르다 — 싱글 스레드 이벤트 루프로 초당 수십만 명령 처리
  4. 아토믹 연산 — 모든 명령이 원자적으로 실행된다

Redis의 5대 자료구조

Redis가 단순한 캐시가 아닌 "데이터 구조 서버"라 불리는 이유가 바로 이 자료구조들이다.

자료구조타입사용 예시핵심 명령
Strings키-값 쌍세션 토큰, 캐시, 카운터GET, SET, INCR
Lists순서 있는 문자열 목록메시지 큐, 최근 활동 로그LPUSH, RPOP, LRANGE
Sets중복 없는 문자열 집합태그, 고유 방문자 추적SADD, SMEMBERS, SINTER
Sorted Sets점수로 정렬된 집합리더보드, 랭킹ZADD, ZRANGE, ZRANK
Hashes필드-값 쌍의 맵사용자 프로필, 상품 정보HSET, HGET, HGETALL

각각을 실제 사용 예시와 함께 살펴보자.

Strings — 가장 기본적인 타입이다. 키 하나에 문자열 하나를 저장한다. 하지만 "문자열"이라고 얕보면 안 된다. 숫자, JSON, 직렬화된 객체, 심지어 이미지 바이너리까지 저장할 수 있다. INCR 명령으로 원자적 카운터를 만들 수 있어서, 좋아요 수나 페이지뷰 카운팅에 많이 쓴다.

Strings 예시 — 세션 캐시
SET (저장)
SET session:user:1234 "{name: '김개발', role: 'admin'}" EX 3600
→ 사용자 1234의 세션을 저장하고, 1시간 후 자동 만료
GET (조회)
GET session:user:1234
→ "{name: '김개발', role: 'admin'}" (0.1ms 이내 응답)

Lists — 양방향 연결 리스트다. 앞(LPUSH)이나 뒤(RPUSH)에 O(1)으로 삽입할 수 있다. 메시지 큐를 직접 구현하거나, "최근 10개 알림"처럼 고정 길이의 최신 데이터를 유지하는 데 적합하다.

Sets — 수학의 집합과 동일하다. 중복을 자동으로 제거하고, 합집합(SUNION), 교집합(SINTER), 차집합(SDIFF)을 네이티브로 지원한다. "상품 A를 본 사람과 상품 B를 본 사람의 교집합"을 한 줄의 명령으로 구할 수 있다.

Sorted Sets — Redis의 가장 독창적인 자료구조. 각 멤버에 점수(score)가 부여되고, 자동으로 점수 순으로 정렬된다. 게임 리더보드가 대표적인 사용 사례다.

Sorted Set 예시 — 게임 리더보드
ZADD (점수 등록)
ZADD leaderboard 9500 "player:김전사"
ZADD leaderboard 8700 "player:이마법사"
ZADD leaderboard 9200 "player:박궁수"
ZREVRANGE (상위 랭킹 조회)
ZREVRANGE leaderboard 0 2 WITHSCORES
→ 1위: 김전사(9500) / 2위: 박궁수(9200) / 3위: 이마법사(8700)
ZRANK (특정 유저 순위)
ZREVRANK leaderboard "player:이마법사"
→ 2 (0부터 시작하므로 3위) — O(log N) 시간 복잡도

Hashes — 하나의 키 안에 여러 필드-값 쌍을 저장한다. 관계형 DB의 한 행(row)과 비슷한 구조다. 사용자 프로필처럼 여러 속성을 가진 객체를 저장할 때, JSON 문자열로 직렬화하는 것보다 효율적이다. 개별 필드만 업데이트할 수 있기 때문이다.


3. Redis vs Memcached: 무엇을 선택할 것인가

캐시 이야기를 하면 반드시 등장하는 비교 대상이 Memcached다. 둘 다 인메모리 키-값 저장소이지만, 철학과 기능에서 큰 차이가 있다.

간략한 역사

Memcached는 2003년 브래드 피츠패트릭(Brad Fitzpatrick)이 LiveJournal(블로그 플랫폼)의 데이터베이스 부하를 줄이기 위해 만들었다. Redis보다 6년 먼저 태어난 선배다. 단순한 키-값 캐시라는 하나의 일을 극도로 잘 수행하도록 설계되었다.

Redis는 2009년에 등장하여, Memcached가 하지 못하는 것들 — 다양한 자료구조, 영속성, 복제, Pub/Sub 등 — 을 채워 나가며 시장을 빠르게 잠식했다.

비교 항목RedisMemcached
자료구조Strings, Lists, Sets, Hashes, Sorted Sets, Streams 등Strings만
영속성RDB 스냅샷 + AOF 로그없음 (순수 캐시)
복제마스터-레플리카 내장없음
클러스터링Redis Cluster (자동 샤딩)클라이언트 측 샤딩
Pub/Sub네이티브 지원없음
스레딩 모델싱글 스레드 (I/O 멀티플렉싱)멀티 스레드
최대 값 크기512 MB1 MB (기본)
Lua 스크립팅지원없음
트랜잭션MULTI/EXECCAS만
메모리 효율메타데이터 오버헤드 있음단순 키-값에서 더 효율적

그래서 뭘 쓸까?

결론부터 말하면, 대부분의 경우 Redis가 정답이다. Memcached가 유리한 시나리오는 매우 제한적이다:

  • 순수 키-값 캐시만 필요하고, 자료구조나 영속성이 전혀 필요 없을 때
  • 멀티 스레드가 중요할 때 — Memcached는 멀티코어를 더 효율적으로 활용한다
  • 극도로 단순한 운영을 원할 때 — Memcached는 설정할 것이 거의 없다

하지만 현실에서는 "단순 캐시로 시작했는데 나중에 세션 관리나 실시간 기능이 필요해졌다"는 경우가 많다. Redis는 이런 확장에 유연하게 대응할 수 있다. 처음부터 Redis를 선택하면 나중에 마이그레이션할 필요가 없다.


4. Redis의 5가지 실전 활용 사례

Redis의 진짜 가치는 "빠른 캐시" 그 이상에 있다. 다양한 자료구조 덕분에 놀라울 정도로 다양한 문제를 해결할 수 있다.

4.1 세션 스토어 (Session Store)

가장 흔한 사용 사례다. 웹 애플리케이션에서 사용자 로그인 상태를 유지하려면 세션 데이터를 어딘가에 저장해야 한다.

!
문제: 서버가 여러 대일 때
서버 A에 저장된 세션은 서버 B에서 접근할 수 없다. 로드 밸런서가 요청을 다른 서버로 보내면 **로그인이 풀린다.** Sticky Session으로 해결할 수 있지만, 서버가 죽으면 세션도 날아간다.
R
해결: Redis를 중앙 세션 스토어로
모든 서버가 **하나의 Redis**에서 세션을 읽고 쓴다. 어떤 서버가 요청을 받든 동일한 세션에 접근 가능하다. `SET session:{id} {data} EX 3600`으로 1시간 만료까지 자동 처리된다.
OK
결과: 무상태(Stateless) 서버
서버는 상태를 갖지 않으므로 **자유롭게 스케일 아웃**할 수 있다. 서버 하나가 죽어도 세션은 Redis에 살아있다. TTL(Time To Live)로 만료된 세션은 자동으로 정리된다.

4.2 리더보드 (Leaderboard)

Sorted Set의 완벽한 사용 사례다. 게임 순위, 판매 랭킹, 실시간 투표 결과 등을 O(log N) 시간에 업데이트하고 조회할 수 있다.

관계형 DB에서 리더보드를 구현하려면 ORDER BY score DESC LIMIT 10 쿼리를 매번 실행해야 한다. 유저가 100만 명이면 이 정렬 연산이 부담이 된다. Redis Sorted Set은 삽입 시점에 이미 정렬이 완료되므로, 상위 10명 조회가 항상 O(log N + M)이다 (M은 반환할 개수).

4.3 속도 제한기 (Rate Limiter)

API를 운영하면 "1분에 100번까지만 요청 허용" 같은 속도 제한이 필수다. Redis의 INCREXPIRE 조합으로 간단하게 구현할 수 있다.

Step 1 요청이 들어오면 INCR rate:{user_id}:{분} 실행 — 카운터 +1
Step 2 카운터가 1이면(첫 요청) EXPIRE rate:{user_id}:{분} 60 — 60초 후 자동 삭제
Step 3 카운터 값이 100 이하이면 요청 허용, 초과하면 429 Too Many Requests 반환
Step 4 60초가 지나면 키가 자동 삭제되어 카운터가 리셋된다 — 별도 정리 로직 불필요

이 전체 로직이 Redis 명령 2~3줄이면 된다. 별도의 백그라운드 작업이나 크론 잡이 필요 없다.

4.4 Pub/Sub (발행/구독)

Redis는 간단한 메시지 브로커로도 작동한다. 채팅방, 실시간 알림, 이벤트 브로드캐스팅에 사용할 수 있다.

Publisher (서버)
PUBLISH "chat:room:42" "안녕하세요!"
Redis (채널 라우팅)
Subscriber A
Subscriber B
Subscriber C

주의할 점은, Redis Pub/Sub은 메시지를 영속적으로 저장하지 않는다. 구독자가 오프라인인 동안 발행된 메시지는 유실된다. "절대 유실되면 안 되는" 메시지라면 Redis Streams나 Kafka를 사용해야 한다. 하지만 "실시간 알림인데, 놓쳐도 다음에 또 오는" 수준이라면 Redis Pub/Sub으로 충분하다.

4.5 실시간 분석 (Real-time Analytics)

INCR이나 PFADD(HyperLogLog) 같은 명령으로 실시간 통계를 수집할 수 있다.

  • 페이지뷰 카운터: INCR pageview:2026-04-01:/blog/redis-guide
  • 고유 방문자 수: PFADD unique_visitors:2026-04-01 "user:1234" — HyperLogLog로 0.81% 오차 내에서 수십억 개의 고유값을 12KB로 추정
  • 실시간 온라인 사용자: SADD online_users "user:1234" + TTL로 5분 내 활동이 없으면 자동 제거

이런 실시간 집계를 관계형 DB에서 하면 INSERTUPDATE가 폭주하여 성능이 급격히 떨어진다. Redis는 초당 수십만 건의 쓰기를 거뜬히 처리한다.


5. AWS ElastiCache: Redis를 편하게 운영하는 방법

Redis가 아무리 좋아도, 직접 운영하면 고생이 따른다. 서버 프로비저닝, 패치, 장애 복구, 백업, 스케일링... 이 모든 운영 부담을 AWS가 대신 져주는 서비스가 Amazon ElastiCache다.

ElastiCache란?

ElastiCache는 AWS의 완전관리형(Fully Managed) 인메모리 캐시 서비스다. Redis와 Memcached 엔진을 모두 지원한다. 2011년에 Memcached 지원으로 시작했고, 2013년에 Redis 지원이 추가되었다.

ElastiCache 핵심 구성 요소
ElastiCache 완전관리형 서비스
Redis 엔진 7.x 자료구조 + 영속성
Memcached 엔진 1.6.x 단순 캐시
Valkey 엔진 7.x+ Redis 호환 포크

AWS가 대신 해주는 것들

운영 항목직접 운영 (EC2 위 Redis)ElastiCache
서버 프로비저닝EC2 인스턴스 직접 선택 + 설정노드 타입 선택 → 클릭
소프트웨어 패치수동 업데이트 + 다운타임 관리자동 패치 (유지보수 윈도우)
장애 감지/복구모니터링 스크립트 직접 작성자동 장애 감지 + 페일오버
백업RDB/AOF 수동 관리자동 백업 + 특정 시점 복원
스케일링샤딩 직접 설계온라인 리사이징 지원
모니터링Prometheus/Grafana 직접 구축CloudWatch 통합
보안TLS, AUTH 직접 설정VPC, 암호화, IAM 통합

클러스터 모드: 수평 확장

ElastiCache Redis는 두 가지 모드로 운영할 수 있다.

1. 클러스터 모드 비활성화 (Cluster Mode Disabled)

하나의 샤드(shard)로 운영한다. 프라이머리 노드 1개 + 레플리카 노드 최대 5개. 단순하고 운영이 쉽다. 데이터가 하나의 노드 메모리에 들어가는 규모(보통 ~100GB 이하)라면 이 모드로 충분하다.

프라이머리 (읽기/쓰기)
↓ 비동기 복제
레플리카 1 (읽기 전용)
레플리카 2 (읽기 전용)

2. 클러스터 모드 활성화 (Cluster Mode Enabled)

데이터를 여러 샤드에 분산 저장한다. 각 샤드가 전체 키 공간의 일부를 담당한다(해시 슬롯 방식). 최대 500개 샤드, 샤드당 최대 5개 레플리카. 테라바이트 규모의 데이터를 처리할 수 있다.

클러스터 모드 (3 샤드, 각 1 레플리카)
샤드 1 슬롯 0~5460
샤드 2 슬롯 5461~10922
샤드 3 슬롯 10923~16383
레플리카 1-1 읽기 전용
레플리카 2-1 읽기 전용
레플리카 3-1 읽기 전용

클러스터 모드에서 Redis는 키의 해시값을 계산하여 16,384개의 해시 슬롯 중 하나에 배정한다. 예를 들어 CRC16("user:1234") % 16384 = 8721이면, 이 키는 슬롯 8721을 담당하는 샤드 2에 저장된다.

Multi-AZ: 가용성 보장

ElastiCache의 Multi-AZ 기능은 프라이머리 노드가 있는 가용 영역(AZ)이 장애를 일으키면, 다른 AZ에 있는 레플리카를 자동으로 프라이머리로 승격시킨다. 일반적으로 수십 초 내에 페일오버가 완료된다.


6. 캐시 패턴: 데이터를 어떻게 넣고 뺄 것인가

캐시를 도입하면 반드시 답해야 하는 질문이 있다: "데이터베이스와 캐시를 어떻게 동기화할 것인가?" 이에 대한 세 가지 대표적인 패턴이 있다.

6.1 Cache-Aside (Lazy Loading)

가장 널리 사용되는 패턴이다. 애플리케이션이 캐시와 데이터베이스를 직접 관리한다.

READ 애플리케이션이 먼저 캐시에서 데이터를 찾는다
HIT? 캐시 히트(Hit)이면 바로 반환 — 끝. 데이터베이스에 접근하지 않는다
MISS 캐시 미스(Miss)이면 데이터베이스에서 조회한다
SET 조회한 데이터를 캐시에 저장한 후 (TTL 설정) 반환한다
NEXT 다음 동일 요청은 캐시 히트로 처리된다
hljs language-python
def get_user(user_id):
    # 1. 캐시에서 먼저 찾기
    cached = redis.get(f"user:{user_id}")
    if cached:
        return json.loads(cached)  # Cache Hit!
    
    # 2. 캐시 미스 → DB에서 조회
    user = db.query("SELECT * FROM users WHERE id = %s", user_id)
    
    # 3. 캐시에 저장 (TTL 1시간)
    redis.set(f"user:{user_id}", json.dumps(user), ex=3600)
    
    return user

장점: 요청된 데이터만 캐시하므로 메모리 낭비가 없다. 캐시가 죽어도 DB에서 직접 서비스 가능하다(성능은 떨어지지만 장애는 아니다).

단점: 첫 번째 요청은 항상 캐시 미스(콜드 스타트). 캐시와 DB 사이에 데이터 불일치가 발생할 수 있다.

6.2 Write-Through

데이터를 쓸 때 캐시와 DB에 동시에 쓴다.

애플리케이션: 데이터 쓰기
캐시에 쓰기 (SET)
DB에 쓰기 (INSERT/UPDATE)
완료 응답

장점: 캐시와 DB가 항상 동기화 상태. 읽기 시 캐시 미스가 발생하지 않는다 (모든 데이터가 캐시에 있으므로).

단점: 쓰기 지연이 증가한다 (캐시 + DB 두 곳에 써야 하므로). 한 번도 읽히지 않는 데이터도 캐시를 차지한다 — 메모리 낭비.

6.3 Write-Behind (Write-Back)

데이터를 캐시에만 즉시 쓰고, DB에는 나중에 비동기로 반영한다.

애플리케이션: 데이터 쓰기
캐시에 즉시 쓰기
즉시 완료 응답 (빠름!)
백그라운드: 캐시 → DB 비동기 동기화

장점: 쓰기 성능이 매우 빠르다 (메모리에만 쓰면 되므로). 대량 쓰기를 배치로 묶어 DB 부하를 줄일 수 있다.

단점: 캐시가 죽으면 아직 DB에 반영되지 않은 데이터가 유실될 수 있다. 구현이 복잡하다.

어떤 패턴을 선택할까?

패턴최적 시나리오주의사항
Cache-Aside읽기가 많은 워크로드 (가장 범용적)콜드 스타트, 데이터 불일치 가능
Write-Through일관성이 중요한 워크로드쓰기 지연, 메모리 낭비
Write-Behind쓰기가 많은 워크로드 (로그, 분석)데이터 유실 위험, 복잡한 구현

실무에서는 Cache-Aside가 80% 이상의 경우에 사용된다. 단순하고, 장애에 강하며, 메모리 효율이 좋기 때문이다.


7. 캐시 무효화: 컴퓨터 과학에서 가장 어려운 두 가지

편의점 냉장고에서 유통기한이 지난 상품은 빨갛게 만료 표시되고 점원이 제거하는 캐시 무효화 개념 일러스트

"컴퓨터 과학에서 어려운 것은 딱 두 가지뿐이다: 캐시 무효화와 이름 짓기."
— Phil Karlton

이 농담 같은 인용구가 수십 년 동안 살아남은 이유가 있다. 캐시 무효화(Cache Invalidation)는 정말로 어렵다.

왜 어려운가?

캐시의 본질적 문제는 이것이다: 데이터의 원본(DB)이 변경되었을 때, 캐시에 있는 복사본은 어떻게 할 것인가?

  • 너무 빨리 무효화하면 → 캐시 히트율이 떨어져 성능이 나빠진다
  • 너무 늦게 무효화하면 → 사용자가 오래된(stale) 데이터를 본다
  • 무효화를 안 하면 → 데이터 불일치가 계속 쌓인다

전략 1: TTL (Time To Live)

가장 단순한 전략. 캐시에 데이터를 넣을 때 "이 데이터는 X초 후에 자동으로 삭제된다"고 선언한다.

TTL 설정 예시
자주 변하는 데이터
SET stock:item:5678 "42" EX 30 → 재고 수량: 30초 TTL
SET trending:posts "{...}" EX 300 → 인기 게시물: 5분 TTL
가끔 변하는 데이터
SET user:profile:1234 "{...}" EX 3600 → 프로필: 1시간 TTL
SET product:detail:5678 "{...}" EX 86400 → 상품 상세: 24시간 TTL
거의 안 변하는 데이터
SET config:site:settings "{...}" EX 604800 → 사이트 설정: 7일 TTL
SET static:country:list "{...}" → 국가 목록: TTL 없음 (영구)

장점: 구현이 극도로 간단하다. 최악의 경우에도 TTL이 지나면 데이터가 갱신된다.

단점: TTL이 만료되기 전까지는 오래된 데이터가 서비스될 수 있다. 이것을 "eventually consistent(결과적 일관성)"이라 부른다.

전략 2: 이벤트 기반 무효화

데이터가 변경될 때 즉시 캐시를 삭제하거나 갱신하는 방식이다.

hljs language-python
def update_user_profile(user_id, new_data):
    # 1. DB 업데이트
    db.execute("UPDATE users SET ... WHERE id = %s", user_id)
    
    # 2. 캐시 즉시 삭제 (다음 읽기에서 DB에서 새로 가져옴)
    redis.delete(f"user:profile:{user_id}")

주의: "삭제 후 재적재" vs "직접 갱신"

실무에서는 캐시를 직접 새 값으로 갱신하는 것보다, 삭제만 하고 다음 읽기에서 Cache-Aside로 다시 적재하는 방식이 더 안전하다. DB 업데이트와 캐시 갱신 사이에 경쟁 조건(race condition)이 발생할 수 있기 때문이다.

전략 3: TTL + 이벤트 기반 조합

실무에서 가장 견고한 패턴은 두 전략을 결합하는 것이다:

  1. 이벤트 기반 무효화로 변경 즉시 캐시를 삭제한다 (95%의 경우 처리)
  2. TTL을 안전망으로 설정한다 — 이벤트가 유실되어 캐시가 삭제되지 못한 경우에도, TTL이 지나면 자연스럽게 갱신된다

이 "벨트와 멜빵" 전략이 대부분의 프로덕션 환경에서 사용된다.


8. 캐시의 위험한 함정들

캐시는 강력하지만, 잘못 쓰면 성능을 더 악화시킬 수 있다. 세 가지 대표적인 함정을 알아보자.

8.1 캐시 스탬피드 (Cache Stampede)

인기 있는 키의 TTL이 만료되는 순간, 수백 개의 동시 요청이 한꺼번에 DB로 몰려드는 현상이다. "인기 상품 목록"의 캐시가 만료되면, 그 순간 들어온 100개의 요청이 모두 캐시 미스를 경험하고, 동시에 같은 DB 쿼리를 실행한다.

!
현상
인기 키 만료 → 동시에 수백 요청이 DB로 폭주 → DB 부하 급증 → 연쇄 지연
FIX
해결: 분산 락 (Distributed Lock)
캐시 미스 시 하나의 요청만 DB를 조회하고 캐시를 갱신하도록 잠금(lock)을 건다. 나머지 요청은 짧게 기다린 뒤 갱신된 캐시를 읽는다. Redis의 SET key value NX EX 10으로 구현할 수 있다.

8.2 캐시 침투 (Cache Penetration)

존재하지 않는 키에 대한 요청이 반복되면, 매번 캐시 미스 → DB 조회 → 결과 없음의 사이클이 반복된다. 악의적인 공격자가 의도적으로 없는 ID를 조회하여 DB에 부하를 줄 수도 있다.

해결 방법은 블룸 필터(Bloom Filter)를 사용하여 존재하지 않는 키를 빠르게 걸러내거나, 널(null) 캐싱 — 존재하지 않는 키에 대해서도 "없음"이라는 값을 짧은 TTL로 캐시하는 것이다.

8.3 캐시 눈사태 (Cache Avalanche)

대량의 키가 같은 시간에 동시에 만료되어, DB에 갑자기 거대한 부하가 발생하는 현상. 서비스 시작 시 캐시를 일괄로 채우면(warm-up), 모든 키의 TTL이 비슷해져서 동시 만료가 발생할 수 있다.

해결 방법은 TTL에 랜덤 지터(jitter)를 추가하는 것이다:

hljs language-python
import random

base_ttl = 3600  # 기본 1시간
jitter = random.randint(0, 600)  # 0~10분 랜덤 추가
redis.set(key, value, ex=base_ttl + jitter)

이렇게 하면 만료 시점이 분산되어, DB에 가해지는 부하가 한 시점에 집중되지 않는다.


9. Redis 라이선스 논쟁: 오픈 소스의 갈림길

무슨 일이 있었나

2024년 3월, Redis의 운영사 Redis Ltd는 Redis 7.4부터 라이선스를 BSD → SSPL + RSALv2 듀얼 라이선스로 변경한다고 발표했다. 이것은 클라우드 업계에 충격파를 던졌다.

2009 Redis 오픈 소스 출시 (3-clause BSD 라이선스) — 누구든 자유롭게 사용 가능
2018~ Redis 모듈(RediSearch, RedisJSON 등)을 독점 라이선스로 전환 시작
2024.03 Redis 코어까지 SSPL + RSALv2로 변경 — 클라우드 업체가 Redis를 관리형 서비스로 제공하려면 라이선스 비용 필요
2024.03 Linux Foundation이 Redis 7.2.4를 포크하여 Valkey 프로젝트 발족 — BSD 라이선스 유지
2024.09 Valkey 8.0 출시 — AWS, Google Cloud, Oracle 등이 공식 지원 선언

왜 이런 일이 벌어졌나

Redis Ltd의 입장은 이해할 수 있다. AWS ElastiCache, Google Cloud Memorystore 같은 클라우드 서비스가 Redis를 그대로 가져다가 관리형 서비스로 판매하면서 수십억 달러의 매출을 올리는데, 정작 Redis 개발에 기여하는 비율은 극히 적었다. Redis Ltd는 "우리가 만든 것으로 남이 돈을 번다"고 느꼈다.

하지만 오픈 소스 커뮤니티의 반발도 강했다. Redis가 이렇게 성장한 것 자체가 오픈 소스 생태계 덕분이고, 라이선스 변경은 그 생태계를 배신하는 것이라는 비판이었다.

Valkey의 등장

Redis의 라이선스 변경 발표 직후, Linux Foundation 주도로 Redis 7.2.4(마지막 BSD 버전)를 포크한 Valkey가 탄생했다. AWS, Google Cloud, Oracle, Ericsson, Snap 등이 참여하여, BSD 라이선스를 유지하는 Redis 호환 대안을 만들겠다고 선언했다.

비교Redis (Redis Ltd)Valkey (Linux Foundation)
라이선스SSPL + RSALv2 (비OSI)BSD 3-Clause (진정한 오픈 소스)
거버넌스Redis Ltd 단독 통제Linux Foundation 커뮤니티
API 호환성원본99%+ 호환 (드롭인 교체)
후원사Redis LtdAWS, Google, Oracle 등 대형 클라우드
클라우드 서비스Redis CloudElastiCache, Memorystore 등

AWS ElastiCache는 이미 Valkey를 엔진 옵션으로 지원하고 있다. 새 클러스터를 만들 때 Redis 대신 Valkey를 선택할 수 있으며, 기존 Redis 클러스터에서 Valkey로의 인플레이스 마이그레이션도 가능하다.

이 논쟁은 오픈 소스 비즈니스 모델의 근본적인 딜레마를 보여준다: 오픈 소스로 사용자를 모은 뒤, 라이선스를 바꿔 수익을 추구하면 커뮤니티가 떠난다. MongoDB(SSPL), Elasticsearch(Elastic License → OpenSearch 포크) 등에서 반복된 패턴이다.


10. 실전 아키텍처: 이커머스에서의 ElastiCache

이론을 종합하여, 실제 이커머스(쇼핑몰) 서비스에서 ElastiCache를 어떻게 사용하는지 살펴보자.

전체 아키텍처

이커머스 캐시 아키텍처
CloudFront CDN 정적 파일 캐시
ALB 로드 밸런서 트래픽 분산
ECS / EKS 애플리케이션 API 서버 클러스터
ElastiCache Redis 캐시 + 세션
RDS Aurora PostgreSQL 주 데이터베이스

역할별 캐시 전략

이커머스에서 ElastiCache Redis가 담당하는 역할과 전략을 정리하면 다음과 같다.

역할키 설계캐시 패턴TTL
세션 관리session:{session_id}Write-Through30분 (슬라이딩)
상품 카탈로그product:{id}Cache-Aside1시간 + 이벤트 무효화
장바구니cart:{user_id} (Hash)Write-Through7일
재고 수량stock:{product_id}Cache-Aside30초 (민감한 데이터)
인기 상품 랭킹ranking:daily (Sorted Set)Write-Behind5분
API 속도 제한rate:{api_key}:{minute}Write-Through60초

데이터 흐름 예시: 상품 상세 페이지

사용자가 상품 상세 페이지를 열었을 때의 데이터 흐름을 추적해보자.

상품 상세 조회 — Cache-Aside 패턴 LIVE
REQUEST 사용자가 GET /products/5678 요청
CACHE Redis에 GET product:5678 명령 → 0.2ms
HIT 캐시 히트! JSON 데이터 반환 → DB 접근 없이 응답 완료
REQUEST 1시간 후, 같은 상품에 다른 사용자가 접근
CACHE Redis에 GET product:5678 → TTL 만료, 키 없음
MISS 캐시 미스 → RDS Aurora에서 SELECT 쿼리 실행 → 15ms
SET Redis에 SET product:5678 ... EX 3600 → 캐시 재적재

장바구니: Hash 자료구조의 활용

장바구니는 Redis Hash로 구현하면 매우 효율적이다. 하나의 키(cart:{user_id}) 안에 상품별 수량을 필드로 저장한다.

장바구니 Redis Hash 구조
상품 추가
HSET cart:user:1234 product:5678 3
HSET cart:user:1234 product:9012 1
→ 사용자 1234의 장바구니에 상품 5678 x 3개, 상품 9012 x 1개
수량 변경
HINCRBY cart:user:1234 product:5678 -1
→ 상품 5678 수량을 1 감소 (3 → 2). 원자적 연산으로 동시성 안전
전체 조회
HGETALL cart:user:1234
→ {"product:5678": "2", "product:9012": "1"} — 한 번의 명령으로 전체 장바구니

RDB에 장바구니를 저장하면 상품 추가/수량 변경마다 UPDATE 쿼리를 실행해야 한다. Redis Hash로 구현하면 이 모든 연산이 서브 밀리초에 완료된다.

모니터링: CloudWatch 핵심 지표

ElastiCache를 운영할 때 반드시 모니터링해야 하는 핵심 지표들이 있다.

ElastiCache 핵심 모니터링 지표
이상적인 임계값 기준
캐시 히트율
≥ 95% 정상
CPU 사용률
≤ 65% 주의
메모리 사용률
≤ 75% 경고
Eviction
≈ 0 위험
  • 캐시 히트율: 95% 이상이면 건강하다. 80% 아래로 떨어지면 캐시 전략을 재검토해야 한다.
  • CPU 사용률: Redis는 싱글 스레드이므로, 한 코어의 사용률이 90%를 넘으면 위험하다.
  • 메모리 사용률: 80%를 넘으면 스케일 업을 고려해야 한다. 100%에 도달하면 기존 데이터가 밀려난다(eviction).
  • Eviction 수: 메모리가 가득 차서 기존 데이터를 강제로 삭제하는 횟수. 이상적으로는 0이어야 한다.

11. ElastiCache 시작하기: 첫 클러스터 만들기

실제로 ElastiCache Redis 클러스터를 만드는 과정을 간단히 살펴보자.

노드 타입 선택 가이드

용도추천 노드 타입메모리월 비용 (서울)
개발/테스트cache.t4g.micro0.5 GB~$12
소규모 서비스cache.t4g.medium3.09 GB~$48
중규모 서비스cache.r7g.large13.07 GB~$250
대규모 서비스cache.r7g.xlarge26.32 GB~$500

t4g 시리즈는 버스트 가능한 범용 인스턴스로, 트래픽이 일정하지 않은 소규모 서비스에 적합하다. r7g 시리즈는 메모리 최적화 인스턴스로, 대규모 캐시 데이터를 안정적으로 유지해야 하는 프로덕션 환경에 추천한다.

보안 설정 체크리스트

ElastiCache는 VPC 내부에서만 접근할 수 있다(퍼블릭 IP 없음). 추가로 다음 보안 설정을 권장한다:

  1. 보안 그룹: Redis 포트(6379)를 애플리케이션 서버의 보안 그룹에서만 허용
  2. 전송 중 암호화(TLS): transit-encryption-enabled=true
  3. 저장 중 암호화: at-rest-encryption-enabled=true
  4. AUTH 토큰: Redis AUTH로 비밀번호 설정
  5. IAM 인증: Redis 7+ / Valkey 7+에서 지원하는 IAM 기반 인증

연결 예시

hljs language-python
import redis

# ElastiCache Redis에 연결 (TLS 활성화)
r = redis.Redis(
    host='my-cluster.abc123.apn2.cache.amazonaws.com',
    port=6379,
    ssl=True,
    password='my-auth-token',
    decode_responses=True
)

# 기본 사용
r.set('greeting', '안녕하세요!', ex=3600)
print(r.get('greeting'))  # → '안녕하세요!'

마치며: 캐시는 선택이 아닌 필수다

이 글에서 우리는 캐시의 기본 개념부터 Redis의 자료구조, ElastiCache의 운영, 캐시 전략, 그리고 오픈 소스 라이선스 논쟁까지 폭넓게 다뤘다.

핵심을 정리하면 이렇다:

  1. 캐시는 속도의 차원을 바꾼다 — 밀리초를 마이크로초로, 초를 밀리초로 줄인다
  2. Redis는 단순한 캐시가 아니라 데이터 구조 서버다 — 5가지 핵심 자료구조가 다양한 문제를 O(1) 또는 O(log N)에 해결한다
  3. ElastiCache는 Redis 운영의 고통을 덜어준다 — 패치, 백업, 장애 복구를 AWS가 대신한다
  4. Cache-Aside + TTL + 이벤트 무효화가 가장 실용적인 캐시 전략이다
  5. Valkey는 Redis의 오픈 소스 미래다 — BSD 라이선스로 커뮤니티 주도 개발이 이어진다

클라우드 네이티브 시대에 캐시를 사용하지 않는 서비스는 없다. 사용자가 "빠르다"고 느끼는 서비스 뒤에는 언제나 잘 설계된 캐시 레이어가 있다. 이 글이 그 첫걸음을 내딛는 데 도움이 되었길 바란다.

Redis 공식 문서와 AWS ElastiCache 문서를 함께 읽으면 더 깊은 이해를 얻을 수 있다: