Caching — Redis bilan MongoDB natijalarini keshlash
Muammodan boshlaylik
Bosh sahifa har ochilganda:
const kategoriyalar = await Kategoriya.find().lean() // 15 ms
const ommabop = await Mahsulot.aggregate([...]) // 420 ms
const aksiyalar = await Aksiya.find({ faol: true }).populate(...) // 60 ms
Daqiqasiga 3000 ta tashrif — MongoDB 3000 marta bir xil og'ir aggregation ni bajaradi. Natija esa soatlab deyarli o'zgarmaydi. CPU 90%, boshqa so'rovlar ham sekinlashadi.
Cache qayerda
Ilova ──▶ Redis (1 ms) ──yo'q──▶ MongoDB (420 ms) ──▶ Redis ga saqlash
│
└──bor──▶ darhol javob
Redis — xotirada ishlaydigan kalit-qiymat ombori: o'qish ~1 ms, barcha ilova nusxalari uchun umumiy (Express kursi, 57-dars).
Hayotiy o'xshatma
Ofisdagi kotib va arxiv. Har kim "o'tgan oylik hisobot"ni so'raydi. Kotib birinchi marta arxivga tushib (MongoDB), hisobotni topadi va bir nusxasini stoliga qo'yadi (Redis). Keyingi safar — stoldan beradi. Hisobot yangilanganda — stoldagi eski nusxani tashlab yuboradi.
O'rnatish
npm install redis
// src/cache.js
import { createClient } from 'redis'
export const redis = createClient({ url: process.env.REDIS_URL })
redis.on('error', (err) => console.error('Redis xatosi', err))
await redis.connect()
Cache-aside — universal yordamchi
// src/cache.js
export async function keshlangan(kalit, soniya, olish) {
try {
const bor = await redis.get(kalit)
if (bor) return JSON.parse(bor)
} catch (err) {
console.error('Redis o\'qish xatosi', err) // Redis ishlamasa — DB dan davom etamiz
}
const yangi = await olish()
redis.set(kalit, JSON.stringify(yangi), { EX: soniya }).catch(() => {})
return yangi
}
export async function keshniTozala(...kalitlar) {
await redis.del(kalitlar).catch(() => {})
}
router.get('/bosh-sahifa', async (req, res) => {
const [kategoriyalar, ommabop] = await Promise.all([
keshlangan('kategoriyalar', 3600, () => Kategoriya.find().lean()),
keshlangan('mahsulotlar:ommabop', 600, () => Mahsulot.ommabop()),
])
res.json({ kategoriyalar, ommabop })
})
.lean()— Mongoose document emas, oddiy obyekt keshlansin- Redis xatosi — sayt to'xtamasin, to'g'ridan-to'g'ri DB ga murojaat qilinsin
- TTL (
EX) — har doim
[!WARNING] JSON ga aylantirganda MongoDB turlari o'zgaradi:
ObjectId→ string,Date→ ISO string. Keshdan olinganm.createdAt.getTime()— xato,m._id.equals()— xato. Keshlangan ma'lumot bilan ishlashda bu turlarni string deb hisoblang yoki kerak joyda qayta o'giring.
Bitta document keshi
export const mahsulotKaliti = (id) => `mahsulot:${id}`
router.get('/mahsulotlar/:id', async (req, res) => {
const m = await keshlangan(mahsulotKaliti(req.params.id), 300, () =>
Mahsulot.findById(req.params.id).populate('kategoriya', 'nomi').lean()
)
if (!m) return res.status(404).json({ xato: 'Topilmadi' })
res.json(m)
})
[!INFO]
nullnatija ham keshlanadi — bu yaxshi: mavjud bo'lmagan ID larni qayta-qayta so'rash (bot yoki hujum) bazani yuklamaydi. Faqat TTL qisqaroq bo'lsin.
Invalidation — eskirgan keshni tozalash
Yozish joyida
router.patch('/mahsulotlar/:id', async (req, res) => {
const m = await Mahsulot.findByIdAndUpdate(req.params.id, req.body, { new: true, runValidators: true })
await keshniTozala(mahsulotKaliti(req.params.id), 'mahsulotlar:ommabop')
res.json(m)
})
Mongoose middleware da — avtomatik
mahsulotSchema.post('save', async function (doc) {
await keshniTozala(mahsulotKaliti(doc._id), 'mahsulotlar:ommabop')
})
mahsulotSchema.post('findOneAndUpdate', async function (doc) {
if (doc) await keshniTozala(mahsulotKaliti(doc._id), 'mahsulotlar:ommabop')
})
Mahsulot qayerdan o'zgartirilmasin (API, admin panel, skript) — kesh tozalanadi. Lekin updateMany kabi so'rovlar uchun ham hook yozishni unutmang.
Barcha manbalardan — Change Streams
Boshqa xizmatlar ham bazani o'zgartirsa — Change Stream (54-dars) orqali:
Mahsulot.watch().on('change', (o) => {
if (o.documentKey) keshniTozala(mahsulotKaliti(o.documentKey._id))
})
Kesh strategiyalari
| Strategiya | Qanday | Qachon |
|---|---|---|
| Cache-aside | O'qishda: yo'q bo'lsa DB → keshga | Ko'pchilik holatlar |
| Write-through | Yozishda: DB + keshni birga yangilash | O'qish juda ko'p, kesh doim yangi bo'lishi kerak |
| TTL faqat | Invalidation siz, muddat tugashini kutish | Biroz eskirish muammo emas (statistika) |
| Oldindan isitish | Scheduled job keshni to'ldiradi | Juda og'ir hisobotlar |
Cache stampede
Mashhur kalitning TTL i tugadi — shu soniyada 500 ta so'rov keshni bo'sh topadi va hammasi bir vaqtda og'ir aggregation ni ishga tushiradi.
Oddiy himoya — bir jarayon ichida bitta so'rov:
const jarayonda = new Map()
export async function keshlangan(kalit, soniya, olish) {
const bor = await redis.get(kalit).catch(() => null)
if (bor) return JSON.parse(bor)
if (jarayonda.has(kalit)) return jarayonda.get(kalit)
const vada = olish()
.then(async (yangi) => {
await redis.set(kalit, JSON.stringify(yangi), { EX: soniya }).catch(() => {})
return yangi
})
.finally(() => jarayonda.delete(kalit))
jarayonda.set(kalit, vada)
return vada
}
Bir nechta server uchun — Redis qulfi (SET kalit:lock 1 NX EX 10) yoki TTL ga tasodifiy qo'shimcha (jitter) — kalitlar bir vaqtda eskirmasligi uchun.
[!TIP] Keshlashdan oldin so'rovni tezlashtirishga harakat qiling: to'g'ri index (26–29-darslar) ko'pincha 420 ms ni 5 ms ga tushiradi va kesh umuman kerak bo'lmaydi. Kesh — murakkablik va eskirgan ma'lumot xavfini qo'shadi.
Tez-tez uchraydigan xatolar
Foydalanuvchiga xos ma'lumotni umumiy kalit bilan
// ❌ — hamma birinchi foydalanuvchining savatini ko'radi
keshlangan('savat', ...)
// ✅
keshlangan(`savat:${req.user.id}`, ...)
Mongoose document ni keshlash
JSON.stringify(doc) — ishlaydi, lekin keshdan olingani document emas, oddiy obyekt. .lean() bilan boshidanoq oddiy obyekt oling.
TTL siz kalitlar
Invalidation unutilsa — kesh abadiy eskirgan qoladi va Redis xotirasi to'ladi.
Xulosa
- Redis — og'ir va tez-tez so'raladigan natijalar uchun umumiy kesh
- Cache-aside: Redis → yo'q bo'lsa MongoDB → saqlash (TTL bilan)
.lean()natijalarni keshlang; JSON daObjectIdvaDatestring ga aylanadi- Invalidation: yozish joyida, Mongoose
posthook lari yoki Change Streams - Redis xatosi saytni to'xtatmasin
- Stampede — jarayon ichida bitta so'rov, qulf, jitter
- Avval index — keyin kesh
Keyingi dars: 59-dars — Migration — migrate-mongo bilan schema o'zgarishlarini boshqarish