์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ Redis & Memcached ์™„์ „์ •๋ณต

๐Ÿš€ Redis & Memcached ์™„์ „์ •๋ณต

์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ ์‹œ์Šคํ…œ๊ณผ ์„ธ์…˜ ๊ด€๋ฆฌ์˜ ๋ชจ๋“  ๊ฒƒ

๐Ÿ’ก ์ด ๊ธ€์„ ์ฝ์œผ๋ฉด Redis์™€ Memcached์˜ ํ•ต์‹ฌ ๊ฐœ๋…๋ถ€ํ„ฐ ์‹ค์ „ ํ™œ์šฉ๋ฒ•๊นŒ์ง€, ์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ ์‹œ์Šคํ…œ์˜ A to Z๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์–ด์š”. ์„ธ์…˜ ๊ด€๋ฆฌ๋ถ€ํ„ฐ ์„ฑ๋Šฅ ์ตœ์ ํ™”๊นŒ์ง€, ์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ์จ๋จน์„ ์ˆ˜ ์žˆ๋Š” ์ง€์‹์„ ์นœ๊ตฌ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ์„ค๋ช…ํ•ด๋“œ๋ฆด๊ฒŒ์š”! ๐ŸŽฏ

๐ŸŽช ์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ, ๋„๋Œ€์ฒด ๋ญ๊ธธ๋ž˜?

์—ฌ๋Ÿฌ๋ถ„, ์‹๋‹น์—์„œ ์ฃผ๋ฌธํ•œ ์Œ์‹์ด ๋‚˜์˜ค๊ธฐ๊นŒ์ง€ 30๋ถ„์„ ๊ธฐ๋‹ค๋ฆฐ๋‹ค๊ณ  ์ƒ์ƒํ•ด๋ณด์„ธ์š”. ๋ฐฐ๊ณ ํ”„์ฃ ? ๐Ÿ˜ซ ๊ทธ๋Ÿฐ๋ฐ ๋งŒ์•ฝ ์ž์ฃผ ์ฃผ๋ฌธํ•˜๋Š” ๋ฉ”๋‰ด๋ฅผ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด๋‘๊ณ  ๋ฐ”๋กœ ๋‚ด์ค€๋‹ค๋ฉด? ์—„์ฒญ ๋น ๋ฅด๊ฒ ์ฃ !

๋ฐ”๋กœ ์ด๊ฒŒ ์บ์‹ฑ(Caching)์˜ ํ•ต์‹ฌ ๊ฐœ๋…์ด์—์š”. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—์„œ ๋งค๋ฒˆ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฑด ์‹œ๊ฐ„์ด ์˜ค๋ž˜ ๊ฑธ๋ ค์š”. ํ•˜๋“œ๋””์Šคํฌ๋‚˜ SSD์— ์ ‘๊ทผํ•˜๋Š” ๊ฑด ์ปดํ“จํ„ฐ ์ž…์žฅ์—์„œ๋Š” ์—„์ฒญ ๋А๋ฆฐ ์ž‘์—…์ด๊ฑฐ๋“ ์š”. ๐ŸŒ

๊ทธ๋ž˜์„œ ๋“ฑ์žฅํ•œ ๊ฒŒ ์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ ์‹œ์Šคํ…œ์ด์—์š”! ๋ฉ”๋ชจ๋ฆฌ(RAM)์— ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•ด๋‘๊ณ  ํ•„์š”ํ•  ๋•Œ ์ดˆ๊ณ ์†์œผ๋กœ ๊บผ๋‚ด ์“ฐ๋Š” ๊ฑฐ์ฃ . ๋ฉ”๋ชจ๋ฆฌ๋Š” ๋””์Šคํฌ๋ณด๋‹ค ์ˆ˜๋ฐฑ ๋ฐฐ์—์„œ ์ˆ˜์ฒœ ๋ฐฐ ๋น ๋ฅด๊ฑฐ๋“ ์š”! โšก
์ „ํ†ต์ ์ธ ๋ฐฉ์‹ ์‚ฌ์šฉ์ž ์š”์ฒญ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋А๋ฆผ ๐ŸŒ 100~500ms ์บ์‹ฑ ์‹œ์Šคํ…œ ์‚ฌ์šฉ์ž ์š”์ฒญ Redis / Memcached ์ดˆ๊ณ ์† โšก 1~5ms ์บ์‹œ ๋ฏธ์Šค ์‹œ DB ์กฐํšŒ ํ›„ ์บ์‹œ ์ €์žฅ

๐ŸŽฏ Redis vs Memcached: ๋ˆ„๊ฐ€ ๋” ์งฑ์ผ๊นŒ?

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ๋‘ ๊ฑฐ์ธ์„ ๋น„๊ตํ•ด๋ณผ๊นŒ์š”? Redis์™€ Memcached๋Š” ๋‘˜ ๋‹ค ์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ์˜ ์Šˆํผ์Šคํƒ€์˜ˆ์š”. ํ•˜์ง€๋งŒ ์„ฑ๊ฒฉ์ด ๊ฝค ๋‹ฌ๋ผ์š”! ๐ŸฅŠ

๐Ÿ“Š ํ•ต์‹ฌ ๋น„๊ตํ‘œ

ํŠน์ง• Redis Memcached
๋ฐ์ดํ„ฐ ๊ตฌ์กฐ String, List, Set, Hash, Sorted Set ๋“ฑ ๋‹ค์–‘ ๐ŸŽจ Key-Value๋งŒ ์ง€์› ๐Ÿ”‘
์˜์†์„ฑ ๋””์Šคํฌ ์ €์žฅ ๊ฐ€๋Šฅ (RDB, AOF) ๐Ÿ’พ ๋ฉ”๋ชจ๋ฆฌ๋งŒ ์‚ฌ์šฉ, ์žฌ์‹œ์ž‘ ์‹œ ๋ฐ์ดํ„ฐ ์†Œ์‹ค โš ๏ธ
๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ ์‹ฑ๊ธ€์Šค๋ ˆ๋“œ (6.0๋ถ€ํ„ฐ I/O ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ) ๐Ÿงต ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ์ง€์› ๐Ÿงถ
๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ LRU, LFU ๋“ฑ ๋‹ค์–‘ํ•œ ์ •์ฑ… ๐ŸŽ›๏ธ LRU ๊ธฐ๋ฐ˜ ๐Ÿ”„
๋ณต์ œ/๊ณ ๊ฐ€์šฉ์„ฑ Master-Slave, Sentinel, Cluster ๐Ÿฐ ์ œํ•œ์  (๋ณ„๋„ ์†”๋ฃจ์…˜ ํ•„์š”) ๐Ÿค”
์†๋„ ๋งค์šฐ ๋น ๋ฆ„ (๋‹จ์ˆœ ์ž‘์—… ์‹œ) โšก ์•ฝ๊ฐ„ ๋” ๋น ๋ฆ„ (๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™œ์šฉ) ๐Ÿš€

๐Ÿ”ด Redis: ๋งŒ๋Šฅ ์Šค์œ„์Šค ์•„๋ฏธ ๋‚˜์ดํ”„

Redis๋Š” "Remote Dictionary Server"์˜ ์•ฝ์ž์˜ˆ์š”. ์ด๋ฆ„์—์„œ ์•Œ ์ˆ˜ ์žˆ๋“ฏ์ด ์›๊ฒฉ์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ๋”•์…”๋„ˆ๋ฆฌ(์‚ฌ์ „) ์„œ๋ฒ„์ฃ ! ๐Ÿ“š

Redis์˜ ์ŠˆํผํŒŒ์›Œ๋“ค:

1๏ธโƒฃ ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ
Redis๋Š” ๋‹จ์ˆœํ•œ Key-Value ์ €์žฅ์†Œ๋ฅผ ๋„˜์–ด์„œ์š”. List, Set, Hash, Sorted Set ๊ฐ™์€ ๋ณต์žกํ•œ ์ž๋ฃŒ๊ตฌ์กฐ๋ฅผ ์ง€์›ํ•ด์š”. ์˜ˆ๋ฅผ ๋“ค์–ด ์‹ค์‹œ๊ฐ„ ๋žญํ‚น ์‹œ์Šคํ…œ์„ ๋งŒ๋“ ๋‹ค๋ฉด? Sorted Set์„ ์“ฐ๋ฉด ๋! ๐Ÿ˜Ž

2๏ธโƒฃ ๋ฐ์ดํ„ฐ ์˜์†์„ฑ
์„œ๋ฒ„๊ฐ€ ์žฌ์‹œ์ž‘๋˜์–ด๋„ ๋ฐ์ดํ„ฐ๋ฅผ ์žƒ์–ด๋ฒ„๋ฆฌ์ง€ ์•Š์•„์š”. RDB(์Šค๋ƒ…์ƒท) ๋ฐฉ์‹์ด๋‚˜ AOF(Append Only File) ๋ฐฉ์‹์œผ๋กœ ๋””์Šคํฌ์— ์ €์žฅํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ ์š”. ์•ˆ์ „๋นต! ๐Ÿ›ก๏ธ

3๏ธโƒฃ Pub/Sub ๋ฉ”์‹œ์ง•
์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ…์ด๋‚˜ ์•Œ๋ฆผ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ๋•Œ ์™„๋ฒฝํ•ด์š”. ๋ฐœํ–‰-๊ตฌ๋… ํŒจํ„ด์„ ๋„ค์ดํ‹ฐ๋ธŒ๋กœ ์ง€์›ํ•˜๊ฑฐ๋“ ์š”! ๐Ÿ“ข

4๏ธโƒฃ ํŠธ๋žœ์žญ์…˜ ์ง€์›
์—ฌ๋Ÿฌ ๋ช…๋ น์–ด๋ฅผ ์›์ž์ ์œผ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด์š”. MULTI, EXEC ๋ช…๋ น์–ด๋กœ ํŠธ๋žœ์žญ์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์ฃ ! ๐Ÿ’ผ
๐Ÿ’ก ์‹ค์ „ ํŒ: Redis๋Š” ์บ์‹ฑ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์„ธ์…˜ ์ €์žฅ์†Œ, ๋ฉ”์‹œ์ง€ ํ, ์‹ค์‹œ๊ฐ„ ๋ถ„์„, ๋ฆฌ๋”๋ณด๋“œ ๋“ฑ ๋‹ค์–‘ํ•œ ์šฉ๋„๋กœ ํ™œ์šฉ๋ผ์š”. ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์‚ฌ์šฉ์ž ์„ธ์…˜ ๊ด€๋ฆฌ๋‚˜ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์‹œ์Šคํ…œ์— Redis๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์–ด์š”!

โšช Memcached: ์‹ฌํ”Œ ์ด์ฆˆ ๋ฒ ์ŠคํŠธ

Memcached๋Š” "Memory Cache Daemon"์˜ ์ค„์ž„๋ง์ด์—์š”. ์ด๋ฆ„์ฒ˜๋Ÿผ ์ •๋ง ์‹ฌํ”Œํ•˜๊ณ  ์ง๊ด€์ ์ด์ฃ ! ๐ŸŽฏ

Memcached์˜ ๊ฐ•์ ๋“ค:

1๏ธโƒฃ ๊ทนํ•œ์˜ ๋‹จ์ˆœํ•จ
Key-Value ์ €์žฅ๋งŒ ์ง€์›ํ•ด์š”. ๋ณต์žกํ•œ ๊ธฐ๋Šฅ์€ ์—†์ง€๋งŒ, ๊ทธ๋ž˜์„œ ๋” ๋น ๋ฅด๊ณ  ์•ˆ์ •์ ์ด์—์š”. ๋ฐฐ์šฐ๊ธฐ๋„ ์‰ฝ๊ณ ์š”! ๐Ÿ“–

2๏ธโƒฃ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ
์—ฌ๋Ÿฌ CPU ์ฝ”์–ด๋ฅผ ํšจ์œจ์ ์œผ๋กœ ํ™œ์šฉํ•ด์š”. ๋Œ€์šฉ๋Ÿ‰ ํŠธ๋ž˜ํ”ฝ ์ฒ˜๋ฆฌ์— ์œ ๋ฆฌํ•˜์ฃ ! ๐Ÿ’ช

