Tối Ưu Performance Gấp 10 Lần Với Redis Caching

5 min read

Một ngày đẹp trời, hệ thống của bạn bỗng dưng “nghẹt thở”. User liên tục than phiền vì loading xoay vòng vòng, trong khi CPU của Database Server thì nhảy vọt lên 99%. Bạn tăng cấu hình DB lên (Scale-up) nhưng hóa đơn cuối tháng của khách hàng tăng chóng mặt, còn performance thì vẫn trồi sụt thất thường.

Nếu kịch bản “ác mộng” này nghe quen quen, thì đã đến lúc bạn cần đưa Redis Caching vào cuộc chơi. Trong bài viết này, chúng ta sẽ cùng mổ xẻ cách Redis giải cứu database và cách triển khai nó một cách chuẩn chỉnh trong các dự án real-world tại nhà NCC.

1. Redis Caching Là Gì Và Tại Sao Hệ Thống Của Bạn Cần Nó?

Về cơ bản, đa số các ứng dụng truyền thống đều lưu trữ dữ liệu trong các Relational Database (MySQL, PostgreSQL…). Mỗi khi user gửi request, ứng dụng phải chạy các câu lệnh Query phức tạp xuống ổ đĩa (Disk) để lấy dữ liệu.

Redis (Remote Dictionary Server) xuất hiện như một vị cứu tinh. Đây là một hệ thống lưu trữ dữ liệu dưới dạng Key-Value lưu hoàn toàn trên RAM (In-memory).

Phép so sánh đơn giản: Việc đọc dữ liệu từ Database giống như bạn phải đi vào kho sách lục lọi từng kệ, còn đọc từ Redis giống như việc bạn mở ngay cuốn sổ tay đang đặt sẵn trên bàn làm việc. Tốc độ phản hồi của RAM tính bằng mili-giây hoặc micro-giây, nhanh hơn ổ đĩa gấp hàng trăm lần.

[User Request] ---> [Ứng dụng (Backend)] 
                          |
             (1) Kiểm tra Cache có dữ liệu không?
              +--------/--------+
             YES               NO
              |                 |
     [Trả về từ REDIS]   [Query từ DATABASE]
                         (Sau đó lưu lại vào Redis cho lần sau)

$$Hình 1 – Alt text: Sơ đồ luồng hoạt động cơ bản của cơ chế Caching trong ứng dụng$$

2. Các Chiến Lược Caching Phổ Biến Trong Real-World

Không phải cứ cài Redis vào rồi “vứt” bừa dữ liệu lên đó là xong. Tùy thuộc vào bài toán của dự án, chúng ta cần chọn chiến lược thích hợp.

2.1 Cache-Aside (Lazy Loading) – Chiến thần “quốc dân”

Đây là chiến lược được áp dụng nhiều nhất tại các dự án của NCC nhờ tính an toàn và dễ triển khai.

  • Luồng đi: Khi có request, ứng dụng tìm trong Redis trước. Nếu có (Cache Hit), trả về luôn. Nếu không có (Cache Miss), ứng dụng sẽ query từ DB, trả về cho user, đồng thời ghi đè dữ liệu đó vào Redis để phục vụ cho các request sau.
  • Ưu điểm: Hệ thống vẫn sống tốt nếu Redis đột ngột “sập” (vì ứng dụng sẽ fallback về DB).

2.2 Write-Through – Đồng bộ tức thì

  • Luồng đi: Dữ liệu được ghi vào Redis trước, sau đó Redis (hoặc ứng dụng) tự động ghi tiếp vào DB rồi mới báo thành công cho user.
  • Ưu điểm: Dữ liệu trong Cache không bao giờ bị cũ (stale data).

3. Hướng Dẫn Triển Khai Redis Cache-Aside Thực Chiến (Node.js/Express)

Hãy cùng bắt tay vào code một ví dụ thực tế: Caching danh sách sản phẩm (Products) thường xuyên được user truy cập nhưng ít khi thay đổi.

