In-memory Cache — IMemoryCache, sliding/absolute expiration
Muammodan boshlaylik
Bosh sahifada "Kategoriyalar" ro'yxati. Har bir so'rovda — bazaga so'rov, JOIN, hisoblash. Kategoriyalar oyiga bir marta o'zgaradi, lekin daqiqasiga 3000 marta o'qiladi. Baza bir xil ishni 3000 marta qiladi; kampaniya kunida u bo'g'iladi va butun sayt sekinlashadi.
Hayotiy o'xshatma
Ofitsiant va menyu. Har bir mehmon "bugun nima bor?" deb so'raganda, ofitsiant oshxonaga borib oshpazdan so'ramaydi — ertalab bir marta so'rab, yozib olgan. Kun davomida yozuvdan o'qiydi. Biror taom tugasa — oshpaz aytadi, ofitsiant yozuvni yangilaydi.
Cache nima?
Cache — tez-tez so'raladigan va kam o'zgaradigan ma'lumotni tezkor joyda (xotirada) vaqtincha saqlash.
| Manba | Taxminiy vaqt |
|---|---|
Xotira (IMemoryCache) |
mikrosekundlar |
| Redis (tarmoq orqali) | ~1 ms |
| Baza so'rovi | 5–100+ ms |
| Tashqi API | 100–1000+ ms |
IMemoryCache
builder.Services.AddMemoryCache();
public class KategoriyaService(DokonDbContext db, IMemoryCache cache)
{
private const string Kalit = "kategoriyalar:hammasi";
public async Task<List<KategoriyaDto>> HammasiAsync(CancellationToken ct)
{
return (await cache.GetOrCreateAsync(Kalit, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30);
return await db.Kategoriyalar
.AsNoTracking()
.OrderBy(k => k.Nomi)
.Select(k => new KategoriyaDto(k.Id, k.Nomi))
.ToListAsync(ct);
}))!;
}
}
Birinchi so'rov — bazadan olib cache ga yozadi; keyingi 30 daqiqa — xotiradan.
Qo'lda boshqarish
if (!cache.TryGetValue(Kalit, out List<KategoriyaDto>? natija))
{
natija = await BazadanOlAsync(ct);
cache.Set(Kalit, natija, TimeSpan.FromMinutes(30));
}
Muddat turlari
var opt = new MemoryCacheEntryOptions()
.SetAbsoluteExpiration(TimeSpan.FromHours(1)) // 1 soatdan keyin — albatta o'chadi
.SetSlidingExpiration(TimeSpan.FromMinutes(10)) // 10 daqiqa so'ralmasa — o'chadi
.SetPriority(CacheItemPriority.High)
.SetSize(1);
| Absolute | Sliding | |
|---|---|---|
| Ma'nosi | Belgilangan vaqtdan keyin | Oxirgi murojaatdan keyin |
| Xavf | — | Tez-tez so'ralsa — hech qachon yangilanmaydi |
[!TIP] Sliding ishlatsangiz — doim absolute bilan birga. Faqat sliding bo'lsa, mashhur kalit abadiy cache da qoladi va eskirgan ma'lumot ko'rsatiladi.
Invalidatsiya — cache ni tozalash
"Kompyuter fanida ikki qiyin narsa bor: cache invalidatsiyasi va narsalarga nom berish." — Phil Karlton
Ma'lumot o'zgarganda — cache ni o'chiring:
public async Task YaratAsync(KategoriyaYaratDto dto, CancellationToken ct)
{
db.Kategoriyalar.Add(new Kategoriya { Nomi = dto.Nomi });
await db.SaveChangesAsync(ct);
cache.Remove(Kalit); // keyingi o'qish bazadan yangisini oladi
}
Strategiyalar:
- Muddat bo'yicha — biroz eskirgan ma'lumotga chidash mumkin bo'lsa (statistika, tavsiyalar)
- O'zgarishda o'chirish — aniqlik muhim bo'lsa (narx, qoldiq)
- Ikkalasi birga — eng ishonchli
Kalitlarni nomlash
quot;mahsulot:{id}"
quot;mahsulotlar:kategoriya:{katId}:sahifa:{sahifa}"
quot;foydalanuvchi:{userId}:savat"
[!WARNING] Foydalanuvchiga xos ma'lumotni kalitda foydalanuvchi ID siz saqlamang —
"savat"kaliti bilan birinchi foydalanuvchining savati hammaga ko'rinadi. Shaxsiy ma'lumot uchun kalitda doim egasi bo'lsin.
Xotira chegarasi
builder.Services.AddMemoryCache(o => o.SizeLimit = 10_000);
SizeLimit qo'yilsa — har bir yozuvda SetSize majburiy; chegara to'lganda past prioritetlilar chiqariladi. Chegarasiz cache — xotira cheksiz o'sishi mumkin.
HybridCache (.NET 9+)
builder.Services.AddHybridCache();
public class KategoriyaService(HybridCache cache, DokonDbContext db)
{
public async Task<List<KategoriyaDto>> HammasiAsync(CancellationToken ct) =>
await cache.GetOrCreateAsync("kategoriyalar:hammasi",
async token => await db.Kategoriyalar.Select(k => new KategoriyaDto(k.Id, k.Nomi)).ToListAsync(token),
cancellationToken: ct);
}
Afzalliklari:
- Stampede himoyasi — cache bo'sh paytda 1000 ta so'rov kelsa, bazaga faqat bitta so'rov ketadi, qolganlari natijani kutadi
- Ikki darajali: xotira (L1) + Redis (L2, 68-dars) — bitta API bilan
- Teglar bo'yicha o'chirish (
RemoveByTagAsync)
IMemoryCache cheklovi
Har bir server nusxasining o'z xotirasi bor. 3 ta server bo'lsa — 3 ta alohida cache: birida o'chirilgan qiymat boshqasida qoladi. Bir nechta server uchun — taqsimlangan cache (Redis, 68-dars).
Tez-tez uchraydigan xatolar
Tracked entity ni cache lash — DbContext bilan bog'liq ob'ekt; DTO yoki AsNoTracking natijasini saqlang.
Cache dagi ob'ektni o'zgartirish — xotiradagi ob'ekt bitta, o'zgarish hamma so'rovlarga ta'sir qiladi; o'zgarmas (record) turlar.
Hammasini cache lash — tez-tez o'zgaradigan yoki kam so'raladigan ma'lumot uchun foyda yo'q, faqat xato xavfi.
Xulosa
Cache — tez-tez o'qiladigan, kam o'zgaradigan ma'lumotni xotirada saqlash. AddMemoryCache + IMemoryCache: GetOrCreateAsync, TryGetValue/Set, Remove. Muddat: absolute (aniq vaqt) va sliding (so'ralmasa) — sliding faqat absolute bilan birga.
Invalidatsiya — muddat va o'zgarishda Remove. Kalitlar — tartibli, shaxsiy ma'lumotda egasi bilan. SizeLimit bilan xotirani cheklang. .NET 9+ HybridCache — stampede himoyasi va L1+L2. IMemoryCache — faqat bitta server uchun.
Keyingi dars
68-dars: Redis Cache — IDistributedCache, StackExchange.Redis