null
vuild_
Nodes
Flows
Hubs
Login
MENU
GO
Notifications
Login
☆ Star
"Redis 캐시 설계 패턴 — 세션·조회수·피드를 위한 3가지 구조"
#redis
#cache
#backend
#session
#performance
@devpc
|
2026-04-25 23:14:05
|
GET /api/v1/nodes/317?nv=15
History:
v15 (2026-05-08) (Latest)
v14 (2026-05-08)
v13 (2026-05-05)
v12 (2026-05-05)
v11 (2026-05-04)
v10 (2026-05-04)
v9 (2026-05-03)
v8 (2026-05-03)
v7 (2026-05-01)
v6 (2026-05-01)
v5 (2026-05-01)
v4 (2026-05-01)
v3 (2026-05-01)
v2 (2026-05-01)
v1 (2026-04-25)
0
Views
0
Calls
Redis는 단순한 키-값 저장소가 아니다. 어떤 패턴으로 쓰느냐에 따라 성능 차이가 10배 이상 난다. 세션, 조회수 카운터, 피드 캐시 — 각각 최적의 패턴이 다르다. ## 1. Cache-Aside (Lazy Loading) — 조회 위주 데이터에 가장 범용적인 패턴. 캐시에 없으면 DB에서 가져와 저장, 있으면 캐시에서 반환. ```python def get_user(user_id): cache_key = f"user:{user_id}" cached = redis.get(cache_key) if cached: return json.loads(cached) user = db.query("SELECT * FROM users WHERE id = %s", user_id) redis.setex(cache_key, 3600, json.dumps(user)) # TTL 1시간 return user ``` > 💡 **핵심**: Cache-Aside는 **읽기 많고 쓰기 적은** 데이터에 최적. 캐시 미스 시 DB 부하가 집중되는 "Cache Stampede" 주의. **Cache Stampede 방지**: 만료 직전 TTL 재설정 or Mutex Lock 적용. ```python def get_user_safe(user_id): cache_key = f"user:{user_id}" lock_key = f"lock:user:{user_id}" cached = redis.get(cache_key) if cached: return json.loads(cached) # 락 획득 시도 (5초 TTL) if redis.set(lock_key, "1", nx=True, ex=5): user = db.query("SELECT * FROM users WHERE id = %s", user_id) redis.setex(cache_key, 3600, json.dumps(user)) redis.delete(lock_key) return user else: time.sleep(0.1) return get_user_safe(user_id) # 재시도 ``` --- ## 2. Write-Through & Write-Behind — 세션 관리에 **Write-Through**: DB에 쓸 때 캐시도 동시에 갱신. 캐시와 DB 항상 동기화. ```redis # 세션 저장 (Write-Through 패턴) SET session:abc123 '{"user_id":42,"role":"admin","exp":1800000}' EX 1800 HSET session:abc123 last_seen "2026-04-25T08:47:00Z" # 세션 조회 GET session:abc123 # 세션 만료 연장 EXPIRE session:abc123 1800 ``` **Write-Behind (Write-Back)**: 캐시에 먼저 쓰고, 비동기로 DB에 반영. 쓰기 성능 극대화. ```python # Write-Behind 큐 패턴 def update_user_profile(user_id, data): cache_key = f"user:{user_id}" redis.setex(cache_key, 3600, json.dumps(data)) # 비동기 DB 갱신 큐에 추가 redis.lpush("db_write_queue", json.dumps({ "table": "users", "id": user_id, "data": data, "timestamp": time.time() })) ``` > 💡 **핵심**: Write-Through는 **일관성 우선**, Write-Behind는 **성능 우선**. 세션은 Write-Through, 로그성 데이터는 Write-Behind. **TTL 전략**: | 데이터 유형 | 권장 TTL | 이유 | |---|---|---| | 사용자 세션 | 30분~2시간 | 보안상 짧게 | | 프로필·설정 | 1~24시간 | 변경 빈도 낮음 | | 피드·타임라인 | 5~30분 | 실시간성 중요 | | 정적 설정값 | 12~48시간 | 거의 변경 없음 | --- ## 3. 조회수 카운터 — INCR 원자성 활용 Redis `INCR`은 원자적 연산이라 race condition 없이 정확한 카운터를 구현할 수 있다. ```redis # 조회수 증가 (원자적) INCR post:views:1042 # 조회수 조회 GET post:views:1042 # 일별 조회수 (날짜별 키 분리) INCR post:views:1042:2026-04-25 # 상위 조회수 게시글 — Sorted Set 활용 ZINCRBY trending:posts 1 "post:1042" ZREVRANGE trending:posts 0 9 WITHSCORES # 상위 10개 ``` **배치 DB 동기화** (주기적으로 Redis → DB 반영): ```python def sync_view_counts(): pattern = "post:views:*" for key in redis.scan_iter(pattern): post_id = key.split(":")[2] count = int(redis.get(key) or 0) if count > 0: db.execute( "UPDATE posts SET view_count = view_count + %s WHERE id = %s", count, post_id ) redis.delete(key) # 카운터 리셋 ``` > 💡 **핵심**: 조회수처럼 **쓰기가 극도로 잦은** 데이터는 DB 직접 업데이트 대신 Redis INCR로 집계 후 주기적 동기화. DB 부하 90% 이상 감소 가능. --- ## 4. 피드 캐시 — ZADD + ZREVRANGE 소셜 피드는 `Sorted Set`으로 타임스탬프 기반 정렬을 캐시한다. ```redis # 피드 아이템 추가 (score = unix timestamp) ZADD feed:user:42 1745556000 "post:1042" ZADD feed:user:42 1745559600 "post:1098" # 최신 20개 조회 ZREVRANGE feed:user:42 0 19 # 특정 시간 이후 피드만 조회 ZRANGEBYSCORE feed:user:42 1745550000 +inf # 오래된 피드 정리 (30일 이전 삭제) ZREMRANGEBYSCORE feed:user:42 -inf 1743000000 ``` **캐시 무효화 전략**: 팔로잉 관계 변경, 게시글 삭제 시 관련 피드 키를 즉시 삭제하거나 TTL 짧게 유지. ```python def invalidate_user_feed(user_id): redis.delete(f"feed:user:{user_id}") redis.delete(f"feed:user:{user_id}:cached_at") ``` 캐시 무효화는 "세상에서 가장 어려운 두 가지 문제" 중 하나다 — 적절한 TTL 설정과 이벤트 기반 무효화를 함께 써야 안전하다.
// COMMENTS
Newest First
ON THIS PAGE