Caching (Cache-aside)
pattern cache-aside (หรือที่รู้จักในชื่อ lazy loading) ให้แอปพลิเคชันของคุณควบคุมว่าอะไรจะเข้าไปใน cache และเมื่อไหร่ Redis อยู่หน้าฐานข้อมูลของคุณ: ถ้าข้อมูลอยู่ที่นั่น ก็ข้ามฐานข้อมูลไปได้เลย ถ้าไม่มี ก็ดึงจาก DB แล้วนำมาเก็บใน cache สำหรับครั้งถัดไป
วิธีการทำงานของ Cache-aside
หัวข้อที่มีชื่อว่า “วิธีการทำงานของ Cache-aside”flowchart TD
A["App needs cache:user:42"] --> B["GET cache:user:42"]
B --> C{"Cache hit?"}
C -->|"Yes"| D["Return cached value"]
C -->|"No (nil)"| E["Query DB"]
E --> F["SET cache:user:42 result EX 300"]
F --> G["Return value"] แอปพลิเคชันเป็นผู้ควบคุมทั้งการอ่านและการเขียนลงใน cache ไม่มีการโหลดข้อมูลล่วงหน้า รายการถูกสร้างขึ้นตามความต้องการและหมดอายุโดยอัตโนมัติผ่าน TTL
การอ่านและเขียน Cache Entry
หัวข้อที่มีชื่อว่า “การอ่านและเขียน Cache Entry”127.0.0.1:6379> GET cache:user:42(nil)127.0.0.1:6379> SET cache:user:42 "{\"name\":\"Ada\",\"email\":\"[email protected]\"}" EX 300OK127.0.0.1:6379> TTL cache:user:42(integer) 299127.0.0.1:6379> GET cache:user:42"{\"name\":\"Ada\",\"email\":\"[email protected]\"}"EX 300 กำหนด TTL เป็น 300 วินาที (5 นาที) หลังจากนั้น key จะหายไปและคำขอถัดไปจะเป็น cache miss ซึ่งจะทำให้อ่านข้อมูลใหม่จาก DB
GET cache:user:42
SET cache:user:42 "{\"name\":\"Ada\",\"email\":\"[email protected]\"}" EX 300
TTL cache:user:42
GET cache:user:42การยกเลิก Cache
หัวข้อที่มีชื่อว่า “การยกเลิก Cache”เมื่อข้อมูลต้นฉบับเปลี่ยนแปลง เช่น ผู้ใช้อัปเดตโปรไฟล์ของตน รายการใน cache จะกลายเป็นข้อมูลเก่า วิธีที่ง่ายและเชื่อถือได้ที่สุดคือการลบ key ทันทีเพื่อให้การอ่านครั้งถัดไปดึงข้อมูลใหม่จาก DB
127.0.0.1:6379> SET cache:user:42 "{\"name\":\"Ada updated\"}" EX 300OK127.0.0.1:6379> DEL cache:user:42(integer) 1127.0.0.1:6379> GET cache:user:42(nil)SET cache:user:42 "{\"name\":\"Ada updated\"}" EX 300
DEL cache:user:42
GET cache:user:42Cache Stampede
หัวข้อที่มีชื่อว่า “Cache Stampede”cache stampede เกิดขึ้นเมื่อ key ยอดนิยมหมดอายุและคำขอพร้อมกันหลายสิบ (หรือหลายพัน) รายการเกิด cache miss ในเวลาเดียวกัน ทั้งหมดแข่งกันไปยังฐานข้อมูลพร้อมกัน ทำให้เกิดการพุ่งสูงของภาระบน DB อย่างกะทันหัน — นั่นคือสิ่งที่ caching ตั้งใจป้องกันอยู่
มีสองวิธีที่ใช้บรรเทาปัญหานี้ทั่วไป: ใช้ SET ... NX เพื่อให้เฉพาะผู้เขียนรายแรกเท่านั้นที่นำข้อมูลเข้า key และให้รายอื่นรอ หรือใช้ probabilistic early expiration — เทคนิคที่รีเฟรช key แบบสุ่มก่อนที่จะหมดอายุ โดยอ้างอิงจาก TTL ที่เหลืออยู่และต้นทุนการคำนวณที่ประมาณไว้ ทั้งสองกลยุทธ์ช่วยลดผลกระทบ thundering-herd โดยไม่ต้องใช้บริการ lock ภายนอก
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| Cache-aside | แอปควบคุมเองว่าอะไรเข้า cache; DB ไม่ถูกโหลดถ้าไม่มีใครขอข้อมูลนั้น | cache miss ครั้งแรกช้ากว่าปกติ (query DB + write cache) |
| Write-through | cache กับ DB sync กันเสมอ ไม่มี stale read | เขียนทุกครั้งช้าลง เพราะต้องเขียนทั้งสองที่ก่อน return |
| Probabilistic early expiration | ลด thundering herd ตอน key ยอดนิยมหมดอายุ | ต้องคำนวณ probability ต่อ request เพิ่ม logic ในแอป |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ไม่ตั้ง TTL ให้ cache key — ถ้าลืมใส่
EXข้อมูลจะค้างอยู่ตลอดไปแม้ source-of-truth เปลี่ยนไปแล้ว กลายเป็น stale cache ถาวร - ปล่อยให้เกิด cache stampede โดยไม่มีการป้องกัน — เมื่อ key ยอดนิยมหมดอายุพร้อมกัน คำขอนับพันจะพุ่งไป DB พร้อมกัน ควรใช้
SET ... NXหรือ probabilistic early expiration - ลืม invalidate cache ตอนข้อมูลต้นฉบับเปลี่ยน — ถ้าอัปเดต DB แต่ไม่
DELcache key เดิม ผู้ใช้จะเห็นข้อมูลเก่าไปจนกว่า TTL จะหมด
💡 ตัวอย่างจากของจริง
Twitter — ใช้ cache-aside pattern แบบเดียวกับบทนี้เป็นแกนของ timeline cache โดยเก็บ tweet ล่าสุดไว้ใน Redis แล้ว fallback ไป storage ชั้นในเมื่อ miss
Stack Overflow — วาง Redis cache-aside ไว้หน้า SQL Server เพื่อลด query ซ้ำสำหรับหน้าที่มีคนอ่านพร้อมกันจำนวนมาก