Tăng tốc độ phản hồi API bằng Cache (Bộ nhớ đệm)

Trong các hệ thống thực tế, truy vấn dữ liệu từ Cơ sở dữ liệu (đặc biệt là các truy vấn phức tạp hoặc bảng có hàng triệu dòng) luôn tốn nhiều thời gian và CPU. Cache (Bộ nhớ đệm) lưu trữ các kết quả truy vấn thường dùng vào bộ nhớ RAM (truy xuất nhanh hơn hàng nghìn lần so với ổ đĩa) giúp API phản hồi tức thì. Tài liệu này hướng dẫn cách tích hợp cơ chế Cache vào NestJS và cách phối hợp với Claude Code để triển khai tối ưu.

1. Cơ chế hoạt động của Cache

Khi Client yêu cầu dữ liệu, Server sẽ kiểm tra bộ nhớ đệm trước:
  • Cache Hit (Trúng Cache): Nếu dữ liệu có sẵn trong Cache, Server trả về ngay lập tức cho Client. (Không cần chạy câu lệnh SQL xuống DB).
  • Cache Miss (Trượt Cache): Nếu chưa có, Server truy cập DB lấy dữ liệu, lưu dữ liệu đó vào Cache (kèm thời gian hết hạn - TTL) rồi trả về cho Client.

2. Thiết lập Cache trong NestJS

NestJS cung cấp gói quản lý cache tích hợp dễ sử dụng.

Cài đặt thư viện:

npm install @nestjs/cache-manager cache-manager

Đăng ký CacheModule trong app.module.ts:

src/app.module.ts
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';

@Module({
  imports: [
    CacheModule.register({
      isGlobal: true, // Cho phép dùng Cache ở tất cả các module khác
      ttl: 60, // Thời gian lưu trữ mặc định là 60 giây (Time to live)
      max: 100, // Số lượng bản ghi tối đa lưu trên RAM
    }),
  ],
})
export class AppModule {}

3. Các cách sử dụng Cache

Cách 1: Tự động Cache toàn bộ Controller (Sử dụng Interceptor)

Đây là cách đơn giản nhất để cache toàn bộ kết quả của các endpoint GET trong Controller.
src/products/products.controller.ts
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager';

@Controller('products')
@UseInterceptors(CacheInterceptor) // Áp dụng tự động cache cho toàn bộ các hàm GET
export class ProductsController {
  
  @Get()
  @CacheKey('all_products') // Tự đặt tên Key lưu trong bộ nhớ
  @CacheTTL(120) // Đặt thời gian hết hạn riêng là 120 giây
  findAll() {
    return this.productsService.findAll();
  }
}

Cách 2: Tự viết mã điều khiển Cache trong Service (Programmatic Cache)

Dùng khi bạn muốn kiểm soát nghiệp vụ chi tiết hơn (như tự động xóa cache khi thêm sản phẩm mới).
src/products/products.service.ts
import { Injectable, Inject } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Cache } from 'cache-manager';

@Injectable()
export class ProductsService {
  constructor(
    @Inject(CACHE_MANAGER) private cacheManager: Cache, // Inject bộ quản lý cache
  ) {}

  async getProductDetails(id: string) {
    const cacheKey = `product_${id}`;

    // 1. Đọc dữ liệu từ Cache
    const cachedData = await this.cacheManager.get(cacheKey);
    if (cachedData) {
      return cachedData; // Trả về luôn nếu có
    }

    // 2. Truy vấn DB nếu trượt Cache
    const product = await this.db.findOne(id);
    
    // 3. Lưu vào Cache để dùng cho lần sau (lưu trong 5 phút)
    await this.cacheManager.set(cacheKey, product, 300);

    return product;
  }

  async createProduct(data: any) {
    // Xóa Cache danh sách sản phẩm khi có sản phẩm mới được tạo
    await this.cacheManager.del('all_products');
    return await this.db.save(data);
  }
}

4. Tích hợp Redis cho môi trường Production

Mặc định, cache-manager lưu dữ liệu ngay trên RAM của máy chủ chạy code (In-Memory). Nếu server bị khởi động lại, cache sẽ mất sạch. Hơn nữa, nếu chạy nhiều server song song, dữ liệu cache sẽ không đồng nhất. Trong thực tế, người ta sử dụng Redis làm máy chủ lưu trữ Cache tập trung:
  • Cài đặt thư viện Redis driver: npm install cache-manager-redis-yet
  • Cấu hình lại CacheModule để trỏ tới địa chỉ của Redis Server.

5. Hướng dẫn viết Prompt cho Claude Code thiết kế Cache

Hãy yêu cầu Claude Code triển khai cache ở các API truy vấn nhiều hoặc tốn thời gian.

Prompt mẫu tích hợp Cache:

Tôi muốn tối ưu hiệu năng cho API lấy danh sách bài viết 'GET /posts' bằng Cache trong NestJS:
1. Hãy đăng ký và cấu hình 'CacheModule' toàn cục trong 'app.module.ts' với thời gian hết hạn mặc định là 5 phút.
2. Áp dụng 'CacheInterceptor' cho hàm 'findAll' trong 'PostsController'. Đặt tên key là 'cached_posts' và TTL là 300 giây.
3. Trong 'PostsService', mỗi khi có bài viết mới được tạo ('create') hoặc cập nhật ('update'), hãy tự động xóa key 'cached_posts' khỏi bộ nhớ đệm để người dùng có thể nhìn thấy dữ liệu mới nhất ngay lập tức.