3๏ธโƒฃ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ
๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ์ด ์ตœ์ ํ™”๋˜์–ด ์žˆ์–ด์š”. Slab Allocator๋ผ๋Š” ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ๋ฐฉ์‹์œผ๋กœ ๋ฉ”๋ชจ๋ฆฌ ๋‹จํŽธํ™”๋ฅผ ์ตœ์†Œํ™”ํ•ด์š”! ๐Ÿงฉ

4๏ธโƒฃ ์ˆ˜ํ‰ ํ™•์žฅ
์—ฌ๋Ÿฌ ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ถ„์‚ฐ์‹œํ‚ค๊ธฐ ์‰ฌ์›Œ์š”. Consistent Hashing์„ ์‚ฌ์šฉํ•˜๋ฉด ์„œ๋ฒ„ ์ถ”๊ฐ€/์ œ๊ฑฐ๊ฐ€ ๋ถ€๋“œ๋Ÿฝ๊ฒŒ ์ด๋ค„์ ธ์š”! ๐Ÿ“ˆ
โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ: Memcached๋Š” ๋ฐ์ดํ„ฐ ์˜์†์„ฑ์ด ์—†์–ด์š”. ์„œ๋ฒ„๊ฐ€ ์žฌ์‹œ์ž‘๋˜๋ฉด ๋ชจ๋“  ๋ฐ์ดํ„ฐ๊ฐ€ ๋‚ ์•„๊ฐ€์š”! ๊ทธ๋ž˜์„œ ์ˆœ์ˆ˜ ์บ์‹ฑ ์šฉ๋„๋กœ๋งŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์•„์š”. ์ค‘์š”ํ•œ ๋ฐ์ดํ„ฐ๋Š” ๋ฐ˜๋“œ์‹œ DB์—๋„ ์ €์žฅํ•˜์„ธ์š”!

๐ŸŽฎ Redis ์‹ค์ „ ํ™œ์šฉ๋ฒ•

์ด๋ก ์€ ์ด์ œ ๊ทธ๋งŒ! ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ์“ฐ๋Š”์ง€ ๋ณผ๊นŒ์š”? ์ฝ”๋“œ๋กœ ์ง์ ‘ ํ™•์ธํ•ด๋ด์š”! ๐Ÿ‘จโ€๐Ÿ’ป

๐Ÿ”ง Redis ์„ค์น˜ ๋ฐ ๊ธฐ๋ณธ ์„ค์ •

๋จผ์ € Redis๋ฅผ ์„ค์น˜ํ•ด์•ผ๊ฒ ์ฃ ? ์šด์˜์ฒด์ œ๋ณ„๋กœ ๋ฐฉ๋ฒ•์ด ์กฐ๊ธˆ์”ฉ ๋‹ฌ๋ผ์š”.

Ubuntu/Debian:
sudo apt update
sudo apt install redis-server
sudo systemctl start redis-server
sudo systemctl enable redis-server
macOS (Homebrew):
brew install redis
brew services start redis
Windows:
Windows๋Š” ๊ณต์‹ ์ง€์›์ด ์•ˆ ๋ผ์š”. WSL(Windows Subsystem for Linux)์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ Docker๋ฅผ ํ™œ์šฉํ•˜๋Š” ๊ฒŒ ์ข‹์•„์š”! ๐Ÿณ
docker run --name my-redis -p 6379:6379 -d redis

๐Ÿ’ป Python์œผ๋กœ Redis ์‚ฌ์šฉํ•˜๊ธฐ

Python์—์„œ Redis๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด redis-py ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ํ•„์š”ํ•ด์š”. ์„ค์น˜๋Š” ๊ฐ„๋‹จํ•ด์š”!
pip install redis
๊ธฐ๋ณธ ์—ฐ๊ฒฐ ๋ฐ ์‚ฌ์šฉ:
import redis

# Redis ์—ฐ๊ฒฐ
r = redis.Redis(host='localhost', port=6379, db=0, decode_responses=True)

# ๊ธฐ๋ณธ SET/GET
r.set('username', 'coding_master')
username = r.get('username')
print(username)  # 'coding_master'

# ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์„ค์ • (์ดˆ ๋‹จ์œ„)
r.setex('session_token', 3600, 'abc123xyz')  # 1์‹œ๊ฐ„ ํ›„ ์ž๋™ ์‚ญ์ œ

# ์ˆซ์ž ์ฆ๊ฐ€/๊ฐ์†Œ
r.set('page_views', 0)
r.incr('page_views')  # 1 ์ฆ๊ฐ€
r.incrby('page_views', 10)  # 10 ์ฆ๊ฐ€
print(r.get('page_views'))  # '11'

# ํ‚ค ์กด์žฌ ํ™•์ธ
if r.exists('username'):
    print('ํ‚ค๊ฐ€ ์กด์žฌํ•ด์š”!')

# ํ‚ค ์‚ญ์ œ
r.delete('username')
โœ… ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค: decode_responses=True ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ”์ดํŠธ ๋ฌธ์ž์—ด ๋Œ€์‹  ์ผ๋ฐ˜ ๋ฌธ์ž์—ด๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด์š”. ํ›จ์”ฌ ํŽธ๋ฆฌํ•˜์ฃ !

๐Ÿ“ List ์ž๋ฃŒ๊ตฌ์กฐ ํ™œ์šฉ

List๋Š” ์ˆœ์„œ๊ฐ€ ์žˆ๋Š” ๋ฌธ์ž์—ด ๋ชฉ๋ก์ด์—์š”. ์ตœ๊ทผ ํ™œ๋™ ๊ธฐ๋ก, ์ž‘์—… ํ ๋“ฑ์— ์™„๋ฒฝํ•ด์š”! ๐Ÿ“‹
# ๋ฆฌ์ŠคํŠธ์— ์š”์†Œ ์ถ”๊ฐ€
r.lpush('recent_searches', 'ํŒŒ์ด์ฌ ๊ฐ•์˜')  # ์™ผ์ชฝ์— ์ถ”๊ฐ€
r.lpush('recent_searches', 'Redis ํŠœํ† ๋ฆฌ์–ผ')
r.rpush('recent_searches', '๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ตœ์ ํ™”')  # ์˜ค๋ฅธ์ชฝ์— ์ถ”๊ฐ€

# ๋ฆฌ์ŠคํŠธ ์กฐํšŒ
searches = r.lrange('recent_searches', 0, -1)  # ์ „์ฒด ์กฐํšŒ
print(searches)  # ['Redis ํŠœํ† ๋ฆฌ์–ผ', 'ํŒŒ์ด์ฌ ๊ฐ•์˜', '๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ตœ์ ํ™”']

# ๋ฆฌ์ŠคํŠธ ๊ธธ์ด
length = r.llen('recent_searches')

# ์š”์†Œ ์ œ๊ฑฐ
r.lpop('recent_searches')  # ์™ผ์ชฝ์—์„œ ์ œ๊ฑฐ
r.rpop('recent_searches')  # ์˜ค๋ฅธ์ชฝ์—์„œ ์ œ๊ฑฐ

# ์ตœ๊ทผ 5๊ฐœ๋งŒ ์œ ์ง€ (๋‚˜๋จธ์ง€ ์‚ญ์ œ)
r.ltrim('recent_searches', 0, 4)
๐Ÿ’ก ํ™œ์šฉ ์•„์ด๋””์–ด: ์‚ฌ์šฉ์ž์˜ ์ตœ๊ทผ ๊ฒ€์ƒ‰์–ด 5๊ฐœ๋ฅผ ์ €์žฅํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด? lpush๋กœ ์ƒˆ ๊ฒ€์ƒ‰์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ltrim์œผ๋กœ 5๊ฐœ๋งŒ ์œ ์ง€ํ•˜๋ฉด ๋! ๊ฐ„๋‹จํ•˜์ฃ ? ๐Ÿ˜Š

๐ŸŽฒ Set ์ž๋ฃŒ๊ตฌ์กฐ ํ™œ์šฉ

Set์€ ์ค‘๋ณต์ด ์—†๋Š” ์ง‘ํ•ฉ์ด์—์š”. ์ข‹์•„์š” ๋ชฉ๋ก, ํƒœ๊ทธ ๊ด€๋ฆฌ, ์ค‘๋ณต ์ œ๊ฑฐ ๋“ฑ์— ์œ ์šฉํ•ด์š”! ๐ŸŒŸ
# Set์— ์š”์†Œ ์ถ”๊ฐ€
r.sadd('user:1000:likes', 'post:1', 'post:5', 'post:10')
r.sadd('user:2000:likes', 'post:5', 'post:15', 'post:20')

# Set ์กฐํšŒ
likes = r.smembers('user:1000:likes')
print(likes)  # {'post:1', 'post:5', 'post:10'}

# ์š”์†Œ ์กด์žฌ ํ™•์ธ
if r.sismember('user:1000:likes', 'post:5'):
    print('์ด๋ฏธ ์ข‹์•„์š”๋ฅผ ๋ˆŒ๋ €์–ด์š”!')

# Set ํฌ๊ธฐ
count = r.scard('user:1000:likes')

# ๊ต์ง‘ํ•ฉ (๋‘ ์‚ฌ์šฉ์ž๊ฐ€ ๊ณตํ†ต์œผ๋กœ ์ข‹์•„ํ•˜๋Š” ๊ฒŒ์‹œ๋ฌผ)
common = r.sinter('user:1000:likes', 'user:2000:likes')
print(common)  # {'post:5'}

# ํ•ฉ์ง‘ํ•ฉ
all_likes = r.sunion('user:1000:likes', 'user:2000:likes')

# ์ฐจ์ง‘ํ•ฉ (user:1000๋งŒ ์ข‹์•„ํ•˜๋Š” ๊ฒŒ์‹œ๋ฌผ)
unique = r.sdiff('user:1000:likes', 'user:2000:likes')

# ์š”์†Œ ์ œ๊ฑฐ
r.srem('user:1000:likes', 'post:1')

๐Ÿ—‚๏ธ Hash ์ž๋ฃŒ๊ตฌ์กฐ ํ™œ์šฉ

Hash๋Š” ํ•„๋“œ-๊ฐ’ ์Œ์˜ ๋ชจ์Œ์ด์—์š”. ๊ฐ์ฒด ์ €์žฅ์— ์™„๋ฒฝํ•˜์ฃ ! ์‚ฌ์šฉ์ž ํ”„๋กœํ•„, ์ƒํ’ˆ ์ •๋ณด ๋“ฑ์— ์‚ฌ์šฉํ•ด์š”! ๐Ÿ‘ค
# Hash ์„ค์ •
r.hset('user:1000', mapping={
    'name': '๊น€์ฝ”๋”ฉ',
    'email': 'coding@example.com',
    'age': '28',
    'level': 'expert'
})

# ๊ฐœ๋ณ„ ํ•„๋“œ ์„ค์ •
r.hset('user:1000', 'points', '1500')

# ํ•„๋“œ ์กฐํšŒ
name = r.hget('user:1000', 'name')
print(name)  # '๊น€์ฝ”๋”ฉ'

# ์ „์ฒด ์กฐํšŒ
user_data = r.hgetall('user:1000')
print(user_data)
# {'name': '๊น€์ฝ”๋”ฉ', 'email': 'coding@example.com', 'age': '28', 'level': 'expert', 'points': '1500'}

# ์ˆซ์ž ํ•„๋“œ ์ฆ๊ฐ€
r.hincrby('user:1000', 'points', 100)  # ํฌ์ธํŠธ 100 ์ฆ๊ฐ€

# ํ•„๋“œ ์กด์žฌ ํ™•์ธ
if r.hexists('user:1000', 'email'):
    print('์ด๋ฉ”์ผ์ด ๋“ฑ๋ก๋˜์–ด ์žˆ์–ด์š”!')

# ํ•„๋“œ ์‚ญ์ œ
r.hdel('user:1000', 'age')

# ๋ชจ๋“  ํ•„๋“œ ์ด๋ฆ„ ์กฐํšŒ
fields = r.hkeys('user:1000')

# ๋ชจ๋“  ๊ฐ’ ์กฐํšŒ
values = r.hvals('user:1000')
๐Ÿ’ก ์™œ Hash๋ฅผ ์“ธ๊นŒ? JSON ๋ฌธ์ž์—ด๋กœ ์ €์žฅํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค Hash๊ฐ€ ๋” ํšจ์œจ์ ์ด์—์š”! ํŠน์ • ํ•„๋“œ๋งŒ ์ˆ˜์ •ํ•˜๊ฑฐ๋‚˜ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์–ด์„œ ๋ฉ”๋ชจ๋ฆฌ์™€ ๋„คํŠธ์›Œํฌ ๋Œ€์—ญํญ์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ ์š”! ๐Ÿ’ฐ