JavaScript

const express = require('express');
const redis = require('redis');
const { ProductModel } = require('./models'); // Giả định Mongoose/Sequelize model

const app = express();
const REDIS_PORT = process.env.REDIS_PORT || 6379;

// Khởi tạo Redis Client
const redisClient = redis.createClient({ url: `redis://localhost:${REDIS_PORT}` });
redisClient.connect().then(() => console.log('Connected to Redis successfully!'));

// Endpoint lấy danh sách sản phẩm áp dụng Cache-Aside
app.get('/products', async (req, res) => {
    const cacheKey = 'products:all';

    try {
        // 1. Kiểm tra dữ liệu trong Redis Cache
        const cachedProducts = await redisClient.get(cacheKey);

        if (cachedProducts) {
            console.log('--- CACHE HIT ---');
            return res.json({ source: 'cache', data: JSON.parse(cachedProducts) });
        }

        // 2. CACHE MISS - Query từ Database
        console.log('--- CACHE MISS ---');
        const products = await ProductModel.find({}); 

        // 3. Lưu dữ liệu vào Redis và set TTL (Time-To-Live) là 1 tiếng (3600 giây)
        await redisClient.setEx(cacheKey, 3600, JSON.stringify(products));

        return res.json({ source: 'database', data: products });
    } catch (error) {
        console.error(error);
        return res.status(500).json({ error: 'Server Error' });
    }
});

app.listen(3000, () => console.log('Server running on port 3000'));

4. Những “Hố Tử Thần” (Pitfalls) Cần Tránh Khi Dùng Redis Tại Dự Án Thực Tế

Trong quá trình làm dự án tại NCC, chúng mình đã rút ra được những bài học “xương máu” khi cấu hình Redis. Dưới đây là những lỗi bạn bắt buộc phải né:

Tên hiện tượngNguyên nhânHậu quảCách khắc phục
Quên Set TTL (Time-To-Live)Ghi dữ liệu vào Redis mà không cài thời gian hết hạn.RAM bị tràn (OOM – Out of Memory), Redis bị crash.Luôn luôn set TTL phù hợp cho mọi Key dựa trên tần suất thay đổi của data.
Cache Avalanche (Tuyết lở)Hàng loạt Key quan trọng cùng hết hạn (expire) vào một thời điểm.Toàn bộ request cùng lúc dội thẳng xuống DB, làm sập DB.Thêm một khoảng thời gian ngẫu nhiên (Random Salt) vào TTL của từng key (Ví dụ: 3600 + rand(1, 300) giây).
Cache Penetration (Xuyên thấu)User cố tình request các data không tồn tại cả trong Cache lẫn DB (Ví dụ: id = -1).Request liên tục đi xuyên qua Cache để “nện” vào DB.Sử dụng Bloom Filter hoặc vẫn cache lại các key rỗng với TTL cực ngắn (ví dụ 5 phút).

5. Kết Luận & Key Takeaways

Redis Caching không chỉ là một công cụ giúp tăng tốc ứng dụng, nó là “vũ khí tối thượng” để bảo vệ tính toàn vẹn và độ sẵn sàng (Availability) của hệ thống khi scale lớn.

3 Quy tắc nằm lòng khi dùng Redis:

  1. Dữ liệu nào nên cache? Data được đọc rất nhiều lần nhưng ít khi thay đổi (Ví dụ: Danh mục, cấu hình hệ thống, thông tin user).
  2. Không bao giờ tin tưởng hoàn toàn vào Cache: Hệ thống phải luôn có cơ chế hoạt động bình thường ngay cả khi Redis “ngỏm”.
  3. Quản lý bộ nhớ thông minh: Luôn đặt TTL và chọn chính sách xóa key (Eviction Policy) phù hợp như LRU (Least Recently Used).
Avatar photo

Leave a Reply

Your email address will not be published. Required fields are marked *