Cache Penetration: เมื่อของไม่มีอยู่จริง กลายเป็นอาวุธเจาะทะลุเกราะ Redis ถล่ม Database
⏱️ ใช้เวลาอ่านประมาณ: 4 นาที 25 วินาที
คุณตั้งค่า Redis ไว้เป็นเกราะหน้าบ้านอย่างแน่นหนา ข้อมูลสินค้ายอดฮิตถูกแคชไว้ครบถ้วน Hit Rate สูงถึง 95%
คืนหนึ่ง มีสคริปต์ลึกลับยิงขอข้อมูลสินค้าด้วย ID แปลกๆ เข้ามาแบบรัวๆ: /products/-9999, /products/abc_fake_id, /products/999999999 นับแสนคำขอต่อวินาที
Redis ยืนงงเพราะค้นหา Key เหล่านี้ไม่เจอ... แล้วสิ่งที่เกิดขึ้นคือ คำขอทั้งหมดพุ่งทะลุผ่าน Redis วิ่งตรงลงไปกระแทก Database หลัก 100% เต็มๆ! ฐานข้อมูลเริ่มค้าง CPU พุ่งแตะ 100% แล้วทั้งระบบก็น็อกสนิท เกราะ Redis ที่ลงทุนสร้างไว้กลายเป็นไร้ความหมายในพริบตา
🕳️ กระสุนทะลวงเกราะ (Cache Penetration)
ในวงการแคช มีบั๊ก 2 ตัวที่ชื่อคล้ายกันแต่คนมักจำสลับ:
- Cache Breakdown (หัวใจวายเฉพาะจุด): ข้อมูลนั้น มีจริง แต่ Key ดังดันหมดอายุลงพร้อมกันในเสี้ยววินาที คนเลยกรูไปขุด DB (เหมือนตอนที่ 14 เรื่อง Thundering Herd)
- Cache Penetration (แทงทะลวงเกราะ): ข้อมูลนั้น "ไม่มีอยู่จริงทั้งใน Cache และไม่มีใน Database เลยตั้งแต่แรก!"
เพราะโค้ดแบบ Cache-Aside ส่วนใหญ่เขียนไว้แบบซื่อๆ:
if (data == null) {
val dbData = database.findById(id) // ค้นหาใน DB
if (dbData != null) {
redis.set(id, dbData)
}
return dbData // ถ้าหาไม่เจอ ก็คืน null โดยไม่แคชอะไรเลย!
}
"เมื่อ ID ไม่มีอยู่จริงใน DB โค้ดจะไม่บันทึกอะไรลง Redis เลย ทำให้ทุกครั้งที่ยิงขอ ID ขยะนี้เข้ามา มันจะวิ่งทะลุลงไปรบกวน Disk ของ Database ทุกรอบโดยไม่มีเกราะดักไว้แม้แต่ชั้นเดียว!"
🛡️ 2 คาถาอุดรอยรั่ว: แคชค่าว่าง & Bloom Filter
1. แคชความว่างเปล่าไว้สั้นๆ (Cache Null Values):
ถ้าลงไปถาม Database แล้วพบว่าไม่มีของ ให้บันทึกค่าว่าง (เช่น "" หรือ "NULL") ลงใน Redis ไว้ด้วย พร้อมตั้งเวลาหมดอายุสั้นๆ สัก 1-2 นาที:
// บันทึกบอกไว้เลยว่า ID นี้ไม่มีของนะ หมดอายุใน 60 วิ
redis.set(id, "EMPTY_MARKER", ttl = 60)
}
เมื่อ Request ถัดไปยิงขอ ID ปลอมนี้ซ้ำมาอีก Redis จะตอบดักไว้ทันทีว่าไม่มีของ โดยไม่ต้องให้ Database เสียแรงค้นหา
2. ตะแกรงร่อนความจริงระดับเทพ: Bloom Filter
ถ้าผู้โจมตีสุ่ม ID มั่วไม่ซ้ำกันเลย การแคชค่าว่างจะทำให้ Redis แรมบวมได้ ทางออกระดับวิศวกรรมคือใช้ Bloom Filter (เช่น โมดูล RedisBloom)
Bloom Filter คือโครงสร้างข้อมูลแบบประหยัดบิต ที่ใช้พื้นที่เพียงไม่กี่เมกะไบต์ในการจดจำ ID สินค้าหลายสิบล้านชิ้น โดยมีคุณสมบัติทางคณิตศาสตร์สุดอัศจรรย์:
- ถ้า Bloom Filter บอกว่า "ไม่มี" ➔ มั่นใจได้ 100% ว่าไม่มีแน่นอน! ตัดคำขอทิ้งที่หน้าประตูได้ทันที ไม่ต้องให้ DB ขยับ
- ถ้าบอกว่า "อาจจะมี" ➔ ค่อยปล่อยให้วิ่งไปหาใน Cache และ Database ต่อไป
💡 5-Min Takeaway
"ระบบที่แข็งแกร่งต้องรับมือกับข้อมูลที่ 'ไม่มีอยู่' ได้ดีพอๆ กับข้อมูลที่มีอยู่" อย่าปล่อยให้การค้นหาไม่เจอ (Missed Query) กลายเป็นช่องโหว่ที่ผู้ไม่หวังดีใช้ยิงถล่มฐานข้อมูลทางอ้อม การบันทึก Null Object ลงแคชแบบมี TTL หรือวางแนวป้องกันด้วย Bloom Filter ตั้งแต่ต้นทาง คือเกราะป้องกันชั้นยอดที่จะทำให้ Database ของคุณนอนหลับได้อย่างปลอดภัย