โ Back to list

redis-patterns
by eco2-team
๐ฑ ์ด์ฝ์์ฝ(Ecoยฒ) BE
โญ 0๐ด 0๐
Jan 25, 2026
SKILL.md
name: redis-patterns description: Redis ํจํด ๊ฐ์ด๋. Cache-Aside, Rate Limiting, Pub/Sub, Streams ๊ตฌํ ์ ์ฐธ์กฐ. "redis", "cache", "rate limit", "pubsub", "streams" ํค์๋๋ก ํธ๋ฆฌ๊ฑฐ.
Redis Patterns Guide
Ecoยฒ Redis ์ฌ์ฉ ํจํด
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Redis Usage in Ecoยฒ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Cache-Aside (L1) โ
โ โโ LangGraph Checkpoint ์บ์ โ
โ โโ Intent Classification ์บ์ โ
โ โโ Session State ์บ์ โ
โ โ
โ Rate Limiting โ
โ โโ API ์์ฒญ ์ ํ (Sliding Window) โ
โ โ
โ Pub/Sub โ
โ โโ SSE ์ค์๊ฐ ์ด๋ฒคํธ (sse:events:{job_id}) โ
โ โ
โ Streams โ
โ โโ ์ด๋ฒคํธ ๋ฒํผ ({domain}:events:{shard}) โ
โ โโ State KV ({domain}:state:{job_id}) โ
โ โ
โ Human-in-the-Loop โ
โ โโ ์
๋ ฅ ์์ฒญ (input:request:{job_id}) โ
โ โโ ์ํธ์์ฉ ์ํ (interaction:state:{job_id}) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
์ฃผ์ ํจํด
1. Cache-Aside
async def get_with_cache(
key: str,
fetch_fn: Callable[[], Awaitable[T]],
ttl: int = 3600,
) -> T:
"""Cache-Aside ํจํด"""
# 1. ์บ์ ์กฐํ
cached = await redis.get(key)
if cached:
return deserialize(cached)
# 2. ์๋ณธ ์กฐํ
data = await fetch_fn()
# 3. ์บ์ ์ ์ฅ
await redis.setex(key, ttl, serialize(data))
return data
2. Rate Limiting (Sliding Window)
async def check_rate_limit(
key: str,
limit: int,
window_seconds: int,
) -> bool:
"""Sliding Window Rate Limit"""
now = time.time()
window_start = now - window_seconds
pipe = redis.pipeline()
pipe.zremrangebyscore(key, 0, window_start) # ์ค๋๋ ๊ธฐ๋ก ์ ๊ฑฐ
pipe.zadd(key, {str(now): now}) # ํ์ฌ ์์ฒญ ์ถ๊ฐ
pipe.zcard(key) # ์์ฒญ ์ ํ์ธ
pipe.expire(key, window_seconds)
_, _, count, _ = await pipe.execute()
return count <= limit
3. Pub/Sub
async def publish_event(job_id: str, event: dict) -> None:
"""์ด๋ฒคํธ ๋ฐํ"""
channel = f"sse:events:{job_id}"
await redis.publish(channel, json.dumps(event))
async def subscribe_events(job_id: str) -> AsyncIterator[dict]:
"""์ด๋ฒคํธ ๊ตฌ๋
"""
pubsub = redis.pubsub()
await pubsub.subscribe(f"sse:events:{job_id}")
async for message in pubsub.listen():
if message["type"] == "message":
yield json.loads(message["data"])
4. Streams (Consumer Group)
async def consume_stream(
stream: str,
group: str,
consumer: str,
) -> AsyncIterator[tuple[str, dict]]:
"""Consumer Group ๊ธฐ๋ฐ ์คํธ๋ฆผ ์๋น"""
while True:
messages = await redis.xreadgroup(
groupname=group,
consumername=consumer,
streams={stream: ">"},
block=5000,
count=100,
)
for stream_name, events in messages:
for event_id, data in events:
yield event_id, data
await redis.xack(stream_name, group, event_id)
Reference Files
- Cache-Aside: See cache-aside.md
- Rate Limiting: See rate-limiting.md
- Pub/Sub & Streams: See messaging.md
Redis ์ธ์คํด์ค ๋ถ๋ฆฌ
Ecoยฒ ํด๋ฌ์คํฐ Redis ์๋น์ค
| ์ธ์คํด์ค | K8s ์๋น์ค | ์ฉ๋ |
|---|---|---|
| Streams | rfr-streams-redis.redis.svc.cluster.local:6379 | Streams, State KV (์์) |
| Pub/Sub | rfr-pubsub-redis.redis.svc.cluster.local:6379 | ์ค์๊ฐ ์ ์ก (ํ๋ฐ) |
# ConfigMap ํ๊ฒฝ๋ณ์ ์์
CHAT_WORKER_REDIS_STREAMS_URL: redis://rfr-streams-redis.redis.svc.cluster.local:6379/0
CHAT_WORKER_REDIS_PUBSUB_URL: redis://rfr-pubsub-redis.redis.svc.cluster.local:6379/0
์ธ์คํด์ค๋ณ ํน์ฑ
rfr-streams-redis: # Streams, State KV (์์)
- AOF ํ์ฑํ
- Checkpoint ๋ฐ์ดํฐ
- Consumer Group
rfr-pubsub-redis: # Pub/Sub (ํ๋ฐ)
- ์ค์๊ฐ ์ ์ก ์ ์ฉ
- SSE Gateway ๊ตฌ๋
Score
Total Score
60/100
Based on repository quality metrics
โSKILL.md
SKILL.mdใใกใคใซใๅซใพใใฆใใ
+20
โLICENSE
ใฉใคใปใณในใ่จญๅฎใใใฆใใ
+10
โ่ชฌๆๆ
100ๆๅญไปฅไธใฎ่ชฌๆใใใ
0/10
โไบบๆฐ
GitHub Stars 100ไปฅไธ
0/15
โๆ่ฟใฎๆดปๅ
3ใถๆไปฅๅ ใซๆดๆฐใใใ
0/10
โใใฉใผใฏ
10ๅไปฅไธใใฉใผใฏใใใฆใใ
0/5
โIssue็ฎก็
ใชใผใใณIssueใ50ๆชๆบ
+5
โ่จ่ช
ใใญใฐใฉใใณใฐ่จ่ชใ่จญๅฎใใใฆใใ
+5
โใฟใฐ
1ใคไปฅไธใฎใฟใฐใ่จญๅฎใใใฆใใ
0/5
Reviews
๐ฌ
Reviews coming soon