Giới thiệu

Trong các dự án backend quy mô trung bình đến lớn, tốc độ phản hồi API thường bị ảnh hưởng bởi hai yếu tố chính: thời gian truy xuất dữ liệu từ database và tải đồng thời của các yêu cầu. Hai kỹ thuật phổ biến giúp giảm thiểu độ trễ là caching dữ liệu tạm thời bằng Redis và connection pooling cho các kết nối database. Bài viết sẽ hướng dẫn chi tiết cách tích hợp cả hai vào ứng dụng Node.js sử dụng Express.

Redis Caching trong middleware

Triển khai middleware cache

Redis là kho lưu trữ key‑value trong bộ nhớ, cho phép trả về dữ liệu trong vòng vài miligiây. Chúng ta sẽ tạo một middleware để kiểm tra cache trước khi thực hiện truy vấn database.

const redis = require('redis');
const redisClient = redis.createClient({ url: process.env.REDIS_URL });
await redisClient.connect();

function cache(keyGenerator, ttl = 60) {
  return async (req, res, next) => {
    const key = typeof keyGenerator === 'function' ? keyGenerator(req) : keyGenerator;
    const cached = await redisClient.get(key);
    if (cached) {
      res.json(JSON.parse(cached));
    } else {
      // Ghi đè hàm res.json để lưu vào cache sau khi dữ liệu được tạo
      const originalJson = res.json.bind(res);
      res.json = async (data) => {
        await redisClient.setEx(key, ttl, JSON.stringify(data));
        originalJson(data);
      };
      next();
    }
  };
}

Middleware trên nhận một keyGenerator (có thể là chuỗi tĩnh hoặc hàm tạo key dựa trên req) và thời gian tồn tại ttl tính bằng giây. Khi dữ liệu đã có trong Redis, middleware trả về ngay mà không gọi tới route handler.

Connection Pooling cho PostgreSQL

Cấu hình pg‑pool

Thay vì tạo một kết nối mới cho mỗi truy vấn, pg cung cấp lớp Pool quản lý một nhóm kết nối tái sử dụng. Điều này giảm chi phí thiết lập kết nối và tránh quá tải database.

const { Pool } = require('pg');
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,                     // Số kết nối tối đa trong pool
  idleTimeoutMillis: 30000,   // Đóng kết nối khi không dùng quá 30s
  connectionTimeoutMillis: 2000, // Thời gian chờ khi lấy kết nối
});

module.exports = {
  query: (text, params) => pool.query(text, params),
};

Với cấu hình trên, ứng dụng sẽ giữ tối đa 20 kết nối mở tới PostgreSQL. Khi một truy vấn hoàn thành, kết nối sẽ được trả lại pool để các yêu cầu tiếp theo sử dụng lại.

Kết hợp Cache và Pool trong route thực tế

Dưới đây là một ví dụ route GET /users/:id sử dụng cả middleware cache và module truy vấn database đã cấu hình pool.

const express = require('express');
const router = express.Router();
const db = require('../db'); // module export ở trên
const cache = require('../middleware/cache');

router.get('/users/:id', cache((req) => `user:${req.params.id}`, 120), async (req, res) => {
  const { rows } = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id]);
  if (rows.length) {
    res.json(rows[0]);
  } else {
    res.status(404).json({ error: 'User not found' });
  }
});

module.exports = router;

Quy trình thực thi sẽ như sau:

  • Middleware kiểm tra Redis với key user:123 (giả sử id = 123).
  • Nếu có dữ liệu trong cache, trả về ngay, không thực hiện truy vấn DB.
  • Nếu không, route handler thực hiện truy vấn qua pg‑pool, nhận kết quả, trả về client và đồng thời lưu vào Redis để các lần gọi tiếp theo nhanh hơn.

Kết luận

Việc kết hợp Redis cachingconnection pooling giúp giảm thời gian phản hồi API xuống mức sub‑100ms cho các truy vấn thường xuyên, đồng thời giảm tải cho database. Đối với các hệ thống cần xử lý hàng nghìn yêu cầu đồng thời, đây là những kỹ thuật không thể thiếu.

Để nắm vững toàn bộ quy trình xây dựng backend chuyên nghiệp, bạn có thể Tham khảo khóa học "Lập trình Back-End với NodeJS Express" tại đây.