๐Ÿ† Sorted Set ํ™œ์šฉ (๋žญํ‚น ์‹œ์Šคํ…œ)

Sorted Set์€ ์ ์ˆ˜์™€ ํ•จ๊ป˜ ์ €์žฅ๋˜๋Š” ์ •๋ ฌ๋œ ์ง‘ํ•ฉ์ด์—์š”. ์‹ค์‹œ๊ฐ„ ๋ฆฌ๋”๋ณด๋“œ, ์šฐ์„ ์ˆœ์œ„ ํ ๋“ฑ์— ์ตœ์ ์ด์ฃ ! ๐Ÿฅ‡
# ์ ์ˆ˜์™€ ํ•จ๊ป˜ ์ถ”๊ฐ€
r.zadd('game_leaderboard', {
    'player1': 1500,
    'player2': 2300,
    'player3': 1800,
    'player4': 2100,
    'player5': 1950
})

# ์ ์ˆ˜ ์ฆ๊ฐ€
r.zincrby('game_leaderboard', 200, 'player1')  # player1 ์ ์ˆ˜ 200 ์ฆ๊ฐ€

# ์ƒ์œ„ 3๋ช… ์กฐํšŒ (์ ์ˆ˜ ๋†’์€ ์ˆœ)
top3 = r.zrevrange('game_leaderboard', 0, 2, withscores=True)
print(top3)
# [('player2', 2300.0), ('player4', 2100.0), ('player5', 1950.0)]

# ํ•˜์œ„ 3๋ช… ์กฐํšŒ (์ ์ˆ˜ ๋‚ฎ์€ ์ˆœ)
bottom3 = r.zrange('game_leaderboard', 0, 2, withscores=True)

# ํŠน์ • ํ”Œ๋ ˆ์ด์–ด ์ˆœ์œ„ ์กฐํšŒ (0๋ถ€ํ„ฐ ์‹œ์ž‘)
rank = r.zrevrank('game_leaderboard', 'player1')
print(f'player1์˜ ์ˆœ์œ„: {rank + 1}์œ„')

# ํŠน์ • ํ”Œ๋ ˆ์ด์–ด ์ ์ˆ˜ ์กฐํšŒ
score = r.zscore('game_leaderboard', 'player1')

# ์ ์ˆ˜ ๋ฒ”์œ„๋กœ ์กฐํšŒ (1800~2000์  ์‚ฌ์ด)
mid_range = r.zrangebyscore('game_leaderboard', 1800, 2000, withscores=True)

# ์ „์ฒด ํ”Œ๋ ˆ์ด์–ด ์ˆ˜
total = r.zcard('game_leaderboard')

# ํ”Œ๋ ˆ์ด์–ด ์ œ๊ฑฐ
r.zrem('game_leaderboard', 'player3')
โœ… ์‹ค์ „ ์˜ˆ์ œ: ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ "์ด๋ฒˆ ์ฃผ ์ธ๊ธฐ ์žฌ๋Šฅ์‚ฌ" ๋žญํ‚น์„ ๋งŒ๋“ ๋‹ค๋ฉด? ๊ฑฐ๋ž˜ ๊ฑด์ˆ˜๋‚˜ ํ‰์ ์„ ์ ์ˆ˜๋กœ ์‚ฌ์šฉํ•ด์„œ Sorted Set์— ์ €์žฅํ•˜๋ฉด ์‹ค์‹œ๊ฐ„ ๋žญํ‚น์ด ์™„์„ฑ๋ผ์š”! ๐ŸŽ‰

๐Ÿ” ์„ธ์…˜ ๊ด€๋ฆฌ์˜ ๋ชจ๋“  ๊ฒƒ

์ž, ์ด์ œ ์„ธ์…˜ ๊ด€๋ฆฌ์— ๋Œ€ํ•ด ๊นŠ์ด ํŒŒ๊ณ ๋“ค์–ด๋ณผ๊นŒ์š”? ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์„ธ์…˜์€ ์ •๋ง ์ค‘์š”ํ•ด์š”! ๐ŸŽซ

๐Ÿค” ์„ธ์…˜์ด ๋ญ์˜ˆ์š”?

์„ธ์…˜(Session)์€ ์‚ฌ์šฉ์ž๊ฐ€ ์›น์‚ฌ์ดํŠธ์— ์ ‘์†ํ•ด์„œ ๋– ๋‚  ๋•Œ๊นŒ์ง€์˜ ์ƒํƒœ ์ •๋ณด์˜ˆ์š”. HTTP๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜์ง€ ์•Š๋Š” ํ”„๋กœํ† ์ฝœ์ด๊ฑฐ๋“ ์š”. ๊ทธ๋ž˜์„œ "์ด ์‚ฌ๋žŒ์ด ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์ธ์ง€" ๊ธฐ์–ตํ•˜๋ ค๋ฉด ์„ธ์…˜์ด ํ•„์š”ํ•ด์š”! ๐Ÿง 

์„ธ์…˜์˜ ์ƒ๋ช…์ฃผ๊ธฐ:
1๏ธโƒฃ ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธํ•˜๋ฉด ์„œ๋ฒ„๊ฐ€ ์„ธ์…˜ ID๋ฅผ ์ƒ์„ฑํ•ด์š”
2๏ธโƒฃ ์„ธ์…˜ ID๋ฅผ ์ฟ ํ‚ค๋กœ ๋ธŒ๋ผ์šฐ์ €์— ์ „๋‹ฌํ•ด์š”
3๏ธโƒฃ ์ดํ›„ ์š”์ฒญ๋งˆ๋‹ค ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์„ธ์…˜ ID๋ฅผ ํ•จ๊ป˜ ๋ณด๋‚ด์š”
4๏ธโƒฃ ์„œ๋ฒ„๋Š” ์„ธ์…˜ ID๋กœ ์‚ฌ์šฉ์ž๋ฅผ ์‹๋ณ„ํ•ด์š”
5๏ธโƒฃ ๋กœ๊ทธ์•„์›ƒํ•˜๊ฑฐ๋‚˜ ์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ์„ธ์…˜์ด ๋งŒ๋ฃŒ๋ผ์š” โฐ
๐Ÿ‘ค ์‚ฌ์šฉ์ž ์›น ์„œ๋ฒ„ Session Manager Redis / Memcached โ‘  ๋กœ๊ทธ์ธ ์š”์ฒญ โ‘ก ์„ธ์…˜ ID ๋ฐœ๊ธ‰ ๋ธŒ๋ผ์šฐ์ € ์ฟ ํ‚ค session_id: abc123xyz789 ์ดํ›„ ๋ชจ๋“  ์š”์ฒญ์— ์ž๋™์œผ๋กœ ํฌํ•จ๋จ ์„ธ์…˜ ์ €์žฅ์†Œ abc123xyz789: user_id: 1000 username: coding_master login_time: 14:30:00 expires: 3600s ์„ธ์…˜ ๊ฒ€์ฆ โœ“ ์œ ํšจํ•œ ์„ธ์…˜ ID๋ฉด ์‚ฌ์šฉ์ž ์ธ์ฆ ์™„๋ฃŒ!

๐Ÿช ์„ธ์…˜ ์ €์žฅ์†Œ ์„ ํƒํ•˜๊ธฐ

์„ธ์…˜์„ ์–ด๋””์— ์ €์žฅํ• ๊นŒ์š”? ์—ฌ๋Ÿฌ ์˜ต์…˜์ด ์žˆ์–ด์š”!

โŒ ๋ฉ”๋ชจ๋ฆฌ (์„œ๋ฒ„)

์žฅ์ :
โ€ข ๊ฐ€์žฅ ๋น ๋ฆ„
โ€ข ๊ตฌํ˜„ ๊ฐ„๋‹จ

๋‹จ์ :
โ€ข ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ์‹œ ์†Œ์‹ค
โ€ข ๋‹ค์ค‘ ์„œ๋ฒ„ ํ™˜๊ฒฝ ๋ถˆ๊ฐ€
โ€ข ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ์œ„ํ—˜

โš ๏ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค

์žฅ์ :
โ€ข ์˜๊ตฌ ์ €์žฅ
โ€ข ์•ˆ์ •์ 

๋‹จ์ :
โ€ข ๋А๋ฆผ (I/O ์˜ค๋ฒ„ํ—ค๋“œ)
โ€ข DB ๋ถ€ํ•˜ ์ฆ๊ฐ€
โ€ข ํ™•์žฅ์„ฑ ์ œํ•œ

โœ… Redis/Memcached

์žฅ์ :
โ€ข ๋งค์šฐ ๋น ๋ฆ„
โ€ข ๋‹ค์ค‘ ์„œ๋ฒ„ ์ง€์›
โ€ข ์ž๋™ ๋งŒ๋ฃŒ ๊ธฐ๋Šฅ
โ€ข ํ™•์žฅ์„ฑ ์šฐ์ˆ˜

๋‹จ์ :
โ€ข ๋ณ„๋„ ์„œ๋ฒ„ ํ•„์š”
โ€ข ์ถ”๊ฐ€ ๋น„์šฉ
โœ… ๊ฒฐ๋ก : ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ Redis๊ฐ€ ์ตœ์„ ์˜ ์„ ํƒ์ด์—์š”! ์†๋„, ์•ˆ์ •์„ฑ, ํ™•์žฅ์„ฑ์„ ๋ชจ๋‘ ๊ฐ–์ท„๊ฑฐ๋“ ์š”. ํŠนํžˆ ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜์—์„œ๋Š” ํ•„์ˆ˜์˜ˆ์š”! ๐ŸŒŸ

๐Ÿ”จ Flask์—์„œ Redis ์„ธ์…˜ ๊ตฌํ˜„ํ•˜๊ธฐ

Flask ์›น ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ Redis๋ฅผ ์„ธ์…˜ ์ €์žฅ์†Œ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณผ๊ฒŒ์š”! ๐Ÿ
from flask import Flask, session, redirect, url_for, request
from flask_session import Session
import redis

app = Flask(__name__)

# Flask ์„ธ์…˜ ์„ค์ •
app.config['SECRET_KEY'] = 'your-secret-key-here'
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_PERMANENT'] = False
app.config['SESSION_USE_SIGNER'] = True
app.config['SESSION_KEY_PREFIX'] = 'session:'
app.config['SESSION_REDIS'] = redis.Redis(
    host='localhost',
    port=6379,
    db=0
)

Session(app)

@app.route('/login', methods=['POST'])
def login():
    username = request.form.get('username')
    password = request.form.get('password')
    
    # ์‹ค์ œ๋กœ๋Š” DB์—์„œ ์‚ฌ์šฉ์ž ํ™•์ธ
    if username == 'admin' and password == 'password123':
        # ์„ธ์…˜์— ์‚ฌ์šฉ์ž ์ •๋ณด ์ €์žฅ
        session['user_id'] = 1000
        session['username'] = username
        session['is_admin'] = True
        session.permanent = True  # ์˜๊ตฌ ์„ธ์…˜ (์„ค์ •๋œ ์‹œ๊ฐ„๊นŒ์ง€)
        
        return redirect(url_for('dashboard'))
    
    return '๋กœ๊ทธ์ธ ์‹คํŒจ!', 401

@app.route('/dashboard')
def dashboard():
    # ์„ธ์…˜์—์„œ ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ
    if 'user_id' not in session:
        return redirect(url_for('login'))
    
    username = session.get('username')
    return f'ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค, {username}๋‹˜!'

@app.route('/logout')
def logout():
    # ์„ธ์…˜ ์‚ญ์ œ
    session.clear()
    return redirect(url_for('login'))

if __name__ == '__main__':
    app.run(debug=True)
๐Ÿ’ก ๋ณด์•ˆ ํŒ: SECRET_KEY๋Š” ๋ฐ˜๋“œ์‹œ ๋ณต์žกํ•˜๊ณ  ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•˜์„ธ์š”! ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒŒ ์ข‹์•„์š”. ์ฝ”๋“œ์— ์ง์ ‘ ๋„ฃ์œผ๋ฉด ์œ„ํ—˜ํ•ด์š”! ๐Ÿ”’

