• 포스트
  • 도구
  • 작업
  • 포스트
  • 도구
  • 작업
로그인회원가입

© 2005 kaisa.co.kr. All Rights Reserved.

소개개인정보처리방침7083620@hanmail.net
← 목록으로
2024-09-04

Redis: 인메모리 키-값으로 캐시·세션·큐

Redis: 인메모리 키-값으로 캐시·세션·큐

요약: 캐시·세션·카운터처럼 빠르고 만료가 자연스러운 계층에 쓰는 인메모리 저장소의 패턴과 운영 주의를 정리한다.


1. 언제 Redis인가

Redis는 메모리 위에서 움직이는 키-값 저장소다. 디스크 DB를 대체하는 도구가 아니다. 빠르고, 만료(TTL)가 자연스럽고, 조금 유실되어도 앱이 다시 만들 수 있는 데이터에 둔다. TTL은 Time To Live, 키의 수명이다.

맞는 경우 덜 맞는 경우
API 응답·핫 키 캐시 주문·결제처럼 유실되면 안 되는 원본 데이터
로그인 세션 스토어 복잡한 조인·제약·리포팅 (PostgreSQL/MySQL)
초당 카운터, 레이트 리밋 문서 구조가 큰 도메인 모델 (MongoDB)
단순 큐, Streams 로그 파일 하나면 충분한 로컬 SQL (SQLite)
짧은 분산 락(신중히) 영구 보관이 목적인 아카이브

주 데이터베이스 앞에 저지연 계층으로 두는 그림이 정석이다. Redis만 믿고 원본을 안 남기면 재시작·퇴출(eviction) 때 데이터가 사라진다.


2. 핵심 개념

키는 문자열이다. 값은 자료구조로 고른다.

타입 용도
String 캐시 블롭, 카운터(INCR)
Hash 객체 필드 여러 개
List 단순 큐(왼쪽 push, 오른쪽 pop)
Set 중복 없는 태그
Sorted Set 점수 랭킹
Stream 메시지 로그, 컨슈머 그룹

TTL을 기본으로 설계한다. 만료 없는 키는 메모리를 영원히 먹는다.

maxmemory와 eviction. 메모리가 가득 차면 정책을 따른다. 캐시면 allkeys-lru가 흔하다. LRU는 Least Recently Used, 오래 안 쓴 키부터 지운다. 세션처럼 지우면 안 되는 키와 캐시를 한 인스턴스에 섞을 때는 DB 번호나 키 prefix로 경계를 긋는다.

영속성. RDB는 특정 시점 스냅샷이다. AOF는 Append Only File, 쓰기를 로그로 남긴다. 둘 다 “디스크 DB만큼” 안전하지 않을 수 있다. fsync 주기에 따라 수 초 유실이 있다. 캐시 전용이면 재시작 유실을 전제로 앱이 동작해야 한다.

핫 키. 한 키에 트래픽이 몰리면 스케일 아웃 전에 그 노드가 병목이다. 키 설계와 로컬 캐시를 검토한다.

키 이름. 서비스:환경:객체:id:필드처럼 접두를 통일한다. FLUSHDB 사고와 키 충돌을 줄인다. 운영 명령을 앱에서 호출하지 않는다.

센티널은 장애 시 프라이머리를 고른다. 클러스터는 슬롯으로 키를 나눈다. 단일 인스턴스로 충분하면 둘 다 미룬다. 운영 복잡도가 먼저 온다.


3. 최소 예제 — 프로필 캐시와 레이트 리밋

사용자 프로필을 1시간 캐시하고, 분당 요청 수를 센다.

# 프로필 캐시 (1시간)
SET user:42:profile '{"name":"Ada","plan":"pro"}' EX 3600

GET user:42:profile

# 분당 레이트 리밋
INCR ratelimit:42:2026082615
EXPIRE ratelimit:42:2026082615 60

앱 쪽 골격은 이렇다. DB가 원본이고 Redis는 가속이다.

# 의사 코드. 비밀번호는 YOUR_REDIS_PASSWORD 로 주입한다.
def get_profile(user_id: int) -> dict:
    key = f"user:{user_id}:profile"
    cached = redis.get(key)
    if cached:
        return json.loads(cached)

    profile = db.fetch_profile(user_id)  # PostgreSQL 등 원본
    redis.setex(key, 3600, json.dumps(profile))
    return profile

