Caching — Cache Manager, Redis
Muammodan boshlaylik
GET /products — 500 ta mahsulot. Har so'rovda baza so'rovi, filtr, sort. Soniyada 1000 ta foydalanuvchi so'raydi. Baza qulab tushdi.
Lekin mahsulotlar har soniyada o'zgarmaydi — 5 daqiqada bir marta yangilanadi. Har so'rovda baza so'rovi qilish — isrof.
Caching: birinchi so'rovda natija hisoblanadi va saqlanadi. Keyingi so'rovlarda bazaga bormasdan saqlangan natija qaytariladi.
Loyiha tuzilishi
src/
├── app.module.ts ← CacheModule ulash
│
├── products/
│ ├── products.module.ts
│ ├── products.controller.ts ← @CacheKey, @CacheTTL
│ └── products.service.ts ← CACHE_MANAGER inject
│
└── cache/
└── cache.config.ts ← Redis sozlamalari
O'rnatish
npm install @nestjs/cache-manager cache-manager
npm install cache-manager-redis-yet # Redis uchun
npm install ioredis
Xotira cache (dev uchun)
// src/app.module.ts
import { CacheModule } from '@nestjs/cache-manager';
@Module({
imports: [
CacheModule.register({
isGlobal: true, // barcha modullarda ishlatish
ttl: 300, // standart: 5 daqiqa (soniyada)
max: 1000, // maksimal 1000 ta element
}),
],
})
export class AppModule {}
Redis cache (production uchun)
// src/cache/cache.config.ts
import { redisStore } from 'cache-manager-redis-yet';
import { ConfigService } from '@nestjs/config';
export const cacheConfig = {
isGlobal: true,
store: async () => {
const config = new ConfigService();
return redisStore({
socket: {
host: config.get('REDIS_HOST', 'localhost'),
port: config.get<number>('REDIS_PORT', 6379),
},
ttl: 300 * 1000, // millisekunda — 5 daqiqa
});
},
};
// src/app.module.ts
import { CacheModule } from '@nestjs/cache-manager';
import { cacheConfig } from './cache/cache.config';
@Module({
imports: [
CacheModule.registerAsync({
isGlobal: true,
useFactory: async (config: ConfigService) => ({
store: await redisStore({
socket: {
host: config.get('REDIS_HOST'),
port: config.get('REDIS_PORT'),
},
ttl: 300_000,
}),
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}
1-usul: CACHE_MANAGER — qo'lda boshqarish
Eng moslashuvchan usul. Cache ni to'liq nazorat qilasiz:
// src/products/products.service.ts
import { Injectable } from '@nestjs/common';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject } from '@nestjs/common';
import { Cache } from 'cache-manager';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
@Injectable()
export class ProductsService {
constructor(
@InjectModel('Product') private productModel: Model<any>,
@Inject(CACHE_MANAGER) private cache: Cache, // ← inject
) {}
async findAll(query: any) {
// Cache kaliti — so'rov parametrlaridan
const key = `products:${JSON.stringify(query)}`;
// 1. Cacheda bormi?
const cached = await this.cache.get(key);
if (cached) return cached; // ← baza so'rovi yo'q
// 2. Yo'q — bazadan olamiz
const data = await this.productModel
.find(query)
.sort({ createdAt: -1 })
.exec();
// 3. Cachega yozamiz — 5 daqiqa
await this.cache.set(key, data, 300_000);
return data;
}
async findOne(id: string) {
const key = `product:${id}`;
const cached = await this.cache.get(key);
if (cached) return cached;
const product = await this.productModel.findById(id).exec();
if (!product) throw new NotFoundException('Topilmadi');
await this.cache.set(key, product, 600_000); // 10 daqiqa
return product;
}
// Mahsulot o'zgarganda — cacheni tozalash
async update(id: string, dto: any) {
const product = await this.productModel
.findByIdAndUpdate(id, dto, { new: true })
.exec();
// Eski cache yaroqsiz — o'chiramiz
await this.cache.del(`product:${id}`);
await this.invalidateListCache(); // ro'yxat ham yangilanishi kerak
return product;
}
async remove(id: string) {
await this.productModel.findByIdAndDelete(id).exec();
await this.cache.del(`product:${id}`);
await this.invalidateListCache();
return { message: 'O\'chirildi' };
}
// Ro'yxat cache larini tozalash (pattern bo'yicha)
private async invalidateListCache() {
// Redis da pattern bilan o'chirish
// Xotira cache da barcha kalitlarni olib, filtrlaymiz
await this.cache.del('products:*'); // ← Redis pattern (store ga bog'liq)
}
}
2-usul: @CacheKey va @CacheTTL — interceptor
Oddiyroq usul — controller metodiga dekorator qo'shiladi:
// src/products/products.controller.ts
import { Controller, Get, Param, UseInterceptors } from '@nestjs/common';
import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager';
@Controller('products')
@UseInterceptors(CacheInterceptor) // ← barcha GET metodlarga
export class ProductsController {
constructor(private productsService: ProductsService) {}
@Get()
@CacheKey('all-products') // ← cache kaliti
@CacheTTL(300_000) // ← 5 daqiqa
findAll() {
return this.productsService.findAll({});
}
@Get('featured')
@CacheKey('featured-products')
@CacheTTL(600_000) // ← 10 daqiqa
findFeatured() {
return this.productsService.findFeatured();
}
// Cache ishlatilmaydi (POST, PUT, DELETE — avtomatik)
@Post()
create(@Body() dto: CreateProductDto) {
return this.productsService.create(dto);
}
}
CacheInterceptor faqat GET so'rovlarni keshleydi. POST, PUT, DELETE — avtomatik o'tkazib yuboriladi.
Cache strategiyalari
Cache-Aside (yuqoridagi misollar):
1. Cacheda qidirish
2. Yo'q → bazadan olish
3. Cachega yozish
✓ Eng ko'p ishlatiladi
Write-Through:
Ma'lumot yozilganda ham cache ham baza yangilanadi
✓ Cache har doim yangi
- Yozish sekinlashadi
Cache Invalidation:
Ma'lumot o'zgarganda — tegishli cache lar o'chiriladi
✓ findAll() dan keyin create() bo'lsa — cache tozalanadi
Cache kalitlari strategiyasi
// Yaxshi kalit nomlash — "resurs:identifikator:variant"
`products` // barcha mahsulotlar
`products:category:electronics` // elektronika kategoriya
`product:64a...` // bitta mahsulot
`user:64b...:profile` // foydalanuvchi profili
`search:telefon:page:1:limit:10` // qidiruv natijalari
// ❌ Yomon — noaniq, to'qnashishi mumkin
`data`
`list`
`result`
Tez-tez uchraydigan xatolar
Cache invalidation yo'q — eski ma'lumot ko'rsatiladi
// ❌ Ma'lumot yangilandi, lekin cache eski — 5 daqiqa eski mahsulot ko'rinadi
async update(id, dto) {
return this.productModel.findByIdAndUpdate(id, dto, { new: true }).exec();
// cache tozalanmadi!
}
// ✅ Yangilananda cache o'chiriladi
async update(id, dto) {
const product = await this.productModel.findByIdAndUpdate(id, dto, { new: true }).exec();
await this.cache.del(`product:${id}`);
return product;
}
Katta ob'ektlarni keshlash
// ❌ 10MB json — Redis da joy egallaydi, serializatsiya sekin
await this.cache.set('all-users', hugeData, 300_000);
// ✅ Faqat kerakli ma'lumotni keshlash, yoki pagination bilan
await this.cache.set('users:page:1', pageData, 300_000);
TTL ni ms yoki soniyada adashish
// @nestjs/cache-manager v2+ → millisekunda
await this.cache.set(key, data, 300_000); // 5 daqiqa (ms)
// Eski versiya → soniya
await this.cache.set(key, data, 300); // 5 daqiqa (s)
// ← versiyaga qarab farq qiladi, docs ni tekshiring
Xulosa
Caching — bazaga keraksiz so'rovlarni kamaytirish. Ko'p o'qiladigan, kam o'zgaradigan ma'lumotlar uchun.
Eslab qoling:
CACHE_MANAGERinject — qo'lda boshqarish (moslashuvchan)CacheInterceptor— dekorator bilan (oddiy)cache.get(key)→cache.set(key, val, ttl)→cache.del(key)- Ma'lumot o'zgarganda — tegishli cache
delqilinadi - Kalit nomi:
resurs:id:variantformatida - Dev — xotira cache, Prod — Redis
Keyingi dars
34-dars: Helmet va CORS sozlash