๐ŸŽฏ Django์—์„œ Redis ์„ธ์…˜ ๊ตฌํ˜„ํ•˜๊ธฐ

Django์—์„œ๋Š” ๋” ๊ฐ„๋‹จํ•ด์š”! django-redis ํŒจํ‚ค์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋! ๐Ÿš€
# settings.py

# Redis ์„ธ์…˜ ๋ฐฑ์—”๋“œ ์„ค์ •
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'

# Redis ์บ์‹œ ์„ค์ •
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
            'PASSWORD': 'your-redis-password',  # ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์žˆ๋‹ค๋ฉด
        },
        'KEY_PREFIX': 'myapp',
        'TIMEOUT': 3600,  # 1์‹œ๊ฐ„
    }
}

# ์„ธ์…˜ ์„ค์ •
SESSION_COOKIE_AGE = 3600  # 1์‹œ๊ฐ„
SESSION_COOKIE_SECURE = True  # HTTPS์—์„œ๋งŒ ์ „์†ก
SESSION_COOKIE_HTTPONLY = True  # JavaScript ์ ‘๊ทผ ์ฐจ๋‹จ
SESSION_COOKIE_SAMESITE = 'Lax'  # CSRF ๋ฐฉ์–ด
views.py์—์„œ ์‚ฌ์šฉํ•˜๊ธฐ:
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required

def user_login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        
        user = authenticate(request, username=username, password=password)
        
        if user is not None:
            login(request, user)
            
            # ์„ธ์…˜์— ์ถ”๊ฐ€ ์ •๋ณด ์ €์žฅ
            request.session['last_login_ip'] = request.META.get('REMOTE_ADDR')
            request.session['login_count'] = request.session.get('login_count', 0) + 1
            
            return redirect('dashboard')
        else:
            return render(request, 'login.html', {'error': '๋กœ๊ทธ์ธ ์‹คํŒจ'})
    
    return render(request, 'login.html')

@login_required
def dashboard(request):
    # ์„ธ์…˜ ์ •๋ณด ์กฐํšŒ
    login_count = request.session.get('login_count', 0)
    last_ip = request.session.get('last_login_ip', 'Unknown')
    
    context = {
        'username': request.user.username,
        'login_count': login_count,
        'last_ip': last_ip,
    }
    
    return render(request, 'dashboard.html', context)

def user_logout(request):
    logout(request)
    return redirect('login')

๐Ÿ›ก๏ธ ์„ธ์…˜ ๋ณด์•ˆ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค

์„ธ์…˜ ๊ด€๋ฆฌ์—์„œ ๋ณด์•ˆ์€ ์ •๋ง ์ค‘์š”ํ•ด์š”! ํ•ด์ปค๋“ค์ด ๋…ธ๋ฆฌ๋Š” ์ฃผ์š” ํƒ€๊ฒŸ์ด๊ฑฐ๋“ ์š”. ๐Ÿ˜ˆ

๐Ÿ” ํ•„์ˆ˜ ๋ณด์•ˆ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

1๏ธโƒฃ ์„ธ์…˜ ID ๋ณดํ˜ธ
โ€ข ์ถฉ๋ถ„ํžˆ ๊ธด ๋žœ๋ค ๊ฐ’ ์‚ฌ์šฉ (์ตœ์†Œ 128๋น„ํŠธ)
โ€ข ์˜ˆ์ธก ๋ถˆ๊ฐ€๋Šฅํ•œ ๊ฐ’์œผ๋กœ ์ƒ์„ฑ
โ€ข HTTPS์—์„œ๋งŒ ์ „์†ก (Secure ํ”Œ๋ž˜๊ทธ)
โ€ข JavaScript ์ ‘๊ทผ ์ฐจ๋‹จ (HttpOnly ํ”Œ๋ž˜๊ทธ)

2๏ธโƒฃ ์„ธ์…˜ ๊ณ ์ • ๊ณต๊ฒฉ ๋ฐฉ์–ด
โ€ข ๋กœ๊ทธ์ธ ์„ฑ๊ณต ์‹œ ์„ธ์…˜ ID ์žฌ์ƒ์„ฑ
โ€ข ๊ถŒํ•œ ๋ณ€๊ฒฝ ์‹œ ์„ธ์…˜ ID ์žฌ์ƒ์„ฑ

3๏ธโƒฃ ์„ธ์…˜ ํ•˜์ด์žฌํ‚น ๋ฐฉ์–ด
โ€ข IP ์ฃผ์†Œ ๊ฒ€์ฆ (์„ ํƒ์ )
โ€ข User-Agent ๊ฒ€์ฆ (์„ ํƒ์ )
โ€ข ์„ธ์…˜ ํƒ€์ž„์•„์›ƒ ์„ค์ •
โ€ข ๋™์‹œ ๋กœ๊ทธ์ธ ์ œํ•œ

4๏ธโƒฃ CSRF ๊ณต๊ฒฉ ๋ฐฉ์–ด
โ€ข CSRF ํ† ํฐ ์‚ฌ์šฉ
โ€ข SameSite ์ฟ ํ‚ค ์†์„ฑ ์„ค์ •
โ€ข Referer ํ—ค๋” ๊ฒ€์ฆ

5๏ธโƒฃ ์„ธ์…˜ ๋งŒ๋ฃŒ ๊ด€๋ฆฌ
โ€ข ์ ˆ๋Œ€ ํƒ€์ž„์•„์›ƒ (์ตœ๋Œ€ ์„ธ์…˜ ์‹œ๊ฐ„)
โ€ข ์ƒ๋Œ€ ํƒ€์ž„์•„์›ƒ (๋งˆ์ง€๋ง‰ ํ™œ๋™ ํ›„ ์‹œ๊ฐ„)
โ€ข ๋กœ๊ทธ์•„์›ƒ ์‹œ ์™„์ „ ์‚ญ์ œ
โš ๏ธ ์‹ค์ œ ์‚ฌ๋ก€: 2019๋…„ ํ•œ ๋Œ€ํ˜• ์‡ผํ•‘๋ชฐ์—์„œ ์„ธ์…˜ ๊ณ ์ • ์ทจ์•ฝ์ ์œผ๋กœ ์ˆ˜์ฒœ ๋ช…์˜ ๊ณ„์ •์ด ํ•ดํ‚น๋‹นํ–ˆ์–ด์š”. ๋กœ๊ทธ์ธ ํ›„ ์„ธ์…˜ ID๋ฅผ ์žฌ์ƒ์„ฑํ•˜์ง€ ์•Š์•˜๋˜ ๊ฒŒ ์›์ธ์ด์—ˆ์ฃ . ๋ณด์•ˆ์€ ์ •๋ง ์ค‘์š”ํ•ด์š”! ๐Ÿšจ

โšก ์„ธ์…˜ ์„ฑ๋Šฅ ์ตœ์ ํ™”

์„ธ์…˜ ๊ด€๋ฆฌ๋„ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•ด์š”! ํŠนํžˆ ๋Œ€๊ทœ๋ชจ ์„œ๋น„์Šค์—์„œ๋Š” ์ž‘์€ ์ฐจ์ด๊ฐ€ ํฐ ์˜ํ–ฅ์„ ๋ฏธ์น˜๊ฑฐ๋“ ์š”. ๐Ÿ“Š

๐Ÿš€ ์„ฑ๋Šฅ ์ตœ์ ํ™” ์ „๋žต

1๏ธโƒฃ ์„ธ์…˜ ๋ฐ์ดํ„ฐ ์ตœ์†Œํ™”
์„ธ์…˜์—๋Š” ๊ผญ ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ์ €์žฅํ•˜์„ธ์š”! ์‚ฌ์šฉ์ž ID ์ •๋„๋ฉด ์ถฉ๋ถ„ํ•ด์š”. ๋‚˜๋จธ์ง€๋Š” ํ•„์š”ํ•  ๋•Œ DB์—์„œ ์กฐํšŒํ•˜๋Š” ๊ฒŒ ๋‚˜์•„์š”.

2๏ธโƒฃ ์••์ถ• ์‚ฌ์šฉ
์„ธ์…˜ ๋ฐ์ดํ„ฐ๊ฐ€ ํฌ๋‹ค๋ฉด ์••์ถ•์„ ๊ณ ๋ คํ•˜์„ธ์š”. Redis๋Š” ์ž๋™ ์••์ถ•์„ ์ง€์›ํ•˜์ง€ ์•Š์ง€๋งŒ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ๋ฒจ์—์„œ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”.
import json
import zlib
import redis

r = redis.Redis()

def save_compressed_session(session_id, data):
    # JSON ์ง๋ ฌํ™” ํ›„ ์••์ถ•
    json_data = json.dumps(data)
    compressed = zlib.compress(json_data.encode('utf-8'))
    r.setex(f'session:{session_id}', 3600, compressed)

def load_compressed_session(session_id):
    # ์••์ถ• ํ•ด์ œ ํ›„ ์—ญ์ง๋ ฌํ™”
    compressed = r.get(f'session:{session_id}')
    if compressed:
        json_data = zlib.decompress(compressed).decode('utf-8')
        return json.loads(json_data)
    return None
3๏ธโƒฃ ์—ฐ๊ฒฐ ํ’€๋ง Redis ์—ฐ๊ฒฐ์„ ๋งค๋ฒˆ ์ƒˆ๋กœ ๋งŒ๋“ค์ง€ ๋ง๊ณ  ์—ฐ๊ฒฐ ํ’€์„ ์‚ฌ์šฉํ•˜์„ธ์š”! ์„ฑ๋Šฅ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋ผ์š”.
import redis

# ์—ฐ๊ฒฐ ํ’€ ์ƒ์„ฑ
pool = redis.ConnectionPool(
    host='localhost',
    port=6379,
    db=0,
    max_connections=50,
    decode_responses=True
)

# ์—ฐ๊ฒฐ ํ’€ ์‚ฌ์šฉ
r = redis.Redis(connection_pool=pool)
4๏ธโƒฃ ํŒŒ์ดํ”„๋ผ์ด๋‹ ์—ฌ๋Ÿฌ ๋ช…๋ น์–ด๋ฅผ ํ•œ ๋ฒˆ์— ๋ณด๋‚ด์„œ ๋„คํŠธ์›Œํฌ ์™•๋ณต์„ ์ค„์ด์„ธ์š”!
# ๋น„ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ•
r.set('key1', 'value1')
r.set('key2', 'value2')
r.set('key3', 'value3')

# ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ• (ํŒŒ์ดํ”„๋ผ์ด๋‹)
pipe = r.pipeline()
pipe.set('key1', 'value1')
pipe.set('key2', 'value2')
pipe.set('key3', 'value3')
pipe.execute()  # ํ•œ ๋ฒˆ์— ์‹คํ–‰!
5๏ธโƒฃ ์„ธ์…˜ ์ •๋ฆฌ ์ž๋™ํ™” ๋งŒ๋ฃŒ๋œ ์„ธ์…˜์„ ์ž๋™์œผ๋กœ ์ •๋ฆฌํ•˜์„ธ์š”. Redis๋Š” ์ž๋™์œผ๋กœ ํ•ด์ฃผ์ง€๋งŒ, ์ถ”๊ฐ€ ์ •๋ฆฌ ๋กœ์ง์ด ํ•„์š”ํ•  ์ˆ˜๋„ ์žˆ์–ด์š”.
import time
from datetime import datetime, timedelta

def cleanup_old_sessions():
    """30์ผ ์ด์ƒ ๋œ ์„ธ์…˜ ์ •๋ฆฌ"""
    cursor = 0
    cutoff_time = (datetime.now() - timedelta(days=30)).timestamp()
    
    while True:
        cursor, keys = r.scan(cursor, match='session:*', count=100)
        
        for key in keys:
            # ์„ธ์…˜ ์ƒ์„ฑ ์‹œ๊ฐ„ ํ™•์ธ
            session_data = r.hgetall(key)
            if session_data:
                created_at = float(session_data.get('created_at', 0))
                if created_at < cutoff_time:
                    r.delete(key)
                    print(f'Deleted old session: {key}')
        
        if cursor == 0:
            break
        
        time.sleep(0.1)  # CPU ๋ถ€ํ•˜ ๋ฐฉ์ง€

๐ŸŽจ ๊ณ ๊ธ‰ ํ™œ์šฉ ํŒจํ„ด

