
Redis: Cache ตัวเก่ง เสริมพลังเว็บให้เร็วปรู๊ดปร๊าด!
September 25, 2025
Database
ทุกคนครับ! เคยเจอปัญหานี้ไหมครับ?
- เว็บไซต์หรือแอปของเราช่วงแรก ๆ ก็เร็วดี แต่พอผู้ใช้เยอะขึ้น ข้อมูลเยอะขึ้น อยู่ดี ๆ ก็เริ่ม โหลดช้าลง ซะอย่างนั้น
- บางทีแค่ต้องการแสดงผลข้อมูลเดิม ๆ ซ้ำ ๆ บนหน้าเว็บ แต่ฐานข้อมูลก็ต้อง ทำงานหนัก ดึงข้อมูลชุดเดิมมาให้ทุกครั้ง
- ผู้ใช้ก็บ่นว่า "เว็บอืดจังเลย" ...เป็นสัญญาณว่าถึงเวลาต้องหาตัวช่วยแล้ว😵!
วันนี้เราจะมาทำความรู้จักกับฮีโร่ที่จะมาช่วยแก้ปัญหานี้ นั่นก็คือ Redis ครับ! และจะเน้นไปที่บทบาทที่สำคัญที่สุดอย่างหนึ่ง นั่นก็คือการทำ Caching
Redis คืออะไร? (แบบเข้าใจง่าย)
Redis ย่อมาจาก Remote Dictionary Server มันคือ In-memory Data Structure Store ครับ (ฟังดูเท่ แต่ไม่ยากอย่างที่คิด!)
- In-memory: จุดเด่นคือมันเก็บข้อมูลไว้ใน RAM (หน่วยความจำหลัก) ของเครื่อง Server ทำให้มันอ่านและเขียนข้อมูลได้ เร็วโคตร ๆ เมื่อเทียบกับการเก็บใน Hard Disk (แบบฐานข้อมูลทั่วไป)
- Data Structure Store: ไม่ได้เป็นแค่ Key-Value Store ธรรมดา แต่เก็บข้อมูลได้หลากหลายรูปแบบ เช่น Strings, Hashes, Lists, Sets และ Sorted Sets ซึ่งแต่ละแบบก็มีประโยชน์ต่างกันไป
ด้วยความเร็วระดับฟ้าผ่า⚡ นี้แหละครับ ที่ทำให้ Redis เป็นตัวเลือกอันดับต้น ๆ สำหรับการทำ Caching
ทำไมต้อง Cache ด้วย Redis? (ปัญหาที่ Redis แก้ได้)
ลองนึกภาพว่าฐานข้อมูลของเราเป็นเหมือน "ห้องสมุดขนาดใหญ่" ที่มีหนังสือ (ข้อมูล) เยอะแยะมากมาย เวลาใครจะหาหนังสือ ก็ต้องเดินเข้าไปหา หยิบออกมา แล้วจดข้อมูลที่ต้องการ
ถ้ามีคน 100 คน อยากได้หนังสือเล่มเดียวกัน ฐานข้อมูลก็ต้องทำกระบวนการนี้ซ้ำ ๆ 100 ครั้ง ซึ่งกินเวลาและทรัพยากรมาก
Redis ในบทบาท Caching ก็เหมือนกับ "สมุดจดโน้ตส่วนตัวของบรรณารักษ์"
- บรรณารักษ์ (แอปของเรา) ไปเอาหนังสือจากห้องสมุดมาครั้งแรก
- จดข้อมูลสำคัญ ๆ ลงในสมุดโน้ต (Redis Cache)
- ครั้งต่อไป ถ้ามีใครถามหาหนังสือเล่มเดิม บรรณารักษ์ก็แค่เปิดสมุดโน้ตดู แป๊บเดียวก็ได้คำตอบแล้ว! ไม่ต้องเดินเข้าห้องสมุดให้เสียเวลา
นี่คือหลักการง่าย ๆ ของ Caching เก็บข้อมูลที่ถูกเรียกใช้บ่อย ๆ หรือใช้เวลานานในการประมวลผลไว้ในที่ ที่เข้าถึงได้เร็วกว่า
Use Case: Caching API Responses (ตัวอย่างจริง)
สมมติว่าเรามี API สำหรับดึงรายการสินค้ายอดนิยม ที่ต้องไป Query ข้อมูลซับซ้อนจากฐานข้อมูลทุกครั้ง ซึ่งใช้เวลาประมาณ 500ms (ครึ่งวินาที)
ก่อนใช้ Redis: ผู้ใช้ 100 คนกดหน้านี้พร้อมกัน ฐานข้อมูลต้องทำงานหนัก 100 รอบ
Old API Route (No Cache)
// ตัวอย่าง: Node.js + Express
const express = require('express');
const app = express();
const db = require('./db'); // สมมติว่านี่คือการเชื่อมต่อฐานข้อมูล
app.get('/api/popular-products', async (req, res) => {
console.log('Fetching from Database...');
const products = await db.getComplexPopularProductsQuery(); // ใช้เวลา 500ms
res.json(products);
});
app.listen(3000, () => console.log('Server running on port 3000'));
หลังจากใช้ Redis Caching: ผู้ใช้ 100 คนกดหน้านี้พร้อมกัน ฐานข้อมูลทำงานแค่ ครั้งแรก เท่านั้น! อีก 99 ครั้งที่เหลือดึงจาก Redis แค่ไม่กี่มิลลิวินาที
New API Route (With Redis Cache)
// ตัวอย่าง: Node.js + Express + Redis
const express = require('express');
const app = express();
const redis = require('redis');
const db = require('./db');
const client = redis.createClient(); // สร้าง Redis Client
client.connect(); // เชื่อมต่อ Redis
// API ดึงรายการสินค้ายอดนิยม
app.get('/api/popular-products', async (req, res) => {
const cacheKey = 'popular_products'; // Key สำหรับเก็บข้อมูลใน Redis
try {
// 1. ลองดึงข้อมูลจาก Redis ก่อน
const cachedProducts = await client.get(cacheKey);
if (cachedProducts) {
console.log('Serving from Redis Cache!');
return res.json(JSON.parse(cachedProducts)); // ส่งข้อมูลจาก Cache
}
// 2. ถ้าไม่มีใน Redis (Cache Miss) ค่อยไปดึงจากฐานข้อมูล
console.log('Fetching from Database...');
const products = await db.getComplexPopularProductsQuery(); // ใช้เวลา 500ms
// 3. เก็บข้อมูลที่ได้จากฐานข้อมูลลง Redis พร้อมกำหนดเวลาหมดอายุ (TTL)
// EX: กำหนดให้ Cache หมดอายุใน 60 วินาที
await client.set(cacheKey, JSON.stringify(products), {
EX: 60, // Expire after 60 seconds
});
res.json(products);
} catch (error) {
console.error('Error:', error);
res.status(500).send('Internal Server Error');
}
});
app.listen(3000, () => console.log('Server running on port 3000'));
สิ่งสำคัญ: Cache Invalidation (การทำให้ Cache เป็นข้อมูลล่าสุด)
การมี Cache นั้นดี แต่ถ้าข้อมูลในฐานข้อมูลเปลี่ยนไป แต่ Cache ยังคงแสดงข้อมูลเก่าอยู่ (เรียกว่า Stale Data) ก็จะเป็นปัญหาใหญ่ได้! เราจึงต้องมีกลยุทธ์ในการ "บอก" Redis ว่าข้อมูลนี้มันไม่สดใหม่แล้วนะ ลบออกไปซะ!
1. Time-to-Live (TTL)
นี่คือวิธีที่ง่ายที่สุดที่เราเห็นในตัวอย่างด้านบน (EX: 60) คือการกำหนดเวลาหมดอายุให้ข้อมูลใน Cache เมื่อครบกำหนด Redis จะลบข้อมูลนั้นทิ้งไปเอง ทำให้ครั้งต่อไประบบจะไปดึงข้อมูลใหม่จากฐานข้อมูลมาเก็บไว้แทน
เหมาะสำหรับ: ข้อมูลที่ไม่จำเป็นต้องเป็น Real-time 100% และมีการเปลี่ยนแปลงไม่บ่อยนัก
2. Manual Invalidation (Delete-on-Update)
วิธีนี้คือการที่เราจะ ลบ Cache ออกทันที เมื่อข้อมูลที่เกี่ยวข้องมีการเปลี่ยนแปลงในฐานข้อมูล
Use Case: สมมติว่ามี API สำหรับอัปเดตข้อมูลสินค้า เมื่อมีผู้ดูแลระบบแก้ไขราคาสินค้า เราต้องการให้ข้อมูลใหม่ปรากฏทันที
API Route (Update Product & Invalidate Cache)
// ... (code for Redis client and DB connection) ...
app.put('/api/products/:id', async (req, res) => {
const productId = req.params.id;
const { newPrice } = req.body;
const cacheKey = 'popular_products'; // Cache ที่เราเคยสร้างไว้
try {
// 1. อัปเดตข้อมูลในฐานข้อมูลก่อน
await db.updateProductPrice(productId, newPrice);
console.log(`Product ${productId} updated in DB.`);
// 2. ลบ Cache ที่เกี่ยวข้องออก
await client.del(cacheKey); // ลบ Cache ของ popular products
console.log('Cache for popular products invalidated!');
res.status(200).send('Product updated and cache invalidated successfully.');
} catch (error) {
console.error('Error:', error);
res.status(500).send('Internal Server Error');
}
});
เมื่อใดที่มีการอัปเดตข้อมูลสินค้า สินค้าใน Cache popular_products ก็จะถูกลบออกไป ทำให้ Request ถัดไป ระบบจะไปดึงข้อมูลใหม่จากฐานข้อมูล และสร้าง Cache ใหม่ขึ้นมาทันที
Redis ยังทำอะไรได้อีก? (ตัวอย่าง Queue เบื้องต้น)
นอกจาก Caching แล้ว Redis ยังเก่งเรื่องอื่น ๆ อีกเยอะเลยครับ หนึ่งในนั้นคือการทำ Message Queue หรือระบบคิวงานเบื้องหลัง
ปัญหา: บางงานใช้เวลานาน เช่น ประมวลผลรูปภาพ, ส่งอีเมลหาผู้ใช้หลายพันคน, หรือสร้างรายงาน ถ้าเราให้ผู้ใช้รอจนกว่างานเหล่านี้จะเสร็จ ก็คงไม่ดีแน่!
Redis Queue เข้ามาช่วย: เราสามารถใช้ Redis ในรูปแบบของ List (โครงสร้างข้อมูลของ Redis) เพื่อเป็นคิวงานได้:
- Producer: เมื่อมีงานที่ต้องทำเบื้องหลัง (เช่น ผู้ใช้กดปุ่ม "สร้างรายงาน") แอปของเราจะใช้คำสั่ง LPUSH เพื่อ "โยน" ข้อมูลงานนั้น ๆ เข้าไปใน Redis List (คิว)
- Consumer/Worker: มีโปรแกรมเล็ก ๆ (Worker) อีกตัว ที่คอย BRPOP (Blocking Right Pop) ดึงงานออกจากคิวไปประมวลผลเบื้องหลัง โดยที่ผู้ใช้ไม่ต้องรอ
Redis Queue (Concept)
// Producer (ใน API ของเรา)
app.post('/api/report', async (req, res) => {
const userId = req.body.userId;
// โยนงานสร้างรายงานเข้าคิว
await client.LPUSH('report_queue', JSON.stringify({ type: 'generate_report', userId: userId }));
res.status(202).send('Generating report in background...');
});
// Consumer / Worker (อีกโปรแกรมหนึ่ง)
async function processQueue() {
while (true) {
// ดึงงานออกจากคิวแบบ Blocking (รอจนกว่าจะมีงาน)
const [listName, jobData] = await client.BRPOP('report_queue', 0); // 0 คือรอตลอดไป
const job = JSON.parse(jobData);
console.log(`Processing job: ${job.type} for user ${job.userId}`);
// ... ทำงานที่ใช้เวลานานตรงนี้ ...
console.log(`Job for user ${job.userId} completed.`);
}
}
processQueue();
วิธีนี้ช่วยให้ระบบของเราตอบสนองผู้ใช้ได้เร็วขึ้น เพราะงานหนัก ๆ ถูกส่งไปทำเบื้องหลัง ทำให้แอปของเราลื่นไหลและมีประสิทธิภาพมากขึ้นครับ!
สรุป
Redis เป็นเครื่องมือที่ยอดเยี่ยมและหลากหลายความสามารถมาก ๆ ครับ โดยเฉพาะในบทบาทของ Caching ที่ช่วยลดภาระของฐานข้อมูลและเพิ่มความเร็วในการตอบสนองของแอปพลิเคชันได้อย่างมหาศาล
การทำความเข้าใจเรื่อง TTL และ Cache Invalidation เป็นสิ่งสำคัญที่จะช่วยให้ Cache ของเรามีประโยชน์สูงสุดและไม่สร้างปัญหาเรื่องข้อมูลเก่าให้กับผู้ใช้ครับ และการใช้ Redis เป็น Message Queue ก็เปิดโลกให้เราสร้างระบบที่ยืดหยุ่นและรองรับงานหนักได้ดียิ่งขึ้นไปอีก
หวังว่าบทความนี้จะทำให้ทุกคนมองเห็นพลังของ Redis และอยากลองนำไปใช้ในโปรเจกต์ของตัวเองกันดูนะครับ! รับรองว่าไม่ผิดหวังแน่นอน!
Related Blogs