Redis: แคชด่วน! เอามาใช้กับ Python ให้เว็บเร็วขึ้น
โอเค คือหลายทีที่เราทำเว็บ ทำ API แล้วมันช้าใช่ป่ะ? แบบว่าทุก request ต้องไป query database ตลอดเลยงี้ บางทีข้อมูลมันไม่ได้เปลี่ยนบ่อยอะ ทำไมต้องไปดึงมาใหม่ทุกรอบ? นี่แหละ ที่มาของ "แคช" หรือ Cache นั่นเอง
ตัวเลือกที่คนชอบใช้กันเยอะๆ ก็มี Redis นี่แหละ. มันเป็น in-memory data store คือเก็บข้อมูลใน RAM ทำให้มันโคตรเร็ว! เหมาะกับการเอามาทำแคชสุดๆ นอกจากแคชก็ใช้ทำอย่างอื่นได้เยอะนะ เช่น message queue, pub/sub ไรงี้ แต่บทความนี้จะเน้นเรื่องแคชละกัน
เริ่มต้น Redis ยังไงดี? (แบบง่ายๆนะ)
ง่ายสุดก็ Docker ครับพี่น้อง! ถ้ามี Docker อยู่แล้วก็พิมพ์ไปเลย
docker run --name my-redis -p 6379:6379 -d redis
แค่นี้ก็ได้ Redis โผล่มาแล้ว ที่พอร์ต 6379 (ซึ่งเป็น default ของมัน)
Python คุยกับ Redis
ใน Python เราใช้ redis-py ครับ install ก่อนเลย
pip install redis
แล้วลองโค้ดนี้ดู:
import redis
# เชื่อมต่อ Redis
# host='localhost' เพราะเรา run Docker บนเครื่องเดียวกัน
# port=6379 ก็ default ของมัน
r = redis.Redis(host='localhost', port=6379, db=0)
# ลองเก็บข้อมูล
r.set('my_key', 'Hello Redis from Python!')
print("ข้อมูลถูกเก็บแล้ว!")
# ลองดึงข้อมูล
value = r.get('my_key')
print(f"ดึงข้อมูลได้: {value.decode('utf-8')}") # ต้อง decode เป็น utf-8 นะ
# ตั้งค่าให้มันหมดอายุ (expire)
r.setex('temp_key', 10, 'อันนี้จะหายไปใน 10 วิ')
print("ตั้งค่า temp_key หมดอายุ 10 วิ")
import time
time.sleep(5)
print(f"5 วิผ่านไป temp_key ยังอยู่ไหม?: {r.get('temp_key')}")
time.sleep(6) # รอเพิ่มอีก 6 วิ รวมเป็น 11 วิ
print(f"11 วิผ่านไป temp_key ยังอยู่ไหม?: {r.get('temp_key')}") # ควรเป็น None แล้ว
ลองรันดู จะเห็นว่า temp_key มันหายไปเองหลัง 10 วิ เจ๋งปะล่ะ!
เอามาทำแคชให้ฟังก์ชัน
สมมติว่าเรามีฟังก์ชันที่มันใช้เวลานาน เช่นไปดึงข้อมูลจาก database ที่กว่าจะ process เสร็จเป็นวินาทีงี้ เราก็เอา Redis มาช่วยแคชผลลัพธ์ได้ง่ายๆ เลย
import redis
import json
import time
r = redis.Redis(host='localhost', port=6379, db=0)
def get_data_from_db_slowly(user_id):
"""
ฟังก์ชันจำลองการดึงข้อมูลจาก DB แบบโคตรช้า
"""
print(f"กำลังดึงข้อมูล user_id: {user_id} จาก DB จริงๆ...")
time.sleep(3) # แกล้งว่าช้า 3 วิ
return {"id": user_id, "name": f"User {user_id}", "email": f"user{user_id}@example.com"}
def cached_get_user_data(user_id):
cache_key = f"user_data:{user_id}"
# ลองเช็คใน Redis ก่อน
cached_data = r.get(cache_key)
if cached_data:
print(f"🎯 ได้ข้อมูลจาก Redis สำหรับ user_id: {user_id}")
return json.loads(cached_data.decode('utf-8'))
# ถ้าไม่มีใน Redis ก็ไปดึงจาก DB จริงๆ
data = get_data_from_db_slowly(user_id)
# เอาข้อมูลไปเก็บใน Redis พร้อมตั้งเวลาหมดอายุ (สมมติ 60 วิ)
r.setex(cache_key, 60, json.dumps(data))
print(f"💾 เก็บข้อมูล user_id: {user_id} ลง Redis แล้ว")
return data
# ลองเรียกครั้งแรก (จะช้า)
print("\n--- เรียกครั้งแรก ---")
user_1_data = cached_get_user_data(1)
print(user_1_data)
# ลองเรียกครั้งที่สอง (จะเร็ว)
print("\n--- เรียกครั้งที่สอง ---")
user_1_data_cached = cached_get_user_data(1)
print(user_1_data_cached)
# ลองเรียก user คนอื่น (จะช้าอีก)
print("\n--- เรียก user คนใหม่ ---")
user_2_data = cached_get_user_data(2)
print(user_2_data)
จะเห็นเลยว่าเรียกซ้ำๆ สำหรับ user_id=1 มันเร็วขึ้นมาก เพราะไปดึงจากแคชแทน
ปัญหาที่อาจจะเจอ (เคยเจอมากับตัว)
บางทีเราแคชข้อมูลไว้แล้ว ข้อมูลใน DB จริงๆ มันเปลี่ยนไป เช่น อัพเดทชื่อ user งี้ แต่ในแคชยังเป็นข้อมูลเก่าอยู่เลย... อันนี้คือ Cache Invalidation ครับ.
วิธีแก้เบื้องต้น: 1. ตั้งเวลาหมดอายุ (TTL): แบบที่ทำ setex ไปเมื่อกี้แหละ ให้มันหายไปเองหลังเวลาที่กำหนด. ง่ายสุดแต่ก็อาจจะเจอปัญหาข้อมูลไม่ตรงกันได้พักนึง. 2. Manual Invalidation: เวลาที่เราอัพเดทข้อมูลใน DB แล้ว ก็ไปสั่ง Redis ให้ลบ key นั้นทิ้งซะเลย (ใช้ r.delete(cache_key)). อันนี้แน่นอนสุด แต่ต้องเขียนโค้ดเพิ่มตอนอัพเดทข้อมูล.
อีกเรื่องคือถ้า Redis มันล่ม หรือคอนเนคไม่ได้ จะทำไง? แอปเราพังเลยปะ? เคยเจอแบบว่าลืมสตาร์ท Docker Redis ตอน Dev แล้วโค้ดมันพยายามไป r.get แล้วก็ ConnectionRefusedError โผล่มาเต็มหน้าจอ! ฮา...
redis.exceptions.ConnectionError: Error 111 connecting to localhost:6379. Connection refused.
วิธีแก้ก็ต้อง try-except ดีๆ ครับ หรือใช้ไลบรารีที่มันมี resilience ในตัว หรือ setup failover ให้ Redis แต่นั่นมันเรื่อง Advance ละ เอาแค่ try-except ก็น่าจะพอสำหรับเริ่มๆ
try:
r.set('test_key', 'ok')
print("Redis connected and working.")
except redis.exceptions.ConnectionError as e:
print(f"เชื่อมต่อ Redis ไม่ได้โว้ย!: {e}")
# อาจจะ fallback ไปดึงจาก DB ตรงๆ หรือ log error
สรุปง่ายๆ
Redis เป็นเครื่องมือที่ดีมากสำหรับการทำแคช เพราะมันเร็วโคตรๆ แค่ต้องเข้าใจเรื่องพวก cache invalidation กับการ handle error ตอนคอนเนคไม่ได้นิดหน่อย.
ลองเอาไปใช้ดูนะครับ รับรองว่าเว็บหรือ API ที่คุณทำมันจะวิ่งได้เร็วขึ้นเยอะเลยล่ะ! ไม่ต้องกลัวเรื่องคอขวดจาก DB ละ :D