- بواسطة x32x01 ||
لما الـ Application يكون صغير، الـ Flow بيكون بسيط:
Client ⇒ API ⇒ Database
لكن مع زيادة الـ Traffic، المشكلة بتظهر بسرعة: نفس البيانات ممكن تتطلب آلاف المرات في الثانية، ومع كل Request بنعمل Query جديدة على الـ Database.
النتيجة؟
الفكرة ببساطة إنك بدل ما تسأل الـ Database عن نفس البيانات كل مرة، تحتفظ بنسخة منها في مكان أسرع، وتستخدم النسخة دي للطلبات المتكررة.
لكن اختيار نوع الـ Cache مش مجرد إنك "تحط Redis وخلاص". هو قرار معماري له علاقة بعدد الـ Servers، سرعة الوصول للبيانات، حجمها، ومدى حساسيتك للـ Stale Data.
مثلًا، لو عندك Query بتجيب قائمة المنتجات وتاخد وقت ملحوظ، ومئات المستخدمين بيطلبوا نفس القائمة، مش منطقي إن كل Request يروح للـ Database من جديد.
بدل كده:
Client ⇒ API ⇒ Cache ⇒ Database
لو البيانات موجودة في الـ Cache، الـ API يرجعها مباشرة.
ولو مش موجودة، نروح للـ Database، وبعدها نخزن النتيجة في الـ Cache عشان الطلبات التالية تستفيد منها.
💡 الهدف الأساسي هو تقليل عدد الـ Database Queries وتحسين زمن الاستجابة.
في .NET، تقدر تستخدم
الميزة الأساسية هنا إن البيانات قريبة جدًا من الكود اللي بيستخدمها، وبالتالي مفيش Network Request لـ Cache Server خارجي.
Server 1 → Cache A
Server 2 → Cache B
Server 3 → Cache C
لو Server 1 عنده بيانات محدثة، وServer 2 عنده نسخة قديمة، ممكن نفس المستخدم ياخد نتائج مختلفة حسب الـ Request راح لأنهي Server.
وده من أهم أسباب استخدام Distributed Cache في الأنظمة اللي بتعمل Scale-out.
وبعدها تقدر تستخدم
الفكرة هنا بسيطة جدًا:
🔍 الأول نشوف البيانات موجودة في الـ Cache ولا لأ.
لو موجودة ⬅️ Cache Hit.
لو مش موجودة ⬅️ Cache Miss، وساعتها نجيب البيانات من الـ Database ونخزنها في الـ Cache.
هنا بييجي دور الـ Distributed Cache.
وأشهر اختيار في الحالة دي هو Redis.
بدل ما كل Application Instance يكون عندها Cache منفصل:
كل الـ Instances بتتعامل مع نفس الـ Cache.
وده بيخلي البيانات الموجودة في الـ Cache مشتركة بين الـ Servers.
وتقدر تتأكد إن الـ Container شغال باستخدام:
بعدها في مشروع .NET تقدر تضيف Package الخاصة بـ Redis:
وتسجل Redis كـ Distributed Cache:
الـ In-Memory Cache موجود داخل نفس الـ Process، بينما Redis غالبًا يحتاج Network Communication.
يعني عند استخدام Redis عندك تكلفة إضافية مثل:
وده غالبًا أهم من فرق السرعة البسيط لما يكون النظام محتاج Scale-out.
الخلاصة هنا:
لو عندك Server واحد والبيانات مناسبة للتخزين المحلي، In-Memory ممكن يكون كفاية جدًا.
لكن لو عندك أكثر من Instance وعايز Cache مشترك، Redis بيكون اختيار أقوى.
ممكن تستخدم الاتنين مع بعض.
وده هو مفهوم Hybrid Caching.
الفكرة بتكون على مستويين:
L1 → In-Memory Cache
L2 → Distributed Cache مثل Redis
الـ Flow بيكون تقريبًا:
Request
↓
L1 - RAM
↓ إذا مش موجود
L2 - Redis
↓ إذا مش موجود
Database
لو البيانات موجودة في L1، ناخدها بأسرع شكل ممكن.
لو مش موجودة، نروح لـ L2.
ولو مش موجودة في Redis كمان، نرجع للـ Database.
بعدها يتم تخزين النتيجة في مستويات الـ Cache المناسبة.
مثلًا:
وتسجيل الخدمات:
وبعدها تقدر تستخدم
الميزة هنا إنك بتتعامل مع API واحدة بدل ما تدير منطق L1 وL2 بنفسك.
كمان HybridCache يوفر حماية من مشكلة مهمة اسمها Cache Stampede، بحيث الطلبات المتزامنة على نفس البيانات لا تضرب الـ Database كلها في نفس الوقت عند انتهاء الـ Cache.
"أخزن البيانات فين؟"
المشكلة الأصعب هي:
إمتى أمسح الـ Cache أو أعتبر البيانات الموجودة فيه قديمة؟
تخيل إن عندك Product سعره 500 جنيه.
الـ Cache حفظ السعر:
وبعدها السعر اتغير في الـ Database إلى:
لو الـ Cache لسه شايل القيمة القديمة، المستخدم ممكن يشوف
وده اسمه Stale Data.
مثلًا:
هنا عندك نوعين من الـ Expiration:
مثلًا:
الـ Request التالي هيعمل Cache Miss ويجيب النسخة الجديدة من الـ Database.
يعني بدل ما نخزن Product أو Object، ممكن نكاش الـ Response نفسه، مثل JSON اللي الـ API بترجعه.
يعني السيرفر يحدد للـ Client أو الـ Proxy هل الـ Response ممكن يتخزن وإزاي يتم استخدامه.
مثال:
لكن مهم تفهم إن Response Caching مش مجرد "Cache داخل السيرفر".
هو جزء من HTTP Caching، والـ Client والـ Proxy لهم دور في تحديد سلوك الـ Cache بناءً على الـ HTTP Headers.
⚠️ لذلك مش دايمًا هو الاختيار الأفضل لو هدفك الأساسي هو التحكم الكامل في Load على الـ Origin Server.
تقدر تفعله كالتالي:
وبعدها تحدد Endpoint عايز تخزن الـ Response الخاص به:
الميزة المهمة هنا إنك بتتحكم في سياسة الـ Cache من السيرفر، بدل ما يكون اعتمادك الأساسي على سلوك الـ HTTP Client والـ Cache-Control Headers.
استخدم In-Memory Cache لما:
هو قرار معماري لازم تحدد فيه:
إيه اللي هيتكاش؟
فين هيتخزن؟
لمدة قد إيه؟
وإمتى البيانات تعتبر قديمة؟
ابدأ بأبسط حل يناسب الـ Architecture بتاعتك.
لو عندك Application بسيط،
لو عندك أكثر من Server، Distributed Cache مثل Redis غالبًا يكون أنسب.
ولو محتاج تجمع سرعة الـ RAM مع مشاركة Redis، فـ
ولو هدفك هو تخزين الـ HTTP Responses نفسها، فرّق بين
📚 ولو عايز تشوف الموضوع بشكل عملي أكتر، مع Architecture Diagrams وأمثلة كاملة على C# وRedis وHybridCache وCache Invalidation، تقدر تكمل في الدليل العملي:
https://mohamed-ehab-mohy.github.io/caching-guide/
Cache Miss يعني البيانات مش موجودة، وبالتالي التطبيق محتاج يرجع للمصدر الأصلي مثل الـ Database.
لو مفيش حماية، ممكن كل الطلبات تضرب الـ Database مع بعض وتعمل ضغط كبير. بعض حلول الـ Caching الحديثة، ومنها
Client ⇒ API ⇒ Database
لكن مع زيادة الـ Traffic، المشكلة بتظهر بسرعة: نفس البيانات ممكن تتطلب آلاف المرات في الثانية، ومع كل Request بنعمل Query جديدة على الـ Database.
النتيجة؟
- ⏱️ زيادة الـ Latency.
- 🗄️ ضغط أكبر على الـ Database.
- 📈 استهلاك أعلى للموارد.
- ⚠️ وفي بعض الحالات، الـ Database نفسها تبقى هي الـ Bottleneck.
الفكرة ببساطة إنك بدل ما تسأل الـ Database عن نفس البيانات كل مرة، تحتفظ بنسخة منها في مكان أسرع، وتستخدم النسخة دي للطلبات المتكررة.
لكن اختيار نوع الـ Cache مش مجرد إنك "تحط Redis وخلاص". هو قرار معماري له علاقة بعدد الـ Servers، سرعة الوصول للبيانات، حجمها، ومدى حساسيتك للـ Stale Data.
⚡ يعني إيه Caching؟
الـ Caching هو الاحتفاظ بنسخة مؤقتة من البيانات في مكان أسرع من المصدر الأصلي.مثلًا، لو عندك Query بتجيب قائمة المنتجات وتاخد وقت ملحوظ، ومئات المستخدمين بيطلبوا نفس القائمة، مش منطقي إن كل Request يروح للـ Database من جديد.
بدل كده:
Client ⇒ API ⇒ Cache ⇒ Database
لو البيانات موجودة في الـ Cache، الـ API يرجعها مباشرة.
ولو مش موجودة، نروح للـ Database، وبعدها نخزن النتيجة في الـ Cache عشان الطلبات التالية تستفيد منها.
💡 الهدف الأساسي هو تقليل عدد الـ Database Queries وتحسين زمن الاستجابة.
🧠 أنواع الـ Caching الأساسية
في تطبيقات .NET، تقدر تتعامل مع أكثر من مستوى للـ Cache، وأشهرهم:- In-Memory Cache - البيانات داخل RAM الخاصة بالـ Application.
- Distributed Cache - Cache مركزي مثل Redis.
- Hybrid Cache - يجمع بين RAM وRedis.
- Response Caching - يعتمد على HTTP Caching والـ Headers.
- Output Caching - تخزين الـ Response على مستوى السيرفر مع تحكم أكبر.
⚡ In-Memory Cache - أسرع وأبسط اختيار
الـ In-Memory Cache بيخزن البيانات مباشرة في RAM الخاصة بالـ Application.في .NET، تقدر تستخدم
IMemoryCache.الميزة الأساسية هنا إن البيانات قريبة جدًا من الكود اللي بيستخدمها، وبالتالي مفيش Network Request لـ Cache Server خارجي.
✅ مميزات In-Memory Cache
- ⚡ سريع جدًا.
- 🧩 سهل في الـ Setup.
- 💰 مش محتاج Server إضافي.
- 📦 مناسب جدًا للتطبيقات اللي عندها Instance واحدة.
❌ أهم المشاكل
- البيانات بتختفي لو الـ Application اتعمل له Restart.
- كل Server عنده Cache خاص بيه.
- ممكن يحصل Data Inconsistency لو عندك أكتر من Server.
- بيستهلك جزء من RAM الخاصة بالـ Application.
Server 1 → Cache A
Server 2 → Cache B
Server 3 → Cache C
لو Server 1 عنده بيانات محدثة، وServer 2 عنده نسخة قديمة، ممكن نفس المستخدم ياخد نتائج مختلفة حسب الـ Request راح لأنهي Server.
وده من أهم أسباب استخدام Distributed Cache في الأنظمة اللي بتعمل Scale-out.
مثال بسيط باستخدام IMemoryCache
لتفعيل الـ In-Memory Cache في تطبيق .NET: C#:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache();
var app = builder.Build();
app.Run(); IMemoryCache داخل الـ Service: C#:
public class ProductService
{
private readonly IMemoryCache _memoryCache;
private readonly AppDbContext _db;
public ProductService(
IMemoryCache memoryCache,
AppDbContext db)
{
_memoryCache = memoryCache;
_db = db;
}
public async Task<Product?> GetProductAsync(int id)
{
string cacheKey = $"product-{id}";
return await _memoryCache.GetOrCreateAsync(
cacheKey,
async entry =>
{
entry.AbsoluteExpirationRelativeToNow =
TimeSpan.FromMinutes(5);
return await _db.Products.FindAsync(id);
});
}
} 🔍 الأول نشوف البيانات موجودة في الـ Cache ولا لأ.
لو موجودة ⬅️ Cache Hit.
لو مش موجودة ⬅️ Cache Miss، وساعتها نجيب البيانات من الـ Database ونخزنها في الـ Cache.
🔴 Distributed Cache - ليه Redis؟
لما يكون عندك أكتر من Application Instance، الـ In-Memory Cache ممكن يعمل مشكلة لأن كل Instance عندها نسخة منفصلة.هنا بييجي دور الـ Distributed Cache.
وأشهر اختيار في الحالة دي هو Redis.
بدل ما كل Application Instance يكون عندها Cache منفصل:
Code:
App 1 ─┐
App 2 ─┼──→ Redis
App 3 ─┘ وده بيخلي البيانات الموجودة في الـ Cache مشتركة بين الـ Servers.
🐳 تشغيل Redis باستخدام Docker
لو عايز تجرب Redis محليًا، تقدر تشغله باستخدام Docker: Bash:
docker run -d -p 6379:6379 --name redis-cache redis:alpine Bash:
docker ps Bash:
dotnet add package Microsoft.Extensions.Caching.StackExchangeRedis C#:
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
options.InstanceName = "MyApp_";
}); ⚠️ Redis مش أسرع من In-Memory في كل حالة
دي نقطة مهمة.الـ In-Memory Cache موجود داخل نفس الـ Process، بينما Redis غالبًا يحتاج Network Communication.
يعني عند استخدام Redis عندك تكلفة إضافية مثل:
- 🌐 Network Round Trip.
- 🔄 Serialization / Deserialization.
- 🖥️ موارد Redis نفسه.
وده غالبًا أهم من فرق السرعة البسيط لما يكون النظام محتاج Scale-out.
⚖️ In-Memory Cache vs Redis
| المقارنة | In-Memory | Redis |
|---|---|---|
| مكان التخزين | RAM داخل الـ Application | Redis Server |
| السرعة | ⚡ الأعلى عادةً | سريع جدًا لكن فيه Network Hop |
| مشاركة البيانات | ❌ بين الـ Instances | ✅ مشتركة |
| Restart للـ Application | البيانات تختفي | Redis مستقل عن الـ App |
| Serialization | أقل حاجة | غالبًا مطلوب |
| مناسب لـ | Instance واحدة وبيانات مؤقتة | عدة Instances وDistributed Systems |
| التعقيد | بسيط | أعلى قليلًا |
لو عندك Server واحد والبيانات مناسبة للتخزين المحلي، In-Memory ممكن يكون كفاية جدًا.
لكن لو عندك أكثر من Instance وعايز Cache مشترك، Redis بيكون اختيار أقوى.
🚀 Hybrid Cache - L1 + L2
طيب، ليه نختار بين RAM وRedis؟ممكن تستخدم الاتنين مع بعض.
وده هو مفهوم Hybrid Caching.
الفكرة بتكون على مستويين:
L1 → In-Memory Cache
L2 → Distributed Cache مثل Redis
الـ Flow بيكون تقريبًا:
Request
↓
L1 - RAM
↓ إذا مش موجود
L2 - Redis
↓ إذا مش موجود
Database
لو البيانات موجودة في L1، ناخدها بأسرع شكل ممكن.
لو مش موجودة، نروح لـ L2.
ولو مش موجودة في Redis كمان، نرجع للـ Database.
بعدها يتم تخزين النتيجة في مستويات الـ Cache المناسبة.
💡 HybridCache في .NET
في الإصدارات الحديثة من .NET، تقدر تستخدمHybridCache لتوحيد التعامل مع الـ Local Cache والـ Distributed Cache.مثلًا:
Bash:
dotnet add package Microsoft.Extensions.Caching.Hybrid C#:
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = "localhost:6379";
});
builder.Services.AddHybridCache(); HybridCache داخل الـ Service: C#:
public class ProductService
{
private readonly HybridCache _hybridCache;
private readonly AppDbContext _db;
public ProductService(
HybridCache hybridCache,
AppDbContext db)
{
_hybridCache = hybridCache;
_db = db;
}
public async Task<Product?> GetProductAsync(int id)
{
string cacheKey = $"product-{id}";
return await _hybridCache.GetOrCreateAsync(
cacheKey,
async cancelToken =>
await _db.Products.FindAsync(id, cancelToken),
new HybridCacheEntryOptions
{
Expiration = TimeSpan.FromMinutes(5),
LocalCacheExpiration = TimeSpan.FromMinutes(1)
});
}
} كمان HybridCache يوفر حماية من مشكلة مهمة اسمها Cache Stampede، بحيث الطلبات المتزامنة على نفس البيانات لا تضرب الـ Database كلها في نفس الوقت عند انتهاء الـ Cache.
🗑️ Cache Invalidation - أصعب جزء
المشكلة مش بس:"أخزن البيانات فين؟"
المشكلة الأصعب هي:
إمتى أمسح الـ Cache أو أعتبر البيانات الموجودة فيه قديمة؟
تخيل إن عندك Product سعره 500 جنيه.
الـ Cache حفظ السعر:
500وبعدها السعر اتغير في الـ Database إلى:
600لو الـ Cache لسه شايل القيمة القديمة، المستخدم ممكن يشوف
500.وده اسمه Stale Data.
⏱️ TTL
واحدة من أبسط الطرق هي تحديد مدة صلاحية للـ Cache.مثلًا:
C#:
var cacheOptions = new MemoryCacheEntryOptions
{
SlidingExpiration = TimeSpan.FromMinutes(2),
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10)
};
_memoryCache.Set(
cacheKey,
product,
cacheOptions); SlidingExpiration- مدة تتجدد مع الاستخدام.AbsoluteExpiration- حد أقصى للعمر مهما حصل.
🗑️ Manual Invalidation
في بعض الحالات، الأفضل إنك تمسح الـ Cache مباشرة بعد تحديث البيانات.مثلًا:
C#:
public async Task UpdateProductAsync(Product product)
{
_db.Products.Update(product);
await _db.SaveChangesAsync();
string cacheKey = $"product-{product.Id}";
_memoryCache.Remove(cacheKey);
} 🌐 Response Caching vs Output Caching
فيه فرق مهم بين Caching للبيانات وبين Caching للـ HTTP Response.يعني بدل ما نخزن Product أو Object، ممكن نكاش الـ Response نفسه، مثل JSON اللي الـ API بترجعه.
Response Caching
الـ Response Caching بيعتمد على HTTP Headers زيCache-Control.يعني السيرفر يحدد للـ Client أو الـ Proxy هل الـ Response ممكن يتخزن وإزاي يتم استخدامه.
مثال:
C#:
[ResponseCache(
Duration = 120,
Location = ResponseCacheLocation.Any)]
public IActionResult GetProducts()
{
return Ok(new
{
Data = "Products"
});
} هو جزء من HTTP Caching، والـ Client والـ Proxy لهم دور في تحديد سلوك الـ Cache بناءً على الـ HTTP Headers.
⚠️ لذلك مش دايمًا هو الاختيار الأفضل لو هدفك الأساسي هو التحكم الكامل في Load على الـ Origin Server.
🚀 Output Caching
الـ Output Caching متاح في ASP.NET Core من .NET 7، وبيوفر تحكم Server-side أكبر في الـ Responses اللي يتم تخزينها.تقدر تفعله كالتالي:
C#:
builder.Services.AddOutputCache();
var app = builder.Build();
app.UseOutputCache(); C#:
[OutputCache(Duration = 600)]
public IActionResult GetProducts()
{
return Ok(_db.Products.ToList());
} 📊 مقارنة سريعة بين أنواع الـ Cache
| النوع | مكان التخزين | أفضل استخدام | أهم ميزة | أهم تكلفة |
|---|---|---|---|---|
| In-Memory | RAM داخل الـ App | Instance واحدة | ⚡ سرعة عالية جدًا | لا يشارك البيانات بين الـ Servers |
| Redis | Server خارجي | عدة Instances | 🔄 Cache مشترك | Network + Serialization |
| HybridCache | L1 RAM + L2 Redis | أنظمة تحتاج الاثنين | 🚀 سرعة L1 مع Distributed Cache | تعقيد أعلى نسبيًا |
| Response Caching | Client / Proxy وفق HTTP Caching | Responses قابلة للكاش | 🌐 يقلل Requests عبر الشبكة | تحكم محدود في بعض السيناريوهات |
| Output Caching | Server-side | Responses تحتاج تحكم | 🎯 تحكم أكبر في سياسة الكاش | يستهلك موارد تخزين على السيرفر |
🎯 طيب أختار أنهي نوع؟
مفيش نوع واحد مناسب لكل Applications.استخدم In-Memory Cache لما:
- عندك Instance واحدة أو السيناريو يسمح بده.
- محتاج أقل Latency ممكن.
- البيانات مؤقتة ومش مشكلة لو اختفت بعد Restart.
- عندك أكثر من Application Instance.
- محتاج Cache مشترك بين الـ Servers.
- الـ Application جزء من Distributed Architecture.
- محتاج سرعة Local Cache.
- وفي نفس الوقت محتاج Distributed Cache.
- عندك سيناريو مناسب لمستويين من الـ Caching.
- عايز تكاش الـ HTTP Response نفسه.
- وعايز تتحكم في سياسة الكاش من السيرفر.
🧩 الخلاصة
الـ Caching مش مجرد إضافة Redis للمشروع.هو قرار معماري لازم تحدد فيه:
إيه اللي هيتكاش؟
فين هيتخزن؟
لمدة قد إيه؟
وإمتى البيانات تعتبر قديمة؟
ابدأ بأبسط حل يناسب الـ Architecture بتاعتك.
لو عندك Application بسيط،
IMemoryCache ممكن يكون كفاية.لو عندك أكثر من Server، Distributed Cache مثل Redis غالبًا يكون أنسب.
ولو محتاج تجمع سرعة الـ RAM مع مشاركة Redis، فـ
HybridCache بيديك نموذج L1 + L2 مناسب جدًا للسيناريو ده.ولو هدفك هو تخزين الـ HTTP Responses نفسها، فرّق بين
Response Caching وOutput Caching قبل ما تختار.📚 ولو عايز تشوف الموضوع بشكل عملي أكتر، مع Architecture Diagrams وأمثلة كاملة على C# وRedis وHybridCache وCache Invalidation، تقدر تكمل في الدليل العملي:
https://mohamed-ehab-mohy.github.io/caching-guide/
الأسئلة الشائعة
--------------هل Caching يلغي الحاجة للـ Database؟
لا. الـ Cache عادةً بيكون طبقة سريعة أمام الـ Database، لكنه لا يستبدلها. لو البيانات مش موجودة أو انتهت صلاحيتها، التطبيق بيرجع للمصدر الأساسي.هل Redis أسرع من In-Memory Cache؟
عادةً لا. الـ In-Memory أقرب للـ Application ومفيش Network Hop، لذلك بيكون أسرع في الوصول المباشر. لكن Redis يتفوق في مشاركة الـ Cache بين عدة Application Instances.هل لازم أستخدم Redis في كل مشروع؟
لا. لو المشروع بسيط أو عندك Instance واحدة، ممكنIMemoryCache يكون كافي جدًا. استخدام Redis بدون حاجة فعلية بيضيف تعقيد وتكلفة تشغيلية.إيه الفرق بين Cache Hit وCache Miss؟
Cache Hit يعني البيانات موجودة في الـ Cache وتقدر تستخدمها مباشرة.Cache Miss يعني البيانات مش موجودة، وبالتالي التطبيق محتاج يرجع للمصدر الأصلي مثل الـ Database.
إيه مشكلة Cache Stampede؟
بتحصل لما Cache Entry تنتهي صلاحيتها، وفي نفس اللحظة عدد كبير من الـ Requests يحاولوا يجيبوا نفس البيانات من الـ Database.لو مفيش حماية، ممكن كل الطلبات تضرب الـ Database مع بعض وتعمل ضغط كبير. بعض حلول الـ Caching الحديثة، ومنها
HybridCache، بتوفر آليات لتقليل المشكلة دي. التعديل الأخير: