Triển khai Caching trong NestJS
1. Giới thiệu về Caching trong NestJS
Caching (bộ nhớ đệm) là một kỹ thuật giúp cải thiện hiệu suất ứng dụng bằng cách lưu trữ tạm thời kết quả của các truy vấn hoặc dữ liệu thường xuyên được sử dụng. Trong NestJS, caching có thể giúp giảm tải cho database, API và tăng tốc độ phản hồi của ứng dụng.
NestJS hỗ trợ caching thông qua @nestjs/cache-manager
, cho phép sử dụng nhiều loại store như Redis, Memcached, hoặc bộ nhớ trong (in-memory cache). Caching có thể giúp giảm độ trễ, giảm chi phí truy vấn cơ sở dữ liệu, và cải thiện trải nghiệm người dùng bằng cách cung cấp dữ liệu nhanh hơn.
Lợi ích của Caching
- Giảm thời gian phản hồi: Dữ liệu đã được cache có thể được truy xuất nhanh hơn so với việc thực hiện truy vấn từ database.
- Giảm tải cho database: Giảm số lượng truy vấn đến cơ sở dữ liệu, từ đó cải thiện hiệu suất tổng thể của hệ thống.
- Tăng khả năng mở rộng: Hệ thống có thể xử lý nhiều yêu cầu hơn mà không làm chậm tốc độ phản hồi.
- Cải thiện trải nghiệm người dùng: Người dùng sẽ có cảm giác ứng dụng chạy mượt mà hơn do thời gian tải dữ liệu nhanh hơn.
Hạn chế và Rủi ro của Caching
- Dữ liệu không được cập nhật ngay lập tức: Nếu dữ liệu được thay đổi nhưng chưa được cập nhật trong cache, ứng dụng có thể hiển thị thông tin cũ.
- Chiếm dụng bộ nhớ: Nếu không quản lý cache đúng cách, dữ liệu cũ có thể chiếm nhiều dung lượng bộ nhớ, gây ảnh hưởng đến hiệu suất hệ thống.
- Tăng độ phức tạp của hệ thống: Việc quản lý cache đòi hỏi các chiến lược hợp lý để tránh lỗi cache và đảm bảo dữ liệu luôn nhất quán.
2. Cài đặt Cache Module trong NestJS
Để sử dụng caching trong NestJS, bạn cần cài đặt gói hỗ trợ caching:
npm install @nestjs/cache-manager cache-manager
Sau đó, import CacheModule
vào module của bạn:
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import { AppController } from './app.controller';
import { AppService } from './app.service';
@Module({
imports: [
CacheModule.register({
isGlobal: true, // Cho phép sử dụng cache trên toàn ứng dụng
ttl: 10, // Thời gian lưu cache (tính bằng giây)
max: 100, // Số lượng items tối đa trong cache
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
3. Các Chiến Lược Caching Phổ Biến
3.1. Cache Aside (Lazy Loading)
- Dữ liệu chỉ được lưu vào cache khi có một yêu cầu thực tế.
- Nếu dữ liệu không có trong cache, hệ thống sẽ lấy từ database và lưu vào cache cho lần truy vấn tiếp theo.
3.2. Write-Through
- Mỗi khi dữ liệu được cập nhật trong database, nó cũng được cập nhật vào cache ngay lập tức.
- Giúp đảm bảo cache luôn có dữ liệu mới nhất.
3.3. Read-Through
- Khi dữ liệu được yêu cầu, hệ thống sẽ kiểm tra cache trước.
- Nếu dữ liệu không có trong cache, nó sẽ được lấy từ database và lưu vào cache trước khi trả về.
3.4. Cache Expiration và Eviction
- TTL (Time-to-Live): Xác định khoảng thời gian dữ liệu tồn tại trong cache trước khi bị xóa.
- Eviction Policies: Các thuật toán như Least Recently Used (LRU) hoặc Least Frequently Used (LFU) giúp loại bỏ dữ liệu ít sử dụng để nhường chỗ cho dữ liệu mới.
4. Áp dụng Cache vào Controller
4.1. Ví dụ
Giả sử bạn đang xây dựng một hệ thống quản lý sản phẩm. API /products sẽ lấy danh sách sản phẩm từ cơ sở dữ liệu và dùng prisma. Nếu không có cache, mỗi lần request sẽ truy vấn trực tiếp vào database, gây ra tải lớn khi có nhiều yêu cầu cùng lúc.
4.2. Không sử dụng Caching
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma.service';
@Injectable()
export class ProductService {
constructor(private prisma: PrismaService) {}
async getProducts() {
console.log('Fetching data from database...');
return this.prisma.product.findMany();
}
}
Mỗi lần gọi API, dữ liệu sẽ được lấy trực tiếp từ database, gây ra độ trễ không cần thiết.
4.3. Sử dụng Caching với CacheManager
Chúng ta sẽ lưu danh sách sản phẩm vào cache để giảm tải truy vấn database:
import { Injectable } from '@nestjs/common';
import { Cache } from 'cache-manager';
import { InjectCacheManager } from '@nestjs/cache-manager';
import { PrismaService } from '../prisma.service';
@Injectable()
export class ProductService {
constructor(
private prisma: PrismaService,
@InjectCacheManager() private cacheManager: Cache
) {}
async getProducts() {
const cacheKey = 'products';
const cachedData = await this.cacheManager.get(cacheKey);
if (cachedData) {
console.log('Returning cached data...');
return cachedData;
}
console.log('Fetching data from database...');
const products = await this.prisma.product.findMany();
await this.cacheManager.set(cacheKey, products, { ttl: 60 });
return products;
}
}
Với cách này, nếu có nhiều request liên tiếp, dữ liệu sẽ được lấy từ cache thay vì truy vấn database.
5. Xóa Cache khi Dữ liệu Thay đổi
Khi dữ liệu được cập nhật hoặc xóa, bạn nên loại bỏ cache để tránh hiển thị dữ liệu lỗi thời:
async updateProduct(productId: number, updateData: any) {
await this.prisma.product.update({ where: { id: productId }, data: updateData });
await this.cacheManager.del('products');
}
6. Sử dụng Redis làm Cache
Redis là gì?
Redis (Remote Dictionary Server) là một hệ thống lưu trữ dữ liệu dạng key-value hoạt động trên bộ nhớ (in-memory). Nó cung cấp hiệu suất cực cao cho việc đọc/ghi dữ liệu, làm cho Redis trở thành một lựa chọn phổ biến trong việc caching.
Tại sao nên dùng Redis để cache?
- Tốc độ nhanh: Vì Redis lưu trữ dữ liệu trong RAM nên tốc độ truy xuất nhanh hơn nhiều so với cơ sở dữ liệu truyền thống.
- Hỗ trợ TTL: Redis cho phép đặt thời gian sống cho mỗi key, giúp quản lý cache dễ dàng hơn.
- Hỗ trợ nhiều cấu trúc dữ liệu: Redis không chỉ lưu trữ key-value mà còn hỗ trợ danh sách, tập hợp, hash, và nhiều loại dữ liệu khác.
- Khả năng mở rộng: Redis hỗ trợ clustering giúp dễ dàng mở rộng khi hệ thống phát triển.
Cài đặt Redis và thư viện hỗ trợ:
npm install cache-manager-redis-store
Sau đó, cấu hình Redis trong CacheModule
:
import * as redisStore from 'cache-manager-redis-store';
CacheModule.register({
store: redisStore,
host: 'localhost',
port: 6379,
ttl: 600,
});
7. Tích hợp Cache vào Middleware
Bạn có thể tạo một middleware để kiểm tra cache trước khi xử lý request:
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { Cache } from 'cache-manager';
@Injectable()
export class CacheMiddleware implements NestMiddleware {
constructor(private cacheManager: Cache) {}
async use(req: Request, res: Response, next: NextFunction) {
const cacheKey = req.originalUrl;
const cachedResponse = await this.cacheManager.get(cacheKey);
if (cachedResponse) {
return res.json(cachedResponse);
}
res.sendResponse = res.json;
res.json = async (body) => {
await this.cacheManager.set(cacheKey, body, { ttl: 300 });
res.sendResponse(body);
};
next();
}
}
8. Kết luận
Caching là một phương pháp quan trọng giúp tối ưu hiệu suất của ứng dụng NestJS. Khi triển khai caching, bạn cần cân nhắc giữa hiệu suất và tính nhất quán của dữ liệu. Một chiến lược caching hợp lý có thể giúp giảm tải hệ thống, tăng tốc độ phản hồi và cải thiện trải nghiệm người dùng.
Cảm ơn bạn đã đọc bài viết này! Hẹn gặp lại ở các bài viết sau nhé :>
All rights reserved