แคชข้อมูลด้วย Redis ใน Flask: เร็วขึ้นเยอะจริงดิ!
เบื่อไหมกับแอปพลิเคชันที่เราเขียนอยู่ แล้วมันก็ช้าเอาช้าเอา โดยเฉพาะตอนที่ต้องดึงข้อมูลจากฐานข้อมูลมาแสดงซ้ำๆ หน้าเดิมๆ เนี่ย? เออ...ใช่! มันเป็นงี้แหละ ปัญหาคลาสสิกเลยนะ
วันนี้เลยอยากมาลองแนะนำ Redis ให้รู้จักกัน มันคือ In-memory Data Structure Store ที่เร็วโคตรๆ เพราะเก็บข้อมูลใน RAM ไง เหมาะมากกับการเอามาทำเป็นแคช (Cache) ให้แอปเราเร็วปรื๋อขึ้นมาทันตาเห็นเลย แค่ไม่กี่บรรทัดโค้ดเองนะเชื่อดิ!
เตรียม Redis ให้พร้อม
วิธีที่ง่ายที่สุดในการรัน Redis ก็คือ Docker แหละ ถ้าใครยังไม่มี Docker ก็ไปหาลงซะนะ มันชีวิตดีขึ้นเยอะ
docker run --name some-redis -p 6379:6379 -d redis
แค่นี้ Redis Server ของเราก็พร้อมใช้งานที่ localhost:6379 แล้วโคตรชิลเลย!
ติดตั้ง Library ใน Python
แน่นอนว่าเราต้องใช้ redis-py ในโปรเจกต์ Flask ของเรา
pip install Flask redis
สร้าง Flask App แบบ Basic
มาลองดูโค้ด Flask ง่ายๆ ที่ดึงข้อมูล (สมมติว่าเป็นข้อมูลจาก DB ที่ช้าโคตรๆ) ทุกครั้งที่มีการเรียกใช้ Endpoint กันก่อนนะ:
# app_no_cache.py
from flask import Flask, jsonify
import time
app = Flask(__name__)
def fetch_data_from_db():
# สมมติว่านี่คือการดึงข้อมูลจาก DB ที่ใช้เวลานาน
time.sleep(2) # Simulate a 2-second DB query
print("\n*** Fetching data from DB... ***\n")
return {"id": 1, "name": "สินค้าสุดว้าว", "price": 999.00, "description": "สินค้านี้โคตรดีจริง!"}
@app.route('/product/<int:product_id>')
def get_product_no_cache(product_id):
print(f"Request for product {product_id} (no cache)")
data = fetch_data_from_db()
return jsonify(data)
if __name__ == '__main__':
app.run(debug=True)
ลองรันแล้วเรียก http://127.0.0.1:5000/product/1 สักสองสามครั้ง จะเห็นว่ามันใช้เวลา 2 วินาทีทุกครั้งเลยใช่ปะล่ะ? น่าหงุดหงิดเนอะ.
เพิ่ม Redis Cache เข้าไป!
ทีนี้มาดูเวอร์ชั่นที่มี Redis Cache กันบ้าง เราจะใช้ redis-py เพื่อ SET และ GET ข้อมูลโดยตรงเลยนะ ไม่ต้องพึ่ง Flask-Caching ก็ได้ ถ้าอยากควบคุมเองเต็มที่:
# app_with_cache.py
from flask import Flask, jsonify
import redis
import time
import json # ต้องใช้ json เพื่อแปลง dict เป็น string ก่อนเก็บใน Redis
app = Flask(__name__)
# ตั้งค่า Redis Connection
# ตรงนี้ถ้า Redis ไม่ได้รันอยู่ หรือคอนฟิกผิด
# มันจะฟ้อง ConnectionError ทันทีที่รันแอปเลยนะ
# เจอประจำ! ต้องเช็คดีๆ
REDIS_HOST = 'localhost'
REDIS_PORT = 6379
REDIS_DB = 0
CACHE_EXPIRATION_SECONDS = 60 # ให้แคชอยู่ได้ 60 วินาที
try:
cache = redis.StrictRedis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB, decode_responses=True)
cache.ping() # ลอง ping ดูว่าเชื่อมต่อได้จริงไหม
print("\n*** Redis connection successful! ***\n")
except redis.exceptions.ConnectionError as e:
print(f"\n!!! ERROR: Could not connect to Redis: {e} !!!\n")
print("!!! Make sure Redis is running and accessible at {REDIS_HOST}:{REDIS_PORT} !!!\n")
cache = None # ถ้าต่อไม่ได้ก็ตั้งเป็น None ไปเลยจะได้ไม่เออเร่อตรงส่วนที่ใช้ cache
def fetch_data_from_db():
# สมมติว่านี่คือการดึงข้อมูลจาก DB ที่ใช้เวลานาน
time.sleep(2)
print("\n*** Fetching data from DB (actual DB call)... ***\n")
return {"id": 1, "name": "สินค้าสุดว้าว", "price": 999.00, "description": "สินค้านี้โคตรดีจริง!"}
@app.route('/product/<int:product_id>')
def get_product_with_cache(product_id):
key = f'product:{product_id}'
if cache:
cached_data = cache.get(key)
if cached_data:
print(f"\n--- Data for {key} found in cache! ---\n")
# อย่าลืมแปลงจาก string กลับเป็น dict นะ
return jsonify(json.loads(cached_data))
# ถ้าไม่มีในแคช หรือต่อ Redis ไม่ได้ ก็ไปดึงจาก DB
data = fetch_data_from_db()
if cache:
# เก็บข้อมูลลงแคช พร้อมกำหนดเวลาหมดอายุ (expire)
# ตรงนี้สำคัญนะ ถ้าลืมกำหนด expire แคชมันจะค้างอยู่ตลอดไปเลย!
# แล้วถ้าข้อมูลใน DB อัปเดต แคชเราจะเก่าอยู่ตลอด ตรงนี้แหละที่ต้องระวัง
cache.setex(key, CACHE_EXPIRATION_SECONDS, json.dumps(data))
print(f"\n--- Data for {key} stored in cache for {CACHE_EXPIRATION_SECONDS} seconds.---\n")
return jsonify(data)
if __name__ == '__main__':
app.run(debug=True)
ลองรัน app_with_cache.py แล้วเรียก http://127.0.0.1:5000/product/1 อีกครั้งดูนะ
- ครั้งแรก: จะเห็นว่าใช้เวลา 2 วินาที เพราะต้องดึงจาก DB
- ครั้งที่สอง: แทบจะทันทีเลย! เพราะดึงจากแคช (ถ้าเรียกภายใน 60 วินาที)
เห็นความแตกต่างปะ? มันเร็วขึ้นเยอะจริงดิ!
ข้อควรระวังและสิ่งที่เจอประจำ
redis.exceptions.ConnectionError: อันนี้เจอบ่อยสุดๆ เลย เวลาลืมรัน Docker Redis หรือรันแล้วแต่พอร์ตชนกัน ต้องเช็คdocker psหรือnetstatให้ดีๆ นะ- Serialization/Deserialization: Redis มันเก็บข้อมูลเป็น String หรือ Binary นะ ถ้าเราจะเก็บ Python dict, list หรือ object ซับซ้อนๆ ลงไป เราต้องแปลงมันเป็น JSON String ก่อน (ใช้
json.dumps()) ตอนดึงออกมาก็ต้องแปลงกลับ (ใช้json.loads()) ไม่งั้นTypeErrorมาแน่ - ลืม
setexหรือttl: ถ้าใช้cache.set(key, value)เฉยๆ เนี่ย แคชมันจะอยู่ตลอดไปเลยนะ! ข้อมูลเก่าในแคชมันก็จะไม่ถูกอัปเดต ถ้า DB เปลี่ยนแล้วล่ะก็ แคชเราก็ยังแสดงข้อมูลเก่าอยู่ดี ทำให้ข้อมูลไม่ตรงกัน อันนี้แหละโคตรอันตราย ต้องกำหนดเวลาหมดอายุsetex(key, seconds, value)หรือexpire(key, seconds)เสมอนะจ๊ะ - Cache Invalidation: ถ้าข้อมูลใน DB เปลี่ยนก่อนที่แคชจะหมดอายุ เราต้องหาวิธีลบแคชตัวนั้นทิ้งด้วยนะ ไม่งั้นผู้ใช้ก็จะเห็นข้อมูลเก่าอยู่ดี อาจจะยิงคำสั่ง
cache.delete(key)ตอนที่เราอัปเดตข้อมูลใน DB ก็ได้
สรุปส่งท้าย
เอาจริงๆ นะ Redis โคตรเจ๋งและมีประโยชน์มากๆ โดยเฉพาะเรื่อง Caching เนี่ย มันช่วยลดโหลดงานของ Database ได้เยอะเลย แถมยังทำให้แอปเราตอบสนองเร็วขึ้นมากๆ ด้วย เหมาะกับข้อมูลที่ไม่ค่อยเปลี่ยนบ่อยๆ หรือข้อมูลที่ต้องถูกดึงมาใช้ซ้ำๆ บ่อยๆ นะ
ลองเอาไปประยุกต์ใช้กับโปรเจกต์ของตัวเองดูนะ แล้วจะติดใจ! ไม่ต้องกลัว มันไม่ได้ยากอย่างที่คิดหรอก.