์ด์ œ ์ข€ ๋” ๊ณ ๊ธ‰ ๊ธฐ์ˆ ๋“ค์„ ์•Œ์•„๋ณผ๊นŒ์š”? ์‹ค๋ฌด์—์„œ ์ •๋ง ์œ ์šฉํ•œ ํŒจํ„ด๋“ค์ด์—์š”! ๐Ÿ’Ž

๐Ÿ”„ ์บ์‹œ ๋ฌดํšจํ™” ์ „๋žต

์บ์‹œ์˜ ๊ฐ€์žฅ ์–ด๋ ค์šด ๋ถ€๋ถ„์ด ๋ฐ”๋กœ "์–ธ์ œ ์บ์‹œ๋ฅผ ์ง€์šธ ๊ฒƒ์ธ๊ฐ€"์˜ˆ์š”. ์ž˜๋ชปํ•˜๋ฉด ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋˜์ฃ ! ๐Ÿ˜ฑ

๐Ÿ“‹ ์ฃผ์š” ๋ฌดํšจํ™” ์ „๋žต

1๏ธโƒฃ TTL (Time To Live) ๊ธฐ๋ฐ˜
๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์ด์—์š”. ์ผ์ • ์‹œ๊ฐ„ ํ›„ ์ž๋™์œผ๋กœ ๋งŒ๋ฃŒ๋ผ์š”.
# 10๋ถ„ ํ›„ ์ž๋™ ๋งŒ๋ฃŒ
r.setex('user:1000:profile', 600, json.dumps(user_data))

# ๋˜๋Š”
r.set('product:500', json.dumps(product_data), ex=3600)  # 1์‹œ๊ฐ„
2๏ธโƒฃ Write-Through ํŒจํ„ด ๋ฐ์ดํ„ฐ๋ฅผ ์“ธ ๋•Œ ์บ์‹œ์™€ DB๋ฅผ ๋™์‹œ์— ์—…๋ฐ์ดํŠธํ•ด์š”. ์ผ๊ด€์„ฑ์ด ๋ณด์žฅ๋˜์ง€๋งŒ ์“ฐ๊ธฐ๊ฐ€ ๋А๋ ค์ ธ์š”.
def update_user_profile(user_id, new_data):
    # 1. DB ์—…๋ฐ์ดํŠธ
    db.update_user(user_id, new_data)
    
    # 2. ์บ์‹œ ์—…๋ฐ์ดํŠธ
    cache_key = f'user:{user_id}:profile'
    r.setex(cache_key, 3600, json.dumps(new_data))
    
    return True
3๏ธโƒฃ Cache-Aside ํŒจํ„ด ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ํŒจํ„ด์ด์—์š”! ์ฝ์„ ๋•Œ๋งŒ ์บ์‹œ๋ฅผ ํ™•์ธํ•˜๊ณ , ์“ธ ๋•Œ๋Š” ์บ์‹œ๋ฅผ ๋ฌดํšจํ™”ํ•ด์š”.
def get_user_profile(user_id):
    cache_key = f'user:{user_id}:profile'
    
    # 1. ์บ์‹œ ํ™•์ธ
    cached_data = r.get(cache_key)
    if cached_data:
        return json.loads(cached_data)
    
    # 2. ์บ์‹œ ๋ฏธ์Šค - DB ์กฐํšŒ
    user_data = db.get_user(user_id)
    
    # 3. ์บ์‹œ์— ์ €์žฅ
    if user_data:
        r.setex(cache_key, 3600, json.dumps(user_data))
    
    return user_data

def update_user_profile(user_id, new_data):
    # 1. DB ์—…๋ฐ์ดํŠธ
    db.update_user(user_id, new_data)
    
    # 2. ์บ์‹œ ๋ฌดํšจํ™”
    cache_key = f'user:{user_id}:profile'
    r.delete(cache_key)
    
    return True
4๏ธโƒฃ ํƒœ๊ทธ ๊ธฐ๋ฐ˜ ๋ฌดํšจํ™” ๊ด€๋ จ๋œ ์—ฌ๋Ÿฌ ์บ์‹œ๋ฅผ ํ•œ ๋ฒˆ์— ๋ฌดํšจํ™”ํ•  ์ˆ˜ ์žˆ์–ด์š”!
def cache_with_tags(key, data, tags, ttl=3600):
    """ํƒœ๊ทธ์™€ ํ•จ๊ป˜ ์บ์‹œ ์ €์žฅ"""
    # ๋ฐ์ดํ„ฐ ์ €์žฅ
    r.setex(key, ttl, json.dumps(data))
    
    # ๊ฐ ํƒœ๊ทธ์— ํ‚ค ์ถ”๊ฐ€
    for tag in tags:
        r.sadd(f'tag:{tag}', key)
        r.expire(f'tag:{tag}', ttl)

def invalidate_by_tag(tag):
    """ํŠน์ • ํƒœ๊ทธ์˜ ๋ชจ๋“  ์บ์‹œ ๋ฌดํšจํ™”"""
    tag_key = f'tag:{tag}'
    keys = r.smembers(tag_key)
    
    if keys:
        r.delete(*keys)  # ๋ชจ๋“  ํ‚ค ์‚ญ์ œ
        r.delete(tag_key)  # ํƒœ๊ทธ๋„ ์‚ญ์ œ
    
    return len(keys)

# ์‚ฌ์šฉ ์˜ˆ์‹œ
user_data = {'id': 1000, 'name': '๊น€์ฝ”๋”ฉ'}
cache_with_tags(
    'user:1000:profile',
    user_data,
    tags=['user:1000', 'users', 'profiles'],
    ttl=3600
)

# ์‚ฌ์šฉ์ž 1000์˜ ๋ชจ๋“  ์บ์‹œ ๋ฌดํšจํ™”
invalidate_by_tag('user:1000')
๐Ÿ’ก ์‹ค์ „ ํŒ: ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์žฌ๋Šฅ์‚ฌ์˜ ํ”„๋กœํ•„์„ ์ˆ˜์ •ํ•˜๋ฉด ๊ด€๋ จ๋œ ๋ชจ๋“  ์บ์‹œ(ํ”„๋กœํ•„, ๋ฆฌ๋ทฐ ๋ชฉ๋ก, ๋žญํ‚น ๋“ฑ)๋ฅผ ๋ฌดํšจํ™”ํ•ด์•ผ ํ•ด์š”. ํƒœ๊ทธ ๊ธฐ๋ฐ˜ ๋ฌดํšจํ™”๊ฐ€ ์™„๋ฒฝํ•œ ํ•ด๊ฒฐ์ฑ…์ด์ฃ ! ๐ŸŽฏ

๐Ÿ”’ ๋ถ„์‚ฐ ๋ฝ(Distributed Lock)

์—ฌ๋Ÿฌ ์„œ๋ฒ„๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ์ž์›์— ์ ‘๊ทผํ•˜๋ฉด ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒจ์š”. ๋ถ„์‚ฐ ๋ฝ์œผ๋กœ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์–ด์š”! ๐Ÿ”
import time
import uuid

class RedisLock:
    def __init__(self, redis_client, lock_name, timeout=10):
        self.redis = redis_client
        self.lock_name = f'lock:{lock_name}'
        self.timeout = timeout
        self.identifier = str(uuid.uuid4())
    
    def acquire(self, blocking=True, timeout=None):
        """๋ฝ ํš๋“"""
        end_time = time.time() + (timeout or self.timeout)
        
        while True:
            # SET NX (์กด์žฌํ•˜์ง€ ์•Š์„ ๋•Œ๋งŒ ์„ค์ •)
            if self.redis.set(self.lock_name, self.identifier, 
                            nx=True, ex=self.timeout):
                return True
            
            if not blocking:
                return False
            
            if time.time() > end_time:
                return False
            
            time.sleep(0.001)  # 1ms ๋Œ€๊ธฐ
    
    def release(self):
        """๋ฝ ํ•ด์ œ"""
        # Lua ์Šคํฌ๋ฆฝํŠธ๋กœ ์›์ž์  ์‚ญ์ œ (์ž์‹ ์˜ ๋ฝ๋งŒ ์‚ญ์ œ)
        lua_script = """
        if redis.call("get", KEYS[1]) == ARGV[1] then
            return redis.call("del", KEYS[1])
        else
            return 0
        end
        """
        self.redis.eval(lua_script, 1, self.lock_name, self.identifier)
    
    def __enter__(self):
        self.acquire()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        self.release()

# ์‚ฌ์šฉ ์˜ˆ์‹œ
def process_payment(order_id):
    lock = RedisLock(r, f'order:{order_id}')
    
    with lock:
        # ์ด ๋ธ”๋ก ์•ˆ์—์„œ๋Š” ๋‹ค๋ฅธ ์„œ๋ฒ„๊ฐ€ ์ ‘๊ทผํ•  ์ˆ˜ ์—†์–ด์š”!
        order = db.get_order(order_id)
        
        if order.status == 'pending':
            # ๊ฒฐ์ œ ์ฒ˜๋ฆฌ
            payment_result = process_payment_gateway(order)
            
            if payment_result.success:
                order.status = 'paid'
                db.save_order(order)
                
                # ์บ์‹œ ๋ฌดํšจํ™”
                r.delete(f'order:{order_id}')
        
        return order
โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ: ๋ถ„์‚ฐ ๋ฝ์€ ์‹ ์ค‘ํ•˜๊ฒŒ ์‚ฌ์šฉํ•ด์•ผ ํ•ด์š”! ๋ฝ์„ ๋„ˆ๋ฌด ์˜ค๋ž˜ ์žก๊ณ  ์žˆ์œผ๋ฉด ์„ฑ๋Šฅ์ด ๋–จ์–ด์ง€๊ณ , ๋ฐ๋“œ๋ฝ์ด ๋ฐœ์ƒํ•  ์ˆ˜๋„ ์žˆ์–ด์š”. ๊ผญ ํ•„์š”ํ•œ ๊ณณ์—๋งŒ ์‚ฌ์šฉํ•˜์„ธ์š”! โฐ

๐Ÿ“Š Rate Limiting (์†๋„ ์ œํ•œ)

API ๋‚จ์šฉ์„ ๋ฐฉ์ง€ํ•˜๋ ค๋ฉด ์†๋„ ์ œํ•œ์ด ํ•„์š”ํ•ด์š”! Redis๋กœ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด์š”. ๐Ÿšฆ
def rate_limit_fixed_window(user_id, limit=100, window=60):
    """
    ๊ณ ์ • ์œˆ๋„์šฐ ๋ฐฉ์‹
    - limit: ํ—ˆ์šฉ ํšŸ์ˆ˜
    - window: ์‹œ๊ฐ„ ์œˆ๋„์šฐ (์ดˆ)
    """
    key = f'rate_limit:{user_id}:{int(time.time() // window)}'
    
    current = r.incr(key)
    
    if current == 1:
        r.expire(key, window)
    
    if current > limit:
        return False, 0
    
    return True, limit - current

def rate_limit_sliding_window(user_id, limit=100, window=60):
    """
    ์Šฌ๋ผ์ด๋”ฉ ์œˆ๋„์šฐ ๋ฐฉ์‹ (๋” ์ •ํ™•ํ•จ)
    """
    key = f'rate_limit:{user_id}'
    now = time.time()
    
    # ํŒŒ์ดํ”„๋ผ์ธ ์‚ฌ์šฉ
    pipe = r.pipeline()
    
    # ์˜ค๋ž˜๋œ ์š”์ฒญ ์ œ๊ฑฐ
    pipe.zremrangebyscore(key, 0, now - window)
    
    # ํ˜„์žฌ ์š”์ฒญ ์ถ”๊ฐ€
    pipe.zadd(key, {str(now): now})
    
    # ํ˜„์žฌ ์š”์ฒญ ์ˆ˜ ์กฐํšŒ
    pipe.zcard(key)
    
    # ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์„ค์ •
    pipe.expire(key, window)
    
    results = pipe.execute()
    request_count = results[2]
    
    if request_count > limit:
        return False, 0
    
    return True, limit - request_count

# ์‚ฌ์šฉ ์˜ˆ์‹œ
@app.route('/api/data')
def get_data():
    user_id = session.get('user_id')
    
    allowed, remaining = rate_limit_sliding_window(user_id, limit=100, window=60)
    
    if not allowed:
        return {
            'error': 'Rate limit exceeded',
            'retry_after': 60
        }, 429
    
    # ์ •์ƒ ์ฒ˜๋ฆฌ
    return {
        'data': 'some data',
        'rate_limit_remaining': remaining
    }