프로필이 바뀌면 캐시를 지운다. DEL user:42:profile. TTL만 기다리면 최대 1시간 낡은 데이터가 나간다.

단순 큐는 리스트로 충분할 때가 많다.

LPUSH jobs '{"type":"email","to":"ada@example.com"}'
BRPOP jobs 5

작업이 유실되면 안 되면 Redis List만 믿지 않는다. 원본 테이블이나 전용 큐를 검토한다.

연결은 인증을 켠다.

redis://:YOUR_REDIS_PASSWORD@redis.internal:6379/0

4. 운영 시 주의

연결. 앱마다 연결을 무한히 열지 않는다. 풀을 쓰고, 서버리스면 연결 폭주를 특히 조심한다. CLIENT LIST로 이상 연결을 본다.

메모리. INFO memory로 사용량을 본다. 큰 키 하나(LRANGE로 수백만 건)는 조회 한 번에 서버를 멈출 수 있다. 컬렉션 크기에 상한을 둔다.

백업·유실. 캐시면 AOF를 꺼도 된다. 세션·큐면 유실 허용 범위를 팀에 합의한다. RDB/AOF는 백업이지 원본 DB 대체가 아니다. 복원 후 앱이 캐시 미스로 원본을 채울 수 있는지 확인한다.

보안. 기본 포트 6379를 인터넷에 열지 않는다. protected-mode, 비밀번호 또는 ACL, VPC 내부만. FLUSHALL 권한이 앱 계정에 있으면 실수 한 번에 전체가 날아간다.

비용. 관리형 Redis는 메모리 크기가 곧 요금이다. TTL 없는 키와 개발용 인스턴스 방치가 비용을 만든다.

분산 락. SET key token NX EX 패턴을 쓰더라도 만료·프로세스 일시 정지에 깨질 수 있다. 결제처럼 치명적이면 DB 제약이나 전용 잠금 서비스를 검토한다.

캐시 스탬피드. 만료가 한순간에 겹치면 원본 DB가 맞는다. 지터(만료를 조금 흩뿌리기)나 단일 채움 락을 검토한다. 측정 없이 미리 복잡하게 만들지 않는다.

관측. INFO stats의 evicted_keys, keyspace_hits/misses를 본다. 미스율이 높으면 TTL이 너무 짧거나 키가 난립한 것이다.

캐시와 세션을 한 Redis에 둘 거면 DB 번호나 prefix로 나눈다. 캐시 eviction이 세션 키를 지우면 전원이 로그아웃된다. 트래픽이 커지면 인스턴스를 분리하는 편이 낫다.

흔한 실수. KEYS *를 운영에서 실행하기(블로킹), 큰 SMEMBERS 한 방, 비밀번호 없는 6379 퍼블릭 오픈. 모니터링은 SCAN을 쓴다.


5. 정리

Redis는 DB라기보다 저지연 데이터 구조 서버다. TTL·메모리·유실 허용을 먼저 합의한다. 원본은 관계형(또는 문서형)에 두고, Redis는 캐시·세션·카운터 계층으로 둔다.

체크리스트

  • 모든 캐시 키에 TTL을 단다. 만료 없는 키는 메모리 누수다.
  • maxmemory와 eviction 정책을 정한다. 가득 차면 쓰기가 거절되거나 키가 지워진다.
  • 캐시 미스 때 원본에서 다시 채울 수 있는지 확인한다. Redis만의 데이터는 재시작에 약하다.
  • 6379를 프라이빗 네트워크와 인증 뒤로 둔다.
  • FLUSHALL·관리 명령을 앱 계정에서 뺀다.

연습

  1. SET/GET/EXPIRE로 프로필 캐시를 재현하고 TTL이 0이 되는지 확인한다.
  2. INCR+EXPIRE로 분당 10회 제한을 흉내 낸다.
  3. 키를 지운 뒤 앱이 DB에서 다시 읽는지(캐시 미스) 경로를 그려 본다.

댓글

0

아직 댓글이 없습니다. 첫 댓글을 남겨보세요.

이전 글MongoDB: 문서형 DB로 유연한 스키마 다루기다음 글SQLite: 파일 하나로 충분한 임베디드 SQL
← 목록으로