1. Giới thiệu về Caching trong NestJS
Caching (bộ nhớ đệm) là một kỹ thuật quản lý dữ liệu 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ả từ các truy vấn hoặc dữ liệu thường xuyên được sử dụng. Trong môi trường phát triển ứng dụng với NestJS, caching đóng vai trò quan trọng trong việc giảm tải cho cơ sở dữ liệu, API và giúp nâng cao tốc độ phản hồi của ứng dụng.
NestJS hỗ trợ caching thông qua gói @nestjs/cache-manager
, cho phép sử dụng nhiều loại kho lưu trữ như Redis, Memcached và bộ nhớ nội bộ (in-memory cache). Việc áp dụng caching không chỉ giúp giảm độ trễ trong các yêu cầu, mà còn tiết kiệm chi phí truy vấn cơ sở dữ liệu và cải thiện trải nghiệm người dùng với tốc độ cung cấp dữ liệu nhanh chóng.
Lợi ích của Caching
- Giảm thời gian phản hồi: Dữ liệu đã được lưu trong cache có thể được truy xuất nhanh hơn so với việc thực hiện truy vấn trực tiếp từ cơ sở dữ liệu.
- Giảm tải cho cơ sở dữ liệu: Việc giảm thiểu số lượng truy vấn đến cơ sở dữ liệu 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 ảnh hưởng đến tốc độ phản hồi.
- Cải thiện trải nghiệm người dùng: Ứng dụng sẽ mang lại cảm giác mượt mà hơn khi dữ liệu được tải nhanh hơn.
Hạn chế và Rủi ro của Caching
- Dữ liệu không cập nhật ngay lập tức: Dữ liệu có thể không được làm mới kịp thời trong cache, dẫn đến việc ứng dụng hiển thị thông tin lỗi thời.
- Chiếm dụng bộ nhớ: Nếu không quản lý cache hợp lý, dữ liệu cũ có thể chiếm nhiều dung lượng bộ nhớ, ả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 cần các chiến lược phù hợp để đảm bảo dữ liệu luôn nhất quán và tránh lỗi phát sinh.
2. Cài đặt Cache Module trong NestJS
Để sử dụng caching trong ứng dụng NestJS, bạn cần thực hiện cài đặt gói hỗ trợ caching:
npm install @nestjs/cache-manager cache-manager
Sau đó, bạn cần import CacheModule
vào module của mình như sau:
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, // Cung cấp khả năng 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, // Giới hạn 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ó yêu cầu thực tế từ người sử dụng.
- Nếu dữ liệu không có trong cache, hệ thống sẽ lấy từ cơ sở dữ liệu và lưu vào cache cho lần truy vấn tiếp theo.
3.2. Write-Through
- Mỗi lần cập nhật dữ liệu trong cơ sở dữ liệu, nó cũng sẽ được cập nhật vào cache ngay lập tức.
- Chiến lược này giúp đảm bảo rằng 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 kiểm tra cache trước tiên.
- Nếu không tìm thấy dữ liệu trong cache, nó sẽ được lấy từ cơ sở dữ liệu và được lưu vào cache trước khi trả về kết quả.
3.4. Cache Expiration và Eviction
- TTL (Time-to-Live): Đây là khoảng thời gian mà 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 trong cache để nhường chỗ cho dữ liệu mới.
4. Cách Áp dụng Cache vào Controller
4.1. Ví dụ thực tế
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à sử dụng Prisma. Nếu không sử dụng cache, mỗi lần có yêu cầu sẽ diễn ra truy vấn trực tiếp đến database, gây ra lượng tải lớn khi hệ thống nhận 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, hệ thống sẽ phải lấy dữ liệu từ cơ sở dữ liệu, dẫn đến độ 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 cho 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 phương pháp này, nếu có nhiều yêu cầu liên tiếp, dữ liệu sẽ được lấy từ cache thay vì phải truy vấn từ cơ sở dữ liệu.
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 cần loại bỏ dữ liệu trong cache để tránh việc hiển thị thông tin 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 kiểu key-value hoạt động trên cơ sở bộ nhớ (in-memory). Điều này mang lại hiệu suất đọc/ghi cực cao, giúp Redis trở thành sự lựa chọn lý tưởng cho việc caching.
Tại sao nên dùng Redis để cache?
- Tốc độ nhanh: Redis lưu trữ dữ liệu trong RAM, giúp thời gian truy xuất nhanh hơn rất nhiều so với cơ sở dữ liệu truyền thống.
- Hỗ trợ TTL: Redis cho phép bạn thiết lập thời gian tồn tại cho mỗi key, giúp dễ dàng quản lý cache.
- 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ợ các kiểu dữ liệu khác như danh sách, tập hợp và nhiều loại như hash.
- Khả năng mở rộng: Redis hỗ trợ clustering, dễ dàng mở rộng khi số lượng yêu cầu tăng lên.
Cài đặt Redis và thư viện hỗ trợ:
npm install cache-manager-redis-store
Sau khi cài đặt, cấu hình Redis trong CacheModule
như sau:
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 hóa hiệu suất ứng dụng NestJS. Khi triển khai caching, bạn cần xem xét một cách 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ó khả năng 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 đáng kể.
Cảm ơn bạn đã theo dõi bài viết này! Hẹn gặp lại trong những bài viết sau nhé!