โœ… ์‹ค์ „ ํ™œ์šฉ: ์žฌ๋Šฅ๋„ท์—์„œ ๊ฒ€์ƒ‰ API๋ฅผ ์ œ๊ณตํ•œ๋‹ค๋ฉด, ์‚ฌ์šฉ์ž๋‹น ๋ถ„๋‹น 100ํšŒ๋กœ ์ œํ•œํ•  ์ˆ˜ ์žˆ์–ด์š”. ์•…์˜์ ์ธ ํฌ๋กค๋ง์ด๋‚˜ DDoS ๊ณต๊ฒฉ์„ ๋ฐฉ์–ดํ•  ์ˆ˜ ์žˆ์ฃ ! ๐Ÿ›ก๏ธ

๐ŸŽฏ Pub/Sub์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ

Redis์˜ Pub/Sub ๊ธฐ๋Šฅ์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด์š”! ๐Ÿ””
# publisher.py (์•Œ๋ฆผ ๋ฐœ์†ก)
import redis
import json

r = redis.Redis()

def send_notification(user_id, message):
    """ํŠน์ • ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ ๋ฐœ์†ก"""
    channel = f'notifications:user:{user_id}'
    
    notification = {
        'type': 'message',
        'title': '์ƒˆ ๋ฉ”์‹œ์ง€',
        'body': message,
        'timestamp': time.time()
    }
    
    r.publish(channel, json.dumps(notification))

def broadcast_notification(message):
    """๋ชจ๋“  ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ ๋ฐœ์†ก"""
    notification = {
        'type': 'announcement',
        'title': '๊ณต์ง€์‚ฌํ•ญ',
        'body': message,
        'timestamp': time.time()
    }
    
    r.publish('notifications:broadcast', json.dumps(notification))

# subscriber.py (์•Œ๋ฆผ ์ˆ˜์‹ )
import redis
import json

r = redis.Redis()

def listen_notifications(user_id):
    """ํŠน์ • ์‚ฌ์šฉ์ž์˜ ์•Œ๋ฆผ ์ˆ˜์‹ """
    pubsub = r.pubsub()
    
    # ๊ฐœ์ธ ์ฑ„๋„๊ณผ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ ์ฑ„๋„ ๊ตฌ๋…
    pubsub.subscribe(
        f'notifications:user:{user_id}',
        'notifications:broadcast'
    )
    
    print(f'์‚ฌ์šฉ์ž {user_id}์˜ ์•Œ๋ฆผ ์ˆ˜์‹  ์‹œ์ž‘...')
    
    for message in pubsub.listen():
        if message['type'] == 'message':
            data = json.loads(message['data'])
            print(f"[{data['type']}] {data['title']}: {data['body']}")

# ์›น์†Œ์ผ“๊ณผ ์—ฐ๋™
from flask import Flask
from flask_socketio import SocketIO, emit
import threading

app = Flask(__name__)
socketio = SocketIO(app)

def redis_listener():
    """Redis Pub/Sub ๋ฆฌ์Šค๋„ˆ (๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ)"""
    pubsub = r.pubsub()
    pubsub.subscribe('notifications:broadcast')
    
    for message in pubsub.listen():
        if message['type'] == 'message':
            data = json.loads(message['data'])
            # ๋ชจ๋“  ์›น์†Œ์ผ“ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „์†ก
            socketio.emit('notification', data, broadcast=True)

@socketio.on('connect')
def handle_connect():
    user_id = session.get('user_id')
    print(f'์‚ฌ์šฉ์ž {user_id} ์—ฐ๊ฒฐ๋จ')

@socketio.on('disconnect')
def handle_disconnect():
    print('์‚ฌ์šฉ์ž ์—ฐ๊ฒฐ ํ•ด์ œ๋จ')

if __name__ == '__main__':
    # ๋ฐฑ๊ทธ๋ผ์šด๋“œ ๋ฆฌ์Šค๋„ˆ ์‹œ์ž‘
    listener_thread = threading.Thread(target=redis_listener, daemon=True)
    listener_thread.start()
    
    socketio.run(app, debug=True)

๐Ÿ—๏ธ Redis ํด๋Ÿฌ์Šคํ„ฐ์™€ ๊ณ ๊ฐ€์šฉ์„ฑ

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋‹จ์ผ Redis ์„œ๋ฒ„๋กœ๋Š” ๋ถ€์กฑํ•ด์š”. ๊ณ ๊ฐ€์šฉ์„ฑ๊ณผ ํ™•์žฅ์„ฑ์„ ์œ„ํ•ด ํด๋Ÿฌ์Šคํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•ด์•ผ ํ•ด์š”! ๐Ÿฐ

๐Ÿ”„ Master-Slave ๋ณต์ œ

๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๊ณ ๊ฐ€์šฉ์„ฑ ๊ตฌ์„ฑ์ด์—์š”. Master๊ฐ€ ์ฃฝ์œผ๋ฉด Slave๊ฐ€ ์Šน๊ฒฉ๋ผ์š”! ๐Ÿ‘‘

๐Ÿ“ Master ์„ค์ • (redis-master.conf)

port 6379
bind 0.0.0.0
requirepass master_password
masterauth master_password

# ์˜์†์„ฑ ์„ค์ •
save 900 1
save 300 10
save 60 10000

appendonly yes
appendfilename "appendonly.aof"

๐Ÿ“ Slave ์„ค์ • (redis-slave.conf)

port 6380
bind 0.0.0.0
requirepass slave_password

# Master ์ •๋ณด
replicaof 192.168.1.100 6379
masterauth master_password

# ์ฝ๊ธฐ ์ „์šฉ
replica-read-only yes

๐Ÿ›ก๏ธ Redis Sentinel (์ž๋™ ์žฅ์•  ์กฐ์น˜)

Sentinel์€ Redis๋ฅผ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ  ์ž๋™์œผ๋กœ ์žฅ์•  ์กฐ์น˜๋ฅผ ์ˆ˜ํ–‰ํ•ด์š”! ๐Ÿค–
# sentinel.conf
port 26379
sentinel monitor mymaster 192.168.1.100 6379 2
sentinel auth-pass mymaster master_password
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 10000
Python์—์„œ Sentinel ์‚ฌ์šฉ:
from redis.sentinel import Sentinel

# Sentinel ์—ฐ๊ฒฐ
sentinel = Sentinel([
    ('192.168.1.101', 26379),
    ('192.168.1.102', 26379),
    ('192.168.1.103', 26379)
], socket_timeout=0.1)

# Master ์—ฐ๊ฒฐ (์“ฐ๊ธฐ)
master = sentinel.master_for('mymaster', 
                            socket_timeout=0.1,
                            password='master_password')

# Slave ์—ฐ๊ฒฐ (์ฝ๊ธฐ)
slave = sentinel.slave_for('mymaster',
                          socket_timeout=0.1,
                          password='master_password')

# ์‚ฌ์šฉ
master.set('key', 'value')  # Master์— ์“ฐ๊ธฐ
value = slave.get('key')    # Slave์—์„œ ์ฝ๊ธฐ

๐ŸŒ Redis Cluster (์ƒค๋”ฉ)

๋ฐ์ดํ„ฐ๋ฅผ ์—ฌ๋Ÿฌ ๋…ธ๋“œ์— ๋ถ„์‚ฐ์‹œ์ผœ ํ™•์žฅ์„ฑ์„ ํ™•๋ณดํ•ด์š”! ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์— ํ•„์ˆ˜์˜ˆ์š”. ๐Ÿ“ˆ
# Python์—์„œ Redis Cluster ์‚ฌ์šฉ
from redis.cluster import RedisCluster

# ํด๋Ÿฌ์Šคํ„ฐ ์—ฐ๊ฒฐ
rc = RedisCluster(
    startup_nodes=[
        {"host": "192.168.1.101", "port": "7000"},
        {"host": "192.168.1.102", "port": "7001"},
        {"host": "192.168.1.103", "port": "7002"},
    ],
    decode_responses=True,
    skip_full_coverage_check=True
)

# ์ผ๋ฐ˜ Redis์ฒ˜๋Ÿผ ์‚ฌ์šฉ
rc.set('key', 'value')
value = rc.get('key')

# ํด๋Ÿฌ์Šคํ„ฐ ์ •๋ณด ์กฐํšŒ
cluster_info = rc.cluster_info()
print(cluster_info)
๐Ÿ’ก ์–ธ์ œ ๋ญ˜ ์“ธ๊นŒ? โ€ข Master-Slave: ์†Œ๊ทœ๋ชจ, ์ฝ๊ธฐ ์œ„์ฃผ ์›Œํฌ๋กœ๋“œ
โ€ข Sentinel: ์ค‘๊ทœ๋ชจ, ์ž๋™ ์žฅ์•  ์กฐ์น˜ ํ•„์š”
โ€ข Cluster: ๋Œ€๊ทœ๋ชจ, ๋ฐ์ดํ„ฐ ์ƒค๋”ฉ ํ•„์š”

๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ Sentinel๋กœ ์‹œ์ž‘ํ•˜๋Š” ๊ฒŒ ์ข‹์•„์š”! ํ•„์š”ํ•˜๋ฉด ๋‚˜์ค‘์— Cluster๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜๋ฉด ๋ผ์š”. ๐Ÿš€

๐Ÿ” ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ๋””๋ฒ„๊น…

Redis๋ฅผ ์šด์˜ํ•˜๋ ค๋ฉด ๋ชจ๋‹ˆํ„ฐ๋ง์ด ํ•„์ˆ˜์˜ˆ์š”! ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๊ธฐ ์ „์— ๋ฏธ๋ฆฌ ์•Œ์•„์ฐจ๋ ค์•ผ ํ•˜๊ฑฐ๋“ ์š”. ๐Ÿ‘€

๐Ÿ“Š ์ฃผ์š” ๋ชจ๋‹ˆํ„ฐ๋ง ์ง€ํ‘œ

๐ŸŽฏ ํ•ต์‹ฌ ๋ฉ”ํŠธ๋ฆญ

1๏ธโƒฃ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰
โ€ข used_memory: ์‹ค์ œ ์‚ฌ์šฉ ์ค‘์ธ ๋ฉ”๋ชจ๋ฆฌ
โ€ข used_memory_rss: OS๊ฐ€ ํ• ๋‹นํ•œ ๋ฉ”๋ชจ๋ฆฌ
โ€ข mem_fragmentation_ratio: ๋ฉ”๋ชจ๋ฆฌ ๋‹จํŽธํ™” ๋น„์œจ

2๏ธโƒฃ ์„ฑ๋Šฅ ์ง€ํ‘œ
โ€ข instantaneous_ops_per_sec: ์ดˆ๋‹น ๋ช…๋ น์–ด ์ˆ˜
โ€ข hit_rate: ์บ์‹œ ํžˆํŠธ์œจ
โ€ข latency: ์‘๋‹ต ์‹œ๊ฐ„

3๏ธโƒฃ ์—ฐ๊ฒฐ ์ƒํƒœ
โ€ข connected_clients: ์—ฐ๊ฒฐ๋œ ํด๋ผ์ด์–ธํŠธ ์ˆ˜
โ€ข blocked_clients: ๋Œ€๊ธฐ ์ค‘์ธ ํด๋ผ์ด์–ธํŠธ ์ˆ˜
โ€ข rejected_connections: ๊ฑฐ๋ถ€๋œ ์—ฐ๊ฒฐ ์ˆ˜

4๏ธโƒฃ ์˜์†์„ฑ
โ€ข rdb_last_save_time: ๋งˆ์ง€๋ง‰ ์ €์žฅ ์‹œ๊ฐ„
โ€ข aof_current_size: AOF ํŒŒ์ผ ํฌ๊ธฐ
โ€ข aof_last_rewrite_time_sec: ๋งˆ์ง€๋ง‰ ์žฌ์ž‘์„ฑ ์‹œ๊ฐ„
# Python์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋ง
import redis
import time

r = redis.Redis()

