المفاهيم
هنفهم إيه؟ لو صفحة المنتجات بتكرر query بطيئة، cache ممكن تسرّع الرد وتخفف حمل قاعدة البيانات. هتتعلم تختار مكان النسخة، تحدد مدة قبول البيانات القديمة، وتتعامل مع التحديث والتعطل من غير ما تعرض بيانات مستخدم لمستخدم تاني.
قبل ما تبدأ: cache نسخة مؤقتة من نتيجة يمكن إعادة بنائها، وsource of truth هو المكان المعتمد للبيانات، غالباً قاعدة البيانات. اعرف إن التطبيق قد يعمل على أكثر من instance؛ ذاكرة واحدة داخل process لن تكون مشتركة تلقائياً بينها.
المصطلحات اللي تحتها خط منقّط تقدر تضغط عليها لمعرفة معناها، وترجع تكمل من نفس مكانك.
#اختيار طبقة الـ cacheChoosing the cache layer
تكرار نفس query المكلفة بيزود وحمل الـ database. الـ cache بيحتفظ بنسخة قابلة لإعادة البناء، مقابل ذاكرة وتعقيد و staleness. ابدأ بقياس bottleneck وحدد مدة تقادم مقبولة لكل use case.
| النوع | النطاق والمقابل |
|---|---|
| IMemoryCache | objects جوه process؛ من غير network أو ، لكن كل instance مستقلة والبيانات بتضيع مع restart. |
| IDistributedCache | عقد لتخزين bytes مع expiry؛ Redis provider بيوفر مخزن مشترك، مع network و serialization. |
| HybridCache | L1 محلية و L2 اختيارية عبر IDistributedCache؛ موحدة وتحكم في serialization وتنسيق الـ misses. |
| Output caching | نسخة response على السيرفر بقواعد caching صريحة. |
| cache و | responses عند browser أو edge بقواعد Cache-Control و validators. |
Redis ممكن يعيش بعد restart بتاع API؛ ده مش ضمان إن Redis نفسه مش هيفقد keys بسبب eviction أو restart أو failover. Cache لازم تقدر تفقده وتعيد بناءه من مصدر الحقيقة.
#Cache-aside بـ IMemoryCache و RedisCache-aside with IMemoryCache and Redis
- صفحة منتج تقرأ سعرًا كثيرًا. أول طلب لا يجد القيمة في الكاش: يقرأ المصدر ويحفظ نسخة مؤقتة، ثم الطلب التالي يستفيد من النسخة.
- لو السعر تغير في المصدر قبل انتهاء ، قد تعرض النسخة القديمة. لازم تقرر متى تبطل الكاش وما مقدار التأخير المقبول في هذه الشاشة.
- عند تأكيد الشراء تحقق من السعر وقواعد العمل في المصدر المعتمد للعملية؛ قيمة الكاش للعرض ليست تلقائيًا السعر المسموح بتحصيله. لو الكاش تعطل، خطط لسعة المصدر عند الرجوع إليه.
في ، التطبيق يقرأ النسخة الأول، ويحمل الأصل عند miss. خلي الـ قدر الإمكان، لأن تعديل object مشترك في memory cache ممكن يغيّر اللي الطلبات التانية بتشوفه.
// DI: builder.Services.AddMemoryCache();
using Microsoft.Extensions.Caching.Memory;
public sealed class CategoryReader(IMemoryCache cache)
{
public string GetLabel(int categoryId) =>
cache.GetOrCreate($"category:v1:{categoryId}", entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5); // (1)
return LoadLabel(categoryId); // (2)
})!;
private static string LoadLabel(int id) => $"Category {id}";
}
// Package: Microsoft.Extensions.Caching.StackExchangeRedis
// redisConnection comes from configuration, not a source-controlled secret.
builder.Services.AddStackExchangeRedisCache(options =>
options.Configuration = redisConnection);
// Fragment in a service with injected IDistributedCache and repository.
var key = $"tenant:{tenantId:N}:product:v1:{productId:N}";
var json = await cache.GetStringAsync(key, ct); // (3)
if (json is null)
{
var product = await repository.LoadAsync(tenantId, productId, ct);
json = JsonSerializer.Serialize(product);
await cache.SetStringAsync(key, json, new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
}, ct); // (4)
}
- (1) absolute expiry بتحط سقف للعمر؛ sliding expiry لوحدها ممكن تخلي key ساخنة تعيش للأبد.
- (2) loader تعليمي؛ في الواقع query أو حساب. GetOrCreate مش ضمان إن factory هتتنادى مرة واحدة تحت التزاحم.
- (3) النسخة distributed bytes أو string، مش references لنفس object. الـ key تشمل tenant موثوق.
- (4) و populate مسؤولية التطبيق. مثال Redis محتاج namespaces بتاعة Caching.Distributed و System.Text.Json.
حدد حدود الذاكرة والـ payload والـ TTL. لو استخدمت SizeLimit في memory cache، كل entry على نفس cache لازم ليها Size؛ استخدم cache مستقلة لو مش متحكم في كل المستخدمين.
#HybridCache من .NET 9: tags و stampede protectionHybridCache since .NET 9: tags and stampede protection
بدل تكرار miss handling و ، استخدم package Microsoft.Extensions.Caching.Hybrid. L2 اختيارية لو فيه IDistributedCache متسجلة؛ من غيرها يفضل L1 و local protection. التنسيق لنفس key جوه نفس HybridCache instance، مش distributed lock بين السيرفرات.
using Microsoft.Extensions.Caching.Hybrid;
builder.Services.AddHybridCache(options =>
options.DefaultEntryOptions = new()
{
Expiration = TimeSpan.FromMinutes(5),
LocalCacheExpiration = TimeSpan.FromSeconds(30) // (1)
});
// Service fragment; LoadCatalogAsync is a tenant-scoped database query.
var catalog = await cache.GetOrCreateAsync(
$"tenant:{tenantId:N}:catalog:v2",
async token => await LoadCatalogAsync(tenantId, token), // (2)
tags: [$"catalog:{tenantId:N}"],
cancellationToken: ct);
// After the database update has committed:
await cache.RemoveByTagAsync($"catalog:{tenantId:N}", ct); // (3)
- (1) عمر محلي أقصر يقلل مدة تقادم النسخ القريبة، لكنه مش consistency guarantee.
- (2) concurrent callers على نفس instance تشترك في تحميل نفس key؛ مرّر اللي factory استلمته.
- (3) tag invalidation منطقي: مش enumeration وحذف مادي لكل payload. باقي instances ممكن تفضل شايفة L1 لحد expiry.
| قبل | من 9 |
|---|---|
| Get و deserialize و load و set و locks يدوية | GetOrCreateAsync و tags وتنسيق local misses في HybridCache |
ماتحطش قواعد correctness الحساسة على tag invalidation. ثبّت نوع القيمة وخياراتها لكل key، وحط نسخة في key عند تغيّر .
#Read و write strategiesRead and write strategies
اختيار strategy بيحدد مين مسؤول عن ملء النسخة وإمتى الأصل يتحدث. مفيش strategy بتخلق بين Redis و تلقائياً.
| الأسلوب | المسار والمقابل |
|---|---|
| التطبيق يفحص cache ويحمل الأصل في miss؛ بسيط لكن فيه cold-start misses و races. | |
| Read-through | طبقة cache أو loader بتخفي تحميل الأصل عن caller؛ سهولة الاستخدام مقابل coupling بالـ provider. |
| Write-through | الكتابة تعدّي عبر طبقة تحدث الأصل والـ cache قبل الإقرار؛ أعلى ولازم failure policy للكتابتين. |
| Write-behind | قبول الكتابة في طبقة مؤقتة، وبعدها persistence async؛ محتاج durable queue و recovery وإلا البيانات ممكن تضيع. |
| Refresh-ahead | تجديد hot keys قبل expiry؛ يقلل misses لكنه بيعمل شغل زائد و scheduling. |
HybridCache شبه read-through للـ caller، وغالباً بيتستخدم لتنفيذ cache-aside في التطبيق. عرّف المصطلح في الانترفيو عشان الاستخدامات بتختلف.
Cache-aside read: GET cache -> miss -> SELECT source -> SET cache // (1)
Write path: UPDATE source -> COMMIT -> invalidate cache // (2)
- (1) miss مش exception؛ ده جزء طبيعي من المسار.
- (2) commit الأول يمنع القارئ يحمل قيمة قبل اكتمال التعديل، لكنه مايلغيش كل stale-fill races.
#Invalidation و سباق stale refillInvalidation and the stale-refill race
سقف تقادم تقريبي حسب توقيت التحميل؛ explicit eviction و tags و events بتسرّع التحديث. Versioned keys بتعزل الأجيال القديمة لكن لازم مصدر version موثوق وتنضيف بالـ TTL. لو invalidation لازم يوصل، سجّله بـ مع التعديل بدل fire-and-forget event.
A: cache miss; reads database version 4 // (1)
B: updates database to version 5; commits; evicts key // (2)
A: stores version 4 in cache after the eviction // (3)
- (1) القارئ بدأ قبل الكاتب لكن اتأخر.
- (2) حتى eviction بعد commit لوحدها مش كفاية لمنع السباق.
- (3) النسخة القديمة رجعت؛ الحل حسب المطلوب: TTL قصير لو مقبول، أو version checks و conditional writes، أو bypass للـ authoritative decision.
#Stampede و penetration و avalancheStampedes, penetration and avalanches
| المشكلة | السبب والحماية |
|---|---|
| طلبات كتير لنفس hot key بعد miss: single-flight، و refresh-ahead، و stale-while-revalidate لو مسموح. | |
| Penetration | طلبات مستمرة لمفاتيح مش موجودة: input validation و negative caching قصيرة و rate limits؛ Bloom filter لو مناسب مع تحديثه. |
| Avalanche | مفاتيح كتير تنتهي مع بعض أو cache تقع بالكامل: jitter و warm-up تدريجي و bounded fallback. |
Single-flight محلية ماتمنعش تحميل واحد من كل instance. Stale-while-revalidate لازم أقصى عمر stale وحدود background refresh، مش بيانات قديمة بلا نهاية.
var baseTtl = TimeSpan.FromMinutes(5);
var jitter = TimeSpan.FromSeconds(Random.Shared.Next(0, 61)); // (1)
var ttl = baseTtl + jitter;
// Apply ttl when populating each independent key.
- (1) توزيع انتهاء الصلاحية من 5 لـ 6 دقايق يقلل الانتهاء المتزامن؛ اختار المدى ضمن freshness budget.
Negative cache لازم تميّز «مش موجود» عن timeout أو permission denied. TTL صغيرة تمنع حجب عنصر اتضاف حديثاً، ومتسمحش لمهاجم يملا الذاكرة بملايين IDs.
#Output cache و HTTP caching و ETagOutput caching, HTTP caching and ETags
Data caching بتوفر query أو حساب. Output caching بتحفظ response كاملة على السيرفر. caching بتخلي المتصفح أو يعيد استخدام response حسب headers. ETag بيسمح بسؤال «اتغيرت؟» وإرجاع 304 من غير body لو representation لسه نفسها.
builder.Services.AddOutputCache(options =>
options.AddPolicy("public-catalog", policy => policy
.Expire(TimeSpan.FromSeconds(30))
.SetVaryByQuery("page", "category")
.Tag("catalog"))); // (1)
var app = builder.Build();
// In authenticated apps, authentication and authorization go before output caching.
app.UseOutputCache();
app.MapGet("/catalog", () => Results.Ok(new[] { "Book", "Pen" }))
.CacheOutput("public-catalog"); // (2)
app.Run();
GET /products/42 HTTP/1.1
If-None-Match: "product-42-v7" # (3)
HTTP/1.1 304 Not Modified
ETag: "product-42-v7"
Cache-Control: public, max-age=60
- (1) policy بتحدد العمر واختلاف query؛ الأمثلة public من غير tenant أو user variation.
- (2) لازم opt-in للـ endpoint؛ AddOutputCache لوحدها مش بتخزن كل حاجة. لإبطال tag استخدم IOutputCacheStore.EvictByTagAsync.
- (3) العميل معاه نسخة؛ السيرفر يقارن validator بالنسخة المصرح بيها قبل 304. المثال exchange توضيحي مش كود HTTP header حرفي للتعليقات.
الـ default policy مش بتخزن authenticated requests أو responses فيها Set-Cookie. ماتكسرش الحماية دي من غير كامل. private يمنع shared caches، و no-store يمنع التخزين، و no-cache يسمح بالتخزين مع revalidation. ETag مش permission check، و مش cache isolation.
#مفاتيح آمنة وحاجات ماينفعش نعتمد على cache فيهاSafe keys and data that must remain authoritative
مفتاح ناقص tenant أو user ممكن يتحول لتسريب بيانات. اشتق tenant من هوية موثوقة بعد authorization، وضمّن أبعاد representation: اللغة، والعملة، والـ filters، و version. استخدم قيم normalized ومحدودة الطول، مش خام من المستخدم.
var key = $"invoice:{invoiceId}";var key = $"tenant:{tenantId:N}:user:{userId:N}:invoice:v2:{invoiceId:N}";
// Authorization and a tenant-scoped source query are still required. // (1)- (1) key تمنع تصادم namespaces، لكنها مش بديل للتحقق من ملكية الفاتورة في الـ source query.
ماتعملش cache للباسوردات، أو CVV، أو raw credentials في response عامة. ماتستخدمش stock أو balance قديمين لاتخاذ قرار حجز أو خصم؛ ممكن تعرض snapshot مع timestamp لكن القرار authoritative. صلاحيات المستخدم وإلغاء الجلسات محتاجين سياسة revocation و مناسبة، مش cache عمياء. داتا شخصية تتخزن بس لضرورة مع encryption و و retention مناسبين.
#Redis غير الـ cache و distributed locksBonusRedis beyond caching and distributed locks
اختياري بس بيفرق: مش هيتسأل فيه في كل انترفيو، بس معرفته بتعمّق فهمك وبتميّزك عن غيرك.
Redis بيوفر data structures: counters للـ rate limits، و sets، و sorted sets للـ leaderboards، و session storage، و Pub/Sub، و Streams. Pub/Sub مش durable queue؛ subscriber غايب يفوته الحدث. Streams فيها history و consumer groups حسب retention والإعدادات.
Distributed lock في Redis عبارة عن lease بمدة، مش ضمان أبدي إن worker واحد بس شغال. لازم فريد لكل acquisition و release ذري يقارن token ويحذف في نفس العملية.
Acquire: SET lock:catalog-import <unique-token> NX PX 30000 // (1)
Release: atomically compare token and delete via Lua // (2)
if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
end
return 0
- (1) NX للغياب و PX مدة بالـ milliseconds؛ ده وصف أوامر، مش C# runnable program.
- (2) مقارنة وحذف في script ذرية؛ GET وبعدين DEL منفصلين ممكن يمسحوا lease جديدة لمالك تاني.
#قياس الـ cache والتعامل مع وقوعهاCache metrics and outage behavior
Hit ratio لوحدها مش نجاح. ممكن hits كتير لداتا رخيصة و misses قليلة بتعمل queries مكلفة جداً. قِس و origin load و evictions و payload size و stale age و errors، وافصل L1 عن L2 والـ negative hits.
hit ratio = hits / (hits + misses) // (1)
900 hits + 100 misses => 90% for that defined measurement window
cache timeout -> bounded fallback -> database concurrency limit // (2)
- (1) حدد النافذة والطبقة وماتحسبش denominator صفر؛ define لو errors ضمن misses ولا metric منفصلة.
- (2) fallback بلا حدود ينقل الضغط كله للـ . استخدم timeouts و bulkhead و load shedding و circuit breaker حسب الحاجة.
اختبر cold cache و unavailable cache و invalidation failure، مش warm-cache benchmark بس. لو الداتا optional، degrade واضح أو serve stale ضمن حد؛ لو authoritative، fail آمن بدل إجابة قديمة توهم المستخدم بالنجاح.
أسئلة الانترفيو
مفيش نتايج
راجع فهمك
ليه الاختيار ده مناسب؟ إمتى ممكن ماينفعش؟ وإيه اللي هيتغير لو الحمل زاد أو ظهر فشل؟
18.1Why use a cache, and what does it cost?Junior
لتقليل تكرار شغل مكلف بعد قياسه. التكلفة ذاكرة و staleness و invalidation وتشغيل إضافي؛ مش بديل عن إصلاح query سيئة.
I use a cache when repeated reads or calculations create measurable or load and the result can tolerate a defined freshness window. It trades memory and operational complexity for less origin work. I still consider query optimization and indexes first, because caching can hide an expensive underlying path until a cold start or outage exposes it. I also define invalidation, capacity limits and fallback behavior before judging the change by its warm-cache latency.
18.2How do IMemoryCache and IDistributedCache differ?Junior
الأولى objects محلية لكل process. التانية contract لـ bytes مشتركة حسب provider، مع network و ومش ضمان persistence مطلق.
I use IMemoryCache for in-process values where independent instance copies are acceptable. It avoids network and costs but disappears on restart and is not shared. IDistributedCache represents an external byte store, commonly Redis, so instances can access the same cached value. That introduces serialization, network failures and deployment compatibility concerns. Neither abstraction makes cached data authoritative or guarantees it can never disappear. I design misses and rebuilds as normal behavior in both cases.
18.3What is cache-aside?Junior
اقرا cache، ولو miss حمّل المصدر وخزن مع . في الكتابة حدث المصدر وبعدين invalidate، مع فهم races.
I implement by checking the cache, loading the authoritative source on a miss, and storing a copy with a bounded lifetime. The application owns that flow and invalidates related entries after a committed write. It is a good fit for read-heavy data with tolerated staleness. However, concurrent misses can overload the source, and readers can repopulate old values around a write, so I also consider single-flight loading and a deliberate consistency policy.
18.4What does HybridCache add in modern .NET?Mid
L1 و L2 اختيارية و و tags وتنسيق concurrent misses. الحماية جوه instance، ومش distributed lock ولا invalidation فورية لكل L1.
I use HybridCache to simplify typed caching with local memory and an optional distributed second level. It handles , supports tags and coordinates concurrent loads for the same key within one HybridCache instance. That removes repetitive application code, but it is not a distributed lock across servers. Other instances can still load independently and retain local values until expiration. I therefore choose local and distributed lifetimes according to freshness requirements and do not use tags as a correctness boundary.
18.5How do you invalidate cached data?Mid
مع eviction بعد commit، و tags أو events أو versioned keys. لو event مهمة استخدم . حدد freshness المقبولة بدل وعد strong consistency.
I combine a bounded with explicit invalidation after the source commits. Depending on the data, I use keys, tags, durable invalidation events or versioned key generations. I define the tolerated stale window rather than claiming eviction provides strong consistency. A reader can still fill an old value after a concurrent write, and another instance can hold a local copy. Correctness-critical decisions bypass stale copies or use an authoritative version check and atomic source operation.
18.6What is a cache stampede?Mid
Hot key غابت فجأة وطلبات كتير حملت الأصل. Single-flight و bounded refresh يقللوا الحمل؛ jitter مفيدة لانتهاء مفاتيح كتير لكنها مش حل كامل لنفس key.
I call it a when many requests miss the same popular entry and all recompute it together. I use single-flight loading to share that work, with refresh-ahead or bounded stale-while-revalidate where the business permits it. HybridCache coordinates loads locally, so I still account for multiple instances. jitter helps distribute different keys' expirations but does not alone prevent a burst against one missing key. I also bound origin concurrency to survive cold starts.
18.7How do penetration and avalanche differ?Mid
Penetration لمفاتيح مش موجودة، و avalanche لانتهاء مفاتيح كتير أو فقد cache كلها. الحلول مختلفة بس fallback bounded مهمة للاتنين.
I distinguish penetration, where repeated requests target nonexistent keys, from an avalanche, where many entries expire together or the cache becomes unavailable. For penetration I validate inputs, apply rate limits and optionally cache a genuine not-found result briefly. For avalanches I spread expiration times, warm gradually and limit fallback concurrency. I never negative-cache a timeout as if the item did not exist, and I bound cache capacity so arbitrary input cannot create unlimited negative entries.
18.8Explain read-through, write-through and write-behind.Mid
Read-through يخبي loader، و write-through يقر بعد تحديث المصدر والنسخة، و write-behind يحفظ الأصل بعدين. مفيش atomicity تلقائية بين مخزنين.
I use read-through to mean a caching layer that loads the source transparently on a miss. Write-through routes writes through a layer that updates the authoritative store and cache before acknowledging. Write-behind acknowledges buffered changes before persisting them asynchronously. I clarify these definitions because terminology varies between products. None automatically makes Redis and one . Write-through needs a partial-failure policy, and write-behind requires durable buffering, recovery and an explicit tolerance for delayed persistence.
18.9How do output caching and HTTP caching differ?Mid
Output server policy للـ response، و caching بروتوكول عند browser أو . الـ ETag validation، مش حفظ body ولا authorization.
I use output caching to store complete responses on the server under application policies, including expiration, variation and tags. caching uses protocol headers to control browser and shared-cache reuse. An ETag supports conditional validation and a 304 response when the authorized representation is unchanged. I keep public and personalized content separate, preserve the output-cache defaults around authenticated requests, and make every representation dimension part of the variation policy before enabling shared response caching.
18.10What is the difference between no-cache, no-store and private?Junior
No-cache يسمح تخزين مع validation قبل reuse. No-store منع تخزين. Private يمنع shared cache لكنه يسمح browser حسب باقي القواعد.
I do not read no-cache as a prohibition on storage. It allows storage but requires validation before reuse. No-store tells caches not to store the response, while private prevents shared caches from storing it but can still permit a browser cache. I choose the directive according to the sensitivity and reuse requirements, together with authorization and secure transport. These headers are cache instructions; they do not make exposed data confidential or erase copies already obtained by a client.
18.11How do you design multi-tenant cache keys safely?Senior
Tenant موثوق، و user لو فيه personalization، وأبعاد اللغة والعملة والـ filters والنسخة. key مش بديل لـ tenant filter في source query.
I derive tenant identity from an authenticated, authorized context and include it in every tenant-specific key. I add user identity when content is personalized, plus relevant language, currency, filter and -version dimensions. Values are normalized and bounded so attacker-controlled input cannot create collisions or unbounded key growth. The source query remains tenant-scoped, and authorization happens before serving cached data. I test cross-tenant access explicitly because a perfectly functioning cache can otherwise distribute another tenant's information very efficiently.
18.12What would you never use stale cached data to decide?Mid
حجز مخزون، أو خصم رصيد، أو قرار صلاحية حساس من غير revocation policy. عرض snapshot شيء واتخاذ قرار نهائي شيء تاني.
I do not use a stale copy as the authority for stock reservation, balance debiting or a sensitive access decision without an explicit revocation model. Those decisions need an atomic operation or current validation at the source. A cached stock display can still be useful if checkout revalidates availability. I also avoid caching passwords, payment security codes or secrets in public responses. Personal data needs a justified purpose, correct partitioning, limited retention and protection at rest and in transit.
18.13Why can a distributed lock expire while work is still running?Senior
الـ lease مدتها ممكن تخلص أثناء pause أو partition. Worker قديم يصحى بعد مالك جديد؛ compare-delete يحمي release بس، و fencing يحمي الكتابة لو المورد بيفحصه.
I treat a distributed lock as a lease with a deadline. A paused process or network interruption can exceed that deadline, allowing another worker to acquire the lock while the original later resumes. A unique and atomic compare-and-delete make release safer, but they do not stop stale writes. For correctness I use a monotonic fencing token that the protected resource enforces, or rely on database concurrency and constraints. Renewal improves availability but does not eliminate every failure window.
18.14What else can Redis do besides caching?Mid
Counters و rate limiting و sorted sets و sessions و Pub/Sub و Streams. Pub/Sub يفوت الرسالة للغايب، و atomic command مش معناها workflow كامل atomic.
I use Redis data structures for counters, rate limiting, session storage, sets and sorted-set leaderboards. Pub/Sub supports transient broadcast, while Streams can retain records and support consumer groups under configured retention. I choose the structure around the required delivery and recovery semantics. An individual atomic command does not make a multi-command workflow atomic, so related updates may require a or script. I also evaluate persistence, and eviction settings before storing anything that cannot simply be rebuilt.
18.15How do you know a cache is helping and can survive an outage?Senior
Hit ratio مع و origin load و evictions و errors، واختبارات cold و outage. Fallback غير محدود ممكن يوقع الـ .
I compare end-to-end and origin load before and after caching, alongside hit ratio, evictions, payload sizes and cache errors. I separate local and distributed hits because their costs differ. Then I exercise cold starts, unavailable Redis and failed invalidation. Fallback must have timeouts and bounded database concurrency; otherwise a cache outage can become a database outage. I define whether each endpoint may serve bounded stale data, degrade its response or fail safely under those conditions.
اختبر نفسك
إجابات الاختبار وعلامات إكمال المحاور بتتحفظ على المتصفح ده، ومش بتتنقل لأجهزة تانية.
Which layer shares serialized cached values across API instances?
What is the scope of HybridCache single-flight coordination?
Many independent keys expire at the same second. Which mitigation fits?
Repeated requests target nonexistent products. Which technique helps?
What does Cache-Control: no-cache mean?
Why is a tenant-aware cache key insufficient by itself?
Why is separate Redis GET then DEL unsafe for lock release?
900 hits and 100 misses give which hit ratio?
A writer commits then evicts, but an older reader fills afterwards. What happened?
Redis becomes unavailable. What protects the database?