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

Read more

ดราม่า "เบิร์ด วันว่างๆ" กับยาแนว: บทเรียนสำคัญของอินฟลูเอนเซอร์และความปลอดภัยบนโซเชียล

ดราม่า "เบิร์ด วันว่างๆ" กับยาแนว: บทเรียนสำคัญของอินฟลูเอนเซอร์และความปลอดภัยบนโซเชียล

เจาะลึกดราม่า "เบิร์ด วันว่างๆ" ใช้ยาแนวเล่นสงกรานต์ คำชี้แจง และผลกระทบต่อสังคม บทเรียนสำคัญสำหรับความรับผิดชอบของอินฟลูเอนเซอร์

By ทีมงาน devdog
Xiaomi 17T หลุดสเปคเด็ดบน Geekbench! ยืนยัน Dimensity 8500 พร้อมแบต 7,000mAh จ่อเปิดตัว

Xiaomi 17T หลุดสเปคเด็ดบน Geekbench! ยืนยัน Dimensity 8500 พร้อมแบต 7,000mAh จ่อเปิดตัว

Xiaomi 17T เตรียมเปิดตัว! พบข้อมูลบน Geekbench ยืนยันใช้ชิป Dimensity 8500 พร้อมแบตเตอรี่จุใจ 7,000mAh คาดบุกตลาดรองเรือธงเร็วๆ นี้

By ทีมงาน devdog
กยศ. เปิดทางรอด! ปรับโครงสร้างหนี้ออนไลน์ หยุดถูกฟ้อง ก่อน 5 ก.ค. 69

กยศ. เปิดทางรอด! ปรับโครงสร้างหนี้ออนไลน์ หยุดถูกฟ้อง ก่อน 5 ก.ค. 69

ผู้กู้ กยศ. กว่า 1 แสนราย เสี่ยงถูกฟ้อง! รีบปรับโครงสร้างหนี้ออนไลน์ผ่านเป๋าตัง/ThaID ก่อน 5 ก.ค. 69 รับสิทธิประโยชน์ ลดดอกเบี้ย หลีกเลี่ยงคดีความ

By ทีมงาน devdog
Baseus MC2: หูฟังคลิปหนีบหูสุดล้ำ แบตอึด 60 ชม. เสียง LDAC กันน้ำ IP67 เพื่ออิสระทางเสียงของคุณ

Baseus MC2: หูฟังคลิปหนีบหูสุดล้ำ แบตอึด 60 ชม. เสียง LDAC กันน้ำ IP67 เพื่ออิสระทางเสียงของคุณ

พบ Baseus MC2 หูฟังคลิปหนีบหูดีไซน์ล้ำ สวมใส่สบายตลอดวัน ด้วยแบตเตอรี่ 60 ชม., กันน้ำ IP67, เสียง LDAC ระดับ Hi-Res และราคาเข้าถึงง่าย

By ทีมงาน devdog