Ver3.0 ๐ Redis & Memcached ์์ ์ ๋ณต

๐ Redis & Memcached ์์ ์ ๋ณต
์ธ๋ฉ๋ชจ๋ฆฌ ์บ์ฑ ์์คํ ๊ณผ ์ธ์ ๊ด๋ฆฌ์ ๋ชจ๋ ๊ฒ
๐ช ์ธ๋ฉ๋ชจ๋ฆฌ ์บ์ฑ, ๋๋์ฒด ๋ญ๊ธธ๋?
๋ฐ๋ก ์ด๊ฒ ์บ์ฑ(Caching)์ ํต์ฌ ๊ฐ๋ ์ด์์. ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋งค๋ฒ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฑด ์๊ฐ์ด ์ค๋ ๊ฑธ๋ ค์. ํ๋๋์คํฌ๋ SSD์ ์ ๊ทผํ๋ ๊ฑด ์ปดํจํฐ ์ ์ฅ์์๋ ์์ฒญ ๋๋ฆฐ ์์ ์ด๊ฑฐ๋ ์. ๐
๊ทธ๋์ ๋ฑ์ฅํ ๊ฒ ์ธ๋ฉ๋ชจ๋ฆฌ ์บ์ฑ ์์คํ ์ด์์! ๋ฉ๋ชจ๋ฆฌ(RAM)์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํด๋๊ณ ํ์ํ ๋ ์ด๊ณ ์์ผ๋ก ๊บผ๋ด ์ฐ๋ ๊ฑฐ์ฃ . ๋ฉ๋ชจ๋ฆฌ๋ ๋์คํฌ๋ณด๋ค ์๋ฐฑ ๋ฐฐ์์ ์์ฒ ๋ฐฐ ๋น ๋ฅด๊ฑฐ๋ ์! โก
๐ฏ Redis vs 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์ ์ํผํ์๋ค:
1๏ธโฃ ๋ค์ํ ๋ฐ์ดํฐ ๊ตฌ์กฐ
Redis๋ ๋จ์ํ Key-Value ์ ์ฅ์๋ฅผ ๋์ด์์. List, Set, Hash, Sorted Set ๊ฐ์ ๋ณต์กํ ์๋ฃ๊ตฌ์กฐ๋ฅผ ์ง์ํด์. ์๋ฅผ ๋ค์ด ์ค์๊ฐ ๋ญํน ์์คํ ์ ๋ง๋ ๋ค๋ฉด? Sorted Set์ ์ฐ๋ฉด ๋! ๐
2๏ธโฃ ๋ฐ์ดํฐ ์์์ฑ
์๋ฒ๊ฐ ์ฌ์์๋์ด๋ ๋ฐ์ดํฐ๋ฅผ ์์ด๋ฒ๋ฆฌ์ง ์์์. RDB(์ค๋ ์ท) ๋ฐฉ์์ด๋ AOF(Append Only File) ๋ฐฉ์์ผ๋ก ๋์คํฌ์ ์ ์ฅํ ์ ์๊ฑฐ๋ ์. ์์ ๋นต! ๐ก๏ธ
3๏ธโฃ Pub/Sub ๋ฉ์์ง
์ค์๊ฐ ์ฑํ ์ด๋ ์๋ฆผ ์์คํ ์ ๋ง๋ค ๋ ์๋ฒฝํด์. ๋ฐํ-๊ตฌ๋ ํจํด์ ๋ค์ดํฐ๋ธ๋ก ์ง์ํ๊ฑฐ๋ ์! ๐ข
4๏ธโฃ ํธ๋์ญ์ ์ง์
์ฌ๋ฌ ๋ช ๋ น์ด๋ฅผ ์์์ ์ผ๋ก ์คํํ ์ ์์ด์. MULTI, EXEC ๋ช ๋ น์ด๋ก ํธ๋์ญ์ ์ ๋ง๋ค ์ ์์ฃ ! ๐ผ
โช Memcached: ์ฌํ ์ด์ฆ ๋ฒ ์คํธ
Memcached์ ๊ฐ์ ๋ค:
1๏ธโฃ ๊ทนํ์ ๋จ์ํจ
Key-Value ์ ์ฅ๋ง ์ง์ํด์. ๋ณต์กํ ๊ธฐ๋ฅ์ ์์ง๋ง, ๊ทธ๋์ ๋ ๋น ๋ฅด๊ณ ์์ ์ ์ด์์. ๋ฐฐ์ฐ๊ธฐ๋ ์ฝ๊ณ ์! ๐
2๏ธโฃ ๋ฉํฐ์ค๋ ๋ฉ
์ฌ๋ฌ CPU ์ฝ์ด๋ฅผ ํจ์จ์ ์ผ๋ก ํ์ฉํด์. ๋์ฉ๋ ํธ๋ํฝ ์ฒ๋ฆฌ์ ์ ๋ฆฌํ์ฃ ! ๐ช
3๏ธโฃ ๋ฉ๋ชจ๋ฆฌ ํจ์จ
๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ์ด ์ต์ ํ๋์ด ์์ด์. Slab Allocator๋ผ๋ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ ๋ฐฉ์์ผ๋ก ๋ฉ๋ชจ๋ฆฌ ๋จํธํ๋ฅผ ์ต์ํํด์! ๐งฉ
4๏ธโฃ ์ํ ํ์ฅ
์ฌ๋ฌ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ๋ถ์ฐ์ํค๊ธฐ ์ฌ์์. Consistent Hashing์ ์ฌ์ฉํ๋ฉด ์๋ฒ ์ถ๊ฐ/์ ๊ฑฐ๊ฐ ๋ถ๋๋ฝ๊ฒ ์ด๋ค์ ธ์! ๐
๐ฎ Redis ์ค์ ํ์ฉ๋ฒ
๐ง Redis ์ค์น ๋ฐ ๊ธฐ๋ณธ ์ค์
Ubuntu/Debian:
sudo apt update
sudo apt install redis-server
sudo systemctl start redis-server
sudo systemctl enable redis-server
brew install redis
brew services start redis
Windows๋ ๊ณต์ ์ง์์ด ์ ๋ผ์. WSL(Windows Subsystem for Linux)์ ์ฌ์ฉํ๊ฑฐ๋ Docker๋ฅผ ํ์ฉํ๋ ๊ฒ ์ข์์! ๐ณ
docker run --name my-redis -p 6379:6379 -d redis
๐ป Python์ผ๋ก Redis ์ฌ์ฉํ๊ธฐ
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')
๐ 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)
๐ฒ 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 ์ค์
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')
๐ 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')
๐ ์ธ์ ๊ด๋ฆฌ์ ๋ชจ๋ ๊ฒ
๐ค ์ธ์ ์ด ๋ญ์์?
์ธ์ ์ ์๋ช ์ฃผ๊ธฐ:
1๏ธโฃ ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธํ๋ฉด ์๋ฒ๊ฐ ์ธ์ ID๋ฅผ ์์ฑํด์
2๏ธโฃ ์ธ์ ID๋ฅผ ์ฟ ํค๋ก ๋ธ๋ผ์ฐ์ ์ ์ ๋ฌํด์
3๏ธโฃ ์ดํ ์์ฒญ๋ง๋ค ๋ธ๋ผ์ฐ์ ๊ฐ ์ธ์ ID๋ฅผ ํจ๊ป ๋ณด๋ด์
4๏ธโฃ ์๋ฒ๋ ์ธ์ ID๋ก ์ฌ์ฉ์๋ฅผ ์๋ณํด์
5๏ธโฃ ๋ก๊ทธ์์ํ๊ฑฐ๋ ์๊ฐ์ด ์ง๋๋ฉด ์ธ์ ์ด ๋ง๋ฃ๋ผ์ โฐ
๐ช ์ธ์ ์ ์ฅ์ ์ ํํ๊ธฐ
โ ๋ฉ๋ชจ๋ฆฌ (์๋ฒ)
์ฅ์ :โข ๊ฐ์ฅ ๋น ๋ฆ
โข ๊ตฌํ ๊ฐ๋จ
๋จ์ :
โข ์๋ฒ ์ฌ์์ ์ ์์ค
โข ๋ค์ค ์๋ฒ ํ๊ฒฝ ๋ถ๊ฐ
โข ๋ฉ๋ชจ๋ฆฌ ๋ถ์กฑ ์ํ
โ ๏ธ ๋ฐ์ดํฐ๋ฒ ์ด์ค
์ฅ์ :โข ์๊ตฌ ์ ์ฅ
โข ์์ ์
๋จ์ :
โข ๋๋ฆผ (I/O ์ค๋ฒํค๋)
โข DB ๋ถํ ์ฆ๊ฐ
โข ํ์ฅ์ฑ ์ ํ
โ Redis/Memcached
์ฅ์ :โข ๋งค์ฐ ๋น ๋ฆ
โข ๋ค์ค ์๋ฒ ์ง์
โข ์๋ ๋ง๋ฃ ๊ธฐ๋ฅ
โข ํ์ฅ์ฑ ์ฐ์
๋จ์ :
โข ๋ณ๋ ์๋ฒ ํ์
โข ์ถ๊ฐ ๋น์ฉ
๐จ 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)
๐ฏ 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 ๋ฐฉ์ด
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๏ธโฃ ์ธ์ ๋ง๋ฃ ๊ด๋ฆฌ
โข ์ ๋ ํ์์์ (์ต๋ ์ธ์ ์๊ฐ)
โข ์๋ ํ์์์ (๋ง์ง๋ง ํ๋ ํ ์๊ฐ)
โข ๋ก๊ทธ์์ ์ ์์ ์ญ์
โก ์ธ์ ์ฑ๋ฅ ์ต์ ํ
๐ ์ฑ๋ฅ ์ต์ ํ ์ ๋ต
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
import redis
# ์ฐ๊ฒฐ ํ ์์ฑ
pool = redis.ConnectionPool(
host='localhost',
port=6379,
db=0,
max_connections=50,
decode_responses=True
)
# ์ฐ๊ฒฐ ํ ์ฌ์ฉ
r = redis.Redis(connection_pool=pool)
# ๋นํจ์จ์ ์ธ ๋ฐฉ๋ฒ
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() # ํ ๋ฒ์ ์คํ!
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์๊ฐ
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
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
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 (์๋ ์ ํ)
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
}
๐ฏ 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 ํด๋ฌ์คํฐ์ ๊ณ ๊ฐ์ฉ์ฑ
๐ 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.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
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)
โข Sentinel: ์ค๊ท๋ชจ, ์๋ ์ฅ์ ์กฐ์น ํ์
โข Cluster: ๋๊ท๋ชจ, ๋ฐ์ดํฐ ์ค๋ฉ ํ์
๋๋ถ๋ถ์ ๊ฒฝ์ฐ Sentinel๋ก ์์ํ๋ ๊ฒ ์ข์์! ํ์ํ๋ฉด ๋์ค์ Cluster๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ๋ฉด ๋ผ์. ๐
๐ ๋ชจ๋ํฐ๋ง๊ณผ ๋๋ฒ๊น
๐ ์ฃผ์ ๋ชจ๋ํฐ๋ง ์งํ
๐ฏ ํต์ฌ ๋ฉํธ๋ฆญ
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 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 ์ค์ ์ฆ๊ฐ
โข ์ฐ๊ฒฐ ํ ํฌ๊ธฐ ์กฐ์
โข ์ ํด ์ฐ๊ฒฐ ์ ๋ฆฌ
โข ์๋ฒ ์ฆ์ค
๐ฐ ๋น์ฉ ์ต์ ํ ์ ๋ต
๐ต ๋น์ฉ ์ ๊ฐ ํ
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๋ ๋จ์ํ๊ณ ๋น ๋ฅธ ์์ ์บ์ฑ ์๋ฃจ์
โ ์ธ์ ๊ด๋ฆฌ๋ ๋ณด์๊ณผ ์ฑ๋ฅ์ ๋ชจ๋ ๊ณ ๋ คํด์ผ ํจ
โ ๋ชจ๋ํฐ๋ง๊ณผ ์ต์ ํ๋ ์ง์์ ์ผ๋ก ํด์ผ ํจ
โ ํ๋ก๋์ ํ๊ฒฝ์์๋ ๊ณ ๊ฐ์ฉ์ฑ ๊ตฌ์ฑ์ด ํ์
์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์ ๊ฐ๋ฐํ๋ค๋ฉด, ์ด ๋ชจ๋ ๊ธฐ์ ์ด ์ค์ ๋ก ์ฌ์ฉ๋ ๊ฑฐ์์. ์ฌ์ฉ์ ์ธ์ ๊ด๋ฆฌ, ์ค์๊ฐ ์๋ฆผ, ๋ญํน ์์คํ , API ์๋ ์ ํ ๋ฑ ๋ค์ํ ๊ณณ์ ํ์ฉํ ์ ์์ฃ ! ๐ช
๊ณ์ ๊ณต๋ถํ๊ณ ์คํํด๋ณด์ธ์. ์ง์ ํด๋ณด๋ ๊ฒ ๊ฐ์ฅ ์ข์ ํ์ต ๋ฐฉ๋ฒ์ด์์! ํ์ดํ ! ๐
๐ ์ถํํฉ๋๋ค!
Redis & Memcached ๋ง์คํฐ ๊ณผ์ ์ ์๋ฃํ์ จ์ต๋๋ค!
์ด์ ์ฌ๋ฌ๋ถ์ ์ธ๋ฉ๋ชจ๋ฆฌ ์บ์ฑ ์ ๋ฌธ๊ฐ์์! ๐
โข Redis University: ๋ฌด๋ฃ ์จ๋ผ์ธ ๊ฐ์
โข Memcached Wiki: https://github.com/memcached/memcached/wiki
โข ์ฌ๋ฅ๋ท์์ ๊ด๋ จ ๊ฐ์๋ ๋ฉํ ๋ง์ ์ฐพ์๋ณด์ธ์! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.