def monitor_redis():
    """Redis ์ƒํƒœ ๋ชจ๋‹ˆํ„ฐ๋ง"""
    info = r.info()
    
    # ๋ฉ”๋ชจ๋ฆฌ ์ •๋ณด
    used_memory_mb = info['used_memory'] / 1024 / 1024
    max_memory_mb = info.get('maxmemory', 0) / 1024 / 1024
    memory_usage = (used_memory_mb / max_memory_mb * 100) if max_memory_mb > 0 else 0
    
    print(f"๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ: {used_memory_mb:.2f}MB / {max_memory_mb:.2f}MB ({memory_usage:.1f}%)")
    
    # ์„ฑ๋Šฅ ์ •๋ณด
    ops_per_sec = info['instantaneous_ops_per_sec']
    print(f"์ดˆ๋‹น ๋ช…๋ น์–ด: {ops_per_sec}")
    
    # ์บ์‹œ ํžˆํŠธ์œจ
    keyspace_hits = info.get('keyspace_hits', 0)
    keyspace_misses = info.get('keyspace_misses', 0)
    total = keyspace_hits + keyspace_misses
    hit_rate = (keyspace_hits / total * 100) if total > 0 else 0
    print(f"์บ์‹œ ํžˆํŠธ์œจ: {hit_rate:.2f}%")
    
    # ์—ฐ๊ฒฐ ์ •๋ณด
    connected_clients = info['connected_clients']
    print(f"์—ฐ๊ฒฐ๋œ ํด๋ผ์ด์–ธํŠธ: {connected_clients}")
    
    # ๊ฒฝ๊ณ  ์ฒดํฌ
    if memory_usage > 80:
        print("โš ๏ธ ๊ฒฝ๊ณ : ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์ด 80%๋ฅผ ์ดˆ๊ณผํ–ˆ์–ด์š”!")
    
    if hit_rate < 80:
        print("โš ๏ธ ๊ฒฝ๊ณ : ์บ์‹œ ํžˆํŠธ์œจ์ด ๋‚ฎ์•„์š”!")
    
    if connected_clients > 1000:
        print("โš ๏ธ ๊ฒฝ๊ณ : ์—ฐ๊ฒฐ ์ˆ˜๊ฐ€ ๋งŽ์•„์š”!")

# ์ฃผ๊ธฐ์  ๋ชจ๋‹ˆํ„ฐ๋ง
while True:
    monitor_redis()
    print("-" * 50)
    time.sleep(10)

๐Ÿ› ๋””๋ฒ„๊น… ๋ช…๋ น์–ด

๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์„ ๋•Œ ์œ ์šฉํ•œ Redis ๋ช…๋ น์–ด๋“ค์ด์—์š”! ๐Ÿ”ง
# Redis CLI์—์„œ ์‹คํ–‰

# 1. ๋А๋ฆฐ ์ฟผ๋ฆฌ ํ™•์ธ
SLOWLOG GET 10

# 2. ํ˜„์žฌ ์‹คํ–‰ ์ค‘์ธ ๋ช…๋ น์–ด ํ™•์ธ
CLIENT LIST

# 3. ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๋ถ„์„
MEMORY STATS
MEMORY DOCTOR

# 4. ํ‚ค ๋ถ„์„
SCAN 0 MATCH user:* COUNT 100
DBSIZE

# 5. ํŠน์ • ํ‚ค์˜ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰
MEMORY USAGE user:1000:profile

# 6. ํ‚ค ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ํ™•์ธ
TTL session:abc123

# 7. ํ‚ค ํƒ€์ž… ํ™•์ธ
TYPE user:1000:likes

# 8. ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง
MONITOR  # ์ฃผ์˜: ํ”„๋กœ๋•์…˜์—์„œ๋Š” ์„ฑ๋Šฅ ์˜ํ–ฅ ์žˆ์Œ!

๐Ÿ› ๏ธ ์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ์™€ ํ•ด๊ฒฐ๋ฒ•

โŒ ๋ฌธ์ œ 1: ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ

์ฆ์ƒ: OOM ์—๋Ÿฌ, ๋А๋ฆฐ ์‘๋‹ต
ํ•ด๊ฒฐ:
โ€ข maxmemory ์„ค์ • ํ™•์ธ
โ€ข ๋งŒ๋ฃŒ ์ •์ฑ… ์„ค์ • (maxmemory-policy)
โ€ข ๋ถˆํ•„์š”ํ•œ ํ‚ค ์‚ญ์ œ
โ€ข ๋ฉ”๋ชจ๋ฆฌ ์ฆ์„ค ๋˜๋Š” ํด๋Ÿฌ์Šคํ„ฐ ํ™•์žฅ
# redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru  # LRU ๋ฐฉ์‹์œผ๋กœ ์ž๋™ ์‚ญ์ œ

โŒ ๋ฌธ์ œ 2: ๋†’์€ ๋ ˆ์ดํ„ด์‹œ

์ฆ์ƒ: ์‘๋‹ต์ด ๋А๋ฆผ
ํ•ด๊ฒฐ:
โ€ข SLOWLOG๋กœ ๋А๋ฆฐ ์ฟผ๋ฆฌ ํ™•์ธ
โ€ข ๋ณต์žกํ•œ ๋ช…๋ น์–ด ์ตœ์ ํ™” (KEYS ๋Œ€์‹  SCAN ์‚ฌ์šฉ)
โ€ข ํŒŒ์ดํ”„๋ผ์ด๋‹ ํ™œ์šฉ
โ€ข ๋„คํŠธ์›Œํฌ ์ง€์—ฐ ํ™•์ธ

โŒ ๋ฌธ์ œ 3: ๋‚ฎ์€ ์บ์‹œ ํžˆํŠธ์œจ

์ฆ์ƒ: DB ๋ถ€ํ•˜ ์ฆ๊ฐ€
ํ•ด๊ฒฐ:
โ€ข TTL ์‹œ๊ฐ„ ์กฐ์ •
โ€ข ์บ์‹ฑ ์ „๋žต ์žฌ๊ฒ€ํ† 
โ€ข ์›Œ๋ฐ์—… ๋กœ์ง ์ถ”๊ฐ€
โ€ข ์ž์ฃผ ์‚ฌ์šฉ๋˜๋Š” ๋ฐ์ดํ„ฐ ์šฐ์„  ์บ์‹ฑ

โŒ ๋ฌธ์ œ 4: ์—ฐ๊ฒฐ ์ˆ˜ ์ดˆ๊ณผ

์ฆ์ƒ: ์ƒˆ ์—ฐ๊ฒฐ ๊ฑฐ๋ถ€
ํ•ด๊ฒฐ:
โ€ข maxclients ์„ค์ • ์ฆ๊ฐ€
โ€ข ์—ฐ๊ฒฐ ํ’€ ํฌ๊ธฐ ์กฐ์ •
โ€ข ์œ ํœด ์—ฐ๊ฒฐ ์ •๋ฆฌ
โ€ข ์„œ๋ฒ„ ์ฆ์„ค

๐Ÿ’ฐ ๋น„์šฉ ์ตœ์ ํ™” ์ „๋žต

Redis๋Š” ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋น„์šฉ์ด ๋งŒ๋งŒ์น˜ ์•Š์•„์š”. ๋˜‘๋˜‘ํ•˜๊ฒŒ ์ตœ์ ํ™”ํ•ด์•ผ ํ•ด์š”! ๐Ÿ’ก

๐Ÿ’ต ๋น„์šฉ ์ ˆ๊ฐ ํŒ

1๏ธโƒฃ ๋ฐ์ดํ„ฐ ์••์ถ•
ํฐ ๊ฐ’์€ ์••์ถ•ํ•ด์„œ ์ €์žฅํ•˜์„ธ์š”. ๋ฉ”๋ชจ๋ฆฌ๋ฅผ 50% ์ด์ƒ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์–ด์š”!

2๏ธโƒฃ ์งง์€ ํ‚ค ์ด๋ฆ„ ์‚ฌ์šฉ
"user:1000:profile:data" ๋Œ€์‹  "u:1000:p"์ฒ˜๋Ÿผ ์งง๊ฒŒ ์“ฐ์„ธ์š”. ์ˆ˜๋ฐฑ๋งŒ ๊ฐœ์˜ ํ‚ค๊ฐ€ ์žˆ๋‹ค๋ฉด ํฐ ์ฐจ์ด์˜ˆ์š”!

3๏ธโƒฃ ์ ์ ˆํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์„ ํƒ
โ€ข ์ž‘์€ Hash๋Š” ziplist๋กœ ์ธ์ฝ”๋”ฉ๋ผ์„œ ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์ด์—์š”
โ€ข Set๋ณด๋‹ค Sorted Set์ด ๋” ๋งŽ์€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด์š”

4๏ธโƒฃ ๋งŒ๋ฃŒ ์‹œ๊ฐ„ ์„ค์ •
๋ชจ๋“  ์บ์‹œ์— ์ ์ ˆํ•œ TTL์„ ์„ค์ •ํ•˜์„ธ์š”. ์˜์›ํžˆ ์‚ด์•„์žˆ๋Š” ๋ฐ์ดํ„ฐ๋Š” ๋ฉ”๋ชจ๋ฆฌ ๋‚ญ๋น„์˜ˆ์š”!

5๏ธโƒฃ ์ •๊ธฐ์ ์ธ ์ •๋ฆฌ
์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ํ‚ค๋ฅผ ์ฃผ๊ธฐ์ ์œผ๋กœ ์‚ญ์ œํ•˜์„ธ์š”.

6๏ธโƒฃ ๋ฉ”๋ชจ๋ฆฌ ์ •์ฑ… ์„ค์ •
maxmemory-policy๋ฅผ ์ ์ ˆํžˆ ์„ค์ •ํ•ด์„œ ์ž๋™์œผ๋กœ ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ญ์ œํ•˜์„ธ์š”.
# ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์ ์ธ ์„ค์ •
# redis.conf

# Hash ์ตœ์ ํ™”
hash-max-ziplist-entries 512
hash-max-ziplist-value 64

# List ์ตœ์ ํ™”
list-max-ziplist-size -2

# Set ์ตœ์ ํ™”
set-max-intset-entries 512

# Sorted Set ์ตœ์ ํ™”
zset-max-ziplist-entries 128
zset-max-ziplist-value 64

๐ŸŽ“ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์™„์ „ํ•œ ์„ธ์…˜ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๊ฑธ ๋ชจ๋‘ ํ™œ์šฉํ•ด์„œ ์™„์ „ํ•œ ์„ธ์…˜ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์„ ๋งŒ๋“ค์–ด๋ณผ๊นŒ์š”? ๐Ÿš€
# session_manager.py
import redis
import json
import hashlib
import secrets
from datetime import datetime, timedelta

class SessionManager:
    def __init__(self, redis_client, session_timeout=3600):
        self.redis = redis_client
        self.session_timeout = session_timeout
        self.session_prefix = 'session:'
    
    def create_session(self, user_id, user_data=None):
        """์ƒˆ ์„ธ์…˜ ์ƒ์„ฑ"""
        # ์•ˆ์ „ํ•œ ์„ธ์…˜ ID ์ƒ์„ฑ
        session_id = secrets.token_urlsafe(32)
        
        # ์„ธ์…˜ ๋ฐ์ดํ„ฐ ๊ตฌ์„ฑ
        session_data = {
            'user_id': user_id,
            'created_at': datetime.now().isoformat(),
            'last_activity': datetime.now().isoformat(),
            'ip_address': user_data.get('ip_address') if user_data else None,
            'user_agent': user_data.get('user_agent') if user_data else None,
        }
        
        # ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ ๋ณ‘ํ•ฉ
        if user_data:
            session_data.update(user_data)
        
        # Redis์— ์ €์žฅ
        session_key = f'{self.session_prefix}{session_id}'
        self.redis.hmset(session_key, session_data)
        self.redis.expire(session_key, self.session_timeout)
        
        # ์‚ฌ์šฉ์ž๋ณ„ ์„ธ์…˜ ๋ชฉ๋ก์— ์ถ”๊ฐ€
        user_sessions_key = f'user_sessions:{user_id}'
        self.redis.sadd(user_sessions_key, session_id)
        self.redis.expire(user_sessions_key, self.session_timeout)
        
        return session_id
    
    def get_session(self, session_id):
        """์„ธ์…˜ ์กฐํšŒ"""
        session_key = f'{self.session_prefix}{session_id}'
        session_data = self.redis.hgetall(session_key)
        
        if not session_data:
            return None
        
        # ๋งˆ์ง€๋ง‰ ํ™œ๋™ ์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ
        self.redis.hset(session_key, 'last_activity', 
                       datetime.now().isoformat())
        self.redis.expire(session_key, self.session_timeout)
        
        return session_data
    
    def update_session(self, session_id, data):
        """์„ธ์…˜ ์—…๋ฐ์ดํŠธ"""
        session_key = f'{self.session_prefix}{session_id}'
        
        if not self.redis.exists(session_key):
            return False
        
        self.redis.hmset(session_key, data)
        self.redis.hset(session_key, 'last_activity', 
                       datetime.now().isoformat())
        self.redis.expire(session_key, self.session_timeout)
        
        return True
    
    def delete_session(self, session_id):
        """์„ธ์…˜ ์‚ญ์ œ"""
        session_key = f'{self.session_prefix}{session_id}'
        session_data = self.redis.hgetall(session_key)
        
        if session_data:
            user_id = session_data.get('user_id')
            
            # ์„ธ์…˜ ์‚ญ์ œ
            self.redis.delete(session_key)
            
            # ์‚ฌ์šฉ์ž ์„ธ์…˜ ๋ชฉ๋ก์—์„œ ์ œ๊ฑฐ
            if user_id:
                user_sessions_key = f'user_sessions:{user_id}'
                self.redis.srem(user_sessions_key, session_id)
            
            return True
        
        return False
    
    def delete_user_sessions(self, user_id):
        """ํŠน์ • ์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ์„ธ์…˜ ์‚ญ์ œ"""
        user_sessions_key = f'user_sessions:{user_id}'
        session_ids = self.redis.smembers(user_sessions_key)
        
        for session_id in session_ids:
            self.delete_session(session_id)
        
        self.redis.delete(user_sessions_key)
        
        return len(session_ids)
    
    def get_user_sessions(self, user_id):
        """์‚ฌ์šฉ์ž์˜ ๋ชจ๋“  ํ™œ์„ฑ ์„ธ์…˜ ์กฐํšŒ"""
        user_sessions_key = f'user_sessions:{user_id}'
        session_ids = self.redis.smembers(user_sessions_key)
        
        sessions = []
        for session_id in session_ids:
            session_data = self.get_session(session_id)
            if session_data:
                session_data['session_id'] = session_id
                sessions.append(session_data)
        
        return sessions
    
    def validate_session(self, session_id, ip_address=None, user_agent=None):
        """์„ธ์…˜ ์œ ํšจ์„ฑ ๊ฒ€์ฆ"""
        session_data = self.get_session(session_id)
        
        if not session_data:
            return False, 'Session not found'
        
        # IP ์ฃผ์†Œ ๊ฒ€์ฆ (์„ ํƒ์ )
        if ip_address and session_data.get('ip_address'):
            if session_data['ip_address'] != ip_address:
                return False, 'IP address mismatch'
        
        # User-Agent ๊ฒ€์ฆ (์„ ํƒ์ )
        if user_agent and session_data.get('user_agent'):
            if session_data['user_agent'] != user_agent:
                return False, 'User agent mismatch'
        
        return True, session_data
    
    def cleanup_expired_sessions(self):
        """๋งŒ๋ฃŒ๋œ ์„ธ์…˜ ์ •๋ฆฌ"""
        cursor = 0
        cleaned = 0
        
        while True:
            cursor, keys = self.redis.scan(
                cursor, 
                match=f'{self.session_prefix}*',
                count=100
            )
            
            for key in keys:
                if not self.redis.exists(key):
                    cleaned += 1
            
            if cursor == 0:
                break
        
        return cleaned

# Flask ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์˜ˆ์ œ
from flask import Flask, request, jsonify, make_response

app = Flask(__name__)
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
session_manager = SessionManager(r, session_timeout=3600)

@app.route('/api/login', methods=['POST'])
def login():
    """๋กœ๊ทธ์ธ"""
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    # ์‹ค์ œ๋กœ๋Š” DB์—์„œ ์‚ฌ์šฉ์ž ํ™•์ธ
    # ์—ฌ๊ธฐ์„œ๋Š” ๊ฐ„๋‹จํžˆ ์ฒ˜๋ฆฌ
    if username == 'admin' and password == 'password123':
        user_id = 1000
        
        # ์„ธ์…˜ ์ƒ์„ฑ
        session_id = session_manager.create_session(
            user_id,
            {
                'username': username,
                'ip_address': request.remote_addr,
                'user_agent': request.headers.get('User-Agent'),
                'role': 'admin'
            }
        )
        
        # ์ฟ ํ‚ค ์„ค์ •
        response = make_response(jsonify({
            'success': True,
            'message': '๋กœ๊ทธ์ธ ์„ฑ๊ณต!'
        }))
        
        response.set_cookie(
            'session_id',
            session_id,
            max_age=3600,
            httponly=True,
            secure=True,  # HTTPS์—์„œ๋งŒ
            samesite='Lax'
        )
        
        return response
    
    return jsonify({'success': False, 'message': '๋กœ๊ทธ์ธ ์‹คํŒจ'}), 401

@app.route('/api/profile')
def get_profile():
    """ํ”„๋กœํ•„ ์กฐํšŒ (์ธ์ฆ ํ•„์š”)"""
    session_id = request.cookies.get('session_id')
    
    if not session_id:
        return jsonify({'error': 'Not authenticated'}), 401
    
    # ์„ธ์…˜ ๊ฒ€์ฆ
    valid, result = session_manager.validate_session(
        session_id,
        ip_address=request.remote_addr,
        user_agent=request.headers.get('User-Agent')
    )
    
    if not valid:
        return jsonify({'error': result}), 401
    
    # ์‚ฌ์šฉ์ž ์ •๋ณด ๋ฐ˜ํ™˜
    return jsonify({
        'user_id': result['user_id'],
        'username': result['username'],
        'role': result['role']
    })

@app.route('/api/sessions')
def list_sessions():
    """๋‚ด ์„ธ์…˜ ๋ชฉ๋ก ์กฐํšŒ"""
    session_id = request.cookies.get('session_id')
    
    if not session_id:
        return jsonify({'error': 'Not authenticated'}), 401
    
    session_data = session_manager.get_session(session_id)
    if not session_data:
        return jsonify({'error': 'Invalid session'}), 401
    
    user_id = session_data['user_id']
    sessions = session_manager.get_user_sessions(user_id)
    
    return jsonify({'sessions': sessions})

@app.route('/api/logout', methods=['POST'])
def logout():
    """๋กœ๊ทธ์•„์›ƒ"""
    session_id = request.cookies.get('session_id')
    
    if session_id:
        session_manager.delete_session(session_id)
    
    response = make_response(jsonify({'success': True}))
    response.delete_cookie('session_id')
    
    return response

@app.route('/api/logout-all', methods=['POST'])
def logout_all():
    """๋ชจ๋“  ๊ธฐ๊ธฐ์—์„œ ๋กœ๊ทธ์•„์›ƒ"""
    session_id = request.cookies.get('session_id')
    
    if not session_id:
        return jsonify({'error': 'Not authenticated'}), 401
    
    session_data = session_manager.get_session(session_id)
    if not session_data:
        return jsonify({'error': 'Invalid session'}), 401
    
    user_id = session_data['user_id']
    count = session_manager.delete_user_sessions(user_id)
    
    response = make_response(jsonify({
        'success': True,
        'message': f'{count}๊ฐœ์˜ ์„ธ์…˜์ด ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'
    }))
    response.delete_cookie('session_id')
    
    return response

if __name__ == '__main__':
    app.run(debug=True)
โœ… ์™„์„ฑ! ์ด์ œ ํ”„๋กœ๋•์…˜ ๋ ˆ๋ฒจ์˜ ์„ธ์…˜ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์ด ์™„์„ฑ๋˜์—ˆ์–ด์š”! ๋‹ค์ค‘ ๊ธฐ๊ธฐ ๋กœ๊ทธ์ธ, ์„ธ์…˜ ๊ฒ€์ฆ, ๋ณด์•ˆ ๊ธฐ๋Šฅ๊นŒ์ง€ ๋ชจ๋‘ ๊ฐ–์ท„์ฃ ! ๐ŸŽ‰

๐ŸŒŸ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์ฃ ? ๐Ÿ˜… Redis์™€ Memcached์˜ ๊ธฐ์ดˆ๋ถ€ํ„ฐ ๊ณ ๊ธ‰ ํ™œ์šฉ๋ฒ•, ์„ธ์…˜ ๊ด€๋ฆฌ, ํด๋Ÿฌ์Šคํ„ฐ ๊ตฌ์„ฑ, ๋ชจ๋‹ˆํ„ฐ๋ง๊นŒ์ง€ ๋ชจ๋“  ๊ฑธ ๋‹ค๋ค˜์–ด์š”!

์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์€ ์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ ์‹œ์Šคํ…œ์˜ ์ „๋ฌธ๊ฐ€์˜ˆ์š”! ๐ŸŽ“ ์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ง€์‹์„ ์–ป์—ˆ์œผ๋‹ˆ, ์ž์‹ ๊ฐ์„ ๊ฐ€์ง€๊ณ  ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด์„ธ์š”.

ํ•ต์‹ฌ ์š”์•ฝ:
โœ… Redis๋Š” ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ์™€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” ๋งŒ๋Šฅ ๋„๊ตฌ
โœ… Memcached๋Š” ๋‹จ์ˆœํ•˜๊ณ  ๋น ๋ฅธ ์ˆœ์ˆ˜ ์บ์‹ฑ ์†”๋ฃจ์…˜
โœ… ์„ธ์…˜ ๊ด€๋ฆฌ๋Š” ๋ณด์•ˆ๊ณผ ์„ฑ๋Šฅ์„ ๋ชจ๋‘ ๊ณ ๋ คํ•ด์•ผ ํ•จ
โœ… ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์ตœ์ ํ™”๋Š” ์ง€์†์ ์œผ๋กœ ํ•ด์•ผ ํ•จ
โœ… ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๊ณ ๊ฐ€์šฉ์„ฑ ๊ตฌ์„ฑ์ด ํ•„์ˆ˜

์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์„ ๊ฐœ๋ฐœํ•œ๋‹ค๋ฉด, ์ด ๋ชจ๋“  ๊ธฐ์ˆ ์ด ์‹ค์ œ๋กœ ์‚ฌ์šฉ๋  ๊ฑฐ์˜ˆ์š”. ์‚ฌ์šฉ์ž ์„ธ์…˜ ๊ด€๋ฆฌ, ์‹ค์‹œ๊ฐ„ ์•Œ๋ฆผ, ๋žญํ‚น ์‹œ์Šคํ…œ, API ์†๋„ ์ œํ•œ ๋“ฑ ๋‹ค์–‘ํ•œ ๊ณณ์— ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์ฃ ! ๐Ÿ’ช

๊ณ„์† ๊ณต๋ถ€ํ•˜๊ณ  ์‹คํ—˜ํ•ด๋ณด์„ธ์š”. ์ง์ ‘ ํ•ด๋ณด๋Š” ๊ฒŒ ๊ฐ€์žฅ ์ข‹์€ ํ•™์Šต ๋ฐฉ๋ฒ•์ด์—์š”! ํ™”์ดํŒ…! ๐Ÿš€

๐ŸŽ‰ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค!

Redis & Memcached ๋งˆ์Šคํ„ฐ ๊ณผ์ •์„ ์™„๋ฃŒํ•˜์…จ์Šต๋‹ˆ๋‹ค!

์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์€ ์ธ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ ์ „๋ฌธ๊ฐ€์˜ˆ์š”! ๐Ÿ†

๐Ÿ’ก ๋” ๋ฐฐ์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด? โ€ข Redis ๊ณต์‹ ๋ฌธ์„œ: https://redis.io/documentation
โ€ข Redis University: ๋ฌด๋ฃŒ ์˜จ๋ผ์ธ ๊ฐ•์˜
โ€ข Memcached Wiki: https://github.com/memcached/memcached/wiki
โ€ข ์žฌ๋Šฅ๋„ท์—์„œ ๊ด€๋ จ ๊ฐ•์˜๋‚˜ ๋ฉ˜ํ† ๋ง์„ ์ฐพ์•„๋ณด์„ธ์š”! ๐Ÿ˜Š
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.