129 lines
3.4 KiB
Markdown
129 lines
3.4 KiB
Markdown
---
|
|
title: Use Caching Strategically
|
|
impact: HIGH
|
|
impactDescription: Dramatically reduces database load and response times
|
|
tags: performance, caching, redis, optimization
|
|
---
|
|
|
|
## Use Caching Strategically
|
|
|
|
Implement caching for expensive operations, frequently accessed data, and external API calls. Use NestJS CacheModule with appropriate TTLs and cache invalidation strategies. Don't cache everything - focus on high-impact areas.
|
|
|
|
**Incorrect (no caching or caching everything):**
|
|
|
|
```typescript
|
|
// No caching for expensive, repeated queries
|
|
@Injectable()
|
|
export class ProductsService {
|
|
async getPopular(): Promise<Product[]> {
|
|
// Runs complex aggregation query EVERY request
|
|
return this.productsRepo
|
|
.createQueryBuilder('p')
|
|
.leftJoin('p.orders', 'o')
|
|
.select('p.*, COUNT(o.id) as orderCount')
|
|
.groupBy('p.id')
|
|
.orderBy('orderCount', 'DESC')
|
|
.limit(20)
|
|
.getMany();
|
|
}
|
|
}
|
|
|
|
// Cache everything without thought
|
|
@Injectable()
|
|
export class UsersService {
|
|
@CacheKey('users')
|
|
@CacheTTL(3600)
|
|
@UseInterceptors(CacheInterceptor)
|
|
async findAll(): Promise<User[]> {
|
|
// Caching user list for 1 hour is wrong if data changes frequently
|
|
return this.usersRepo.find();
|
|
}
|
|
}
|
|
```
|
|
|
|
**Correct (strategic caching with proper invalidation):**
|
|
|
|
```typescript
|
|
// Setup caching module
|
|
@Module({
|
|
imports: [
|
|
CacheModule.registerAsync({
|
|
imports: [ConfigModule],
|
|
inject: [ConfigService],
|
|
useFactory: (config: ConfigService) => ({
|
|
stores: [
|
|
new KeyvRedis(config.get('REDIS_URL')),
|
|
],
|
|
ttl: 60 * 1000, // Default 60s
|
|
}),
|
|
}),
|
|
],
|
|
})
|
|
export class AppModule {}
|
|
|
|
// Manual caching for granular control
|
|
@Injectable()
|
|
export class ProductsService {
|
|
constructor(
|
|
@Inject(CACHE_MANAGER) private cache: Cache,
|
|
private productsRepo: ProductRepository,
|
|
) {}
|
|
|
|
async getPopular(): Promise<Product[]> {
|
|
const cacheKey = 'products:popular';
|
|
|
|
// Try cache first
|
|
const cached = await this.cache.get<Product[]>(cacheKey);
|
|
if (cached) return cached;
|
|
|
|
// Cache miss - fetch and cache
|
|
const products = await this.fetchPopularProducts();
|
|
await this.cache.set(cacheKey, products, 5 * 60 * 1000); // 5 min TTL
|
|
return products;
|
|
}
|
|
|
|
// Invalidate cache on changes
|
|
async updateProduct(id: string, dto: UpdateProductDto): Promise<Product> {
|
|
const product = await this.productsRepo.save({ id, ...dto });
|
|
await this.cache.del('products:popular'); // Invalidate
|
|
return product;
|
|
}
|
|
}
|
|
|
|
// Decorator-based caching with auto-interceptor
|
|
@Controller('categories')
|
|
@UseInterceptors(CacheInterceptor)
|
|
export class CategoriesController {
|
|
@Get()
|
|
@CacheTTL(30 * 60 * 1000) // 30 minutes - categories rarely change
|
|
findAll(): Promise<Category[]> {
|
|
return this.categoriesService.findAll();
|
|
}
|
|
|
|
@Get(':id')
|
|
@CacheTTL(60 * 1000) // 1 minute
|
|
@CacheKey('category')
|
|
findOne(@Param('id') id: string): Promise<Category> {
|
|
return this.categoriesService.findOne(id);
|
|
}
|
|
}
|
|
|
|
// Event-based cache invalidation
|
|
@Injectable()
|
|
export class CacheInvalidationService {
|
|
constructor(@Inject(CACHE_MANAGER) private cache: Cache) {}
|
|
|
|
@OnEvent('product.created')
|
|
@OnEvent('product.updated')
|
|
@OnEvent('product.deleted')
|
|
async invalidateProductCaches(event: ProductEvent) {
|
|
await Promise.all([
|
|
this.cache.del('products:popular'),
|
|
this.cache.del(`product:${event.productId}`),
|
|
]);
|
|
}
|
|
}
|
|
```
|
|
|
|
Reference: [NestJS Caching](https://docs.nestjs.com/techniques/caching)
|