Redis كمخزن مؤقت للتطبيقات: كيف تقلل الحمل على قاعدة البيانات باستخدام Django و FastAPI

Redis كمخزن مؤقت للتطبيقات: كيف تقلل الحمل على قاعدة البيانات باستخدام Django و FastAPI

استخدام Redis كمخزن مؤقت (Cache) أصبح من أهم الأدوات لتحسين أداء تطبيقات الويب الحديثة، خصوصًا مع أطر مثل Django وFastAPI. في هذا المقال سنشرح كيف تستخدم Redis Django FastAPI Cache لتقليل الحمل على قاعدة البيانات، تسريع الاستجابة، وإدارة الجلسات والكيوز بشكل فعّال، مع مقارنات مباشرة مع التخزين في الذاكرة فقط.

إذا كنت مهتمًا بالأداء والمهام الخلفية، قد يفيدك أيضًا مقالنا عن التعامل مع Background Tasks في Django وFastAPI، وكذلك مقال البرمجة غير المتزامنة في بايثون: تحسين الأداء باستخدام async و await.

ما هو Redis ولماذا هو مناسب كـ Cache؟

Redis هو مخزن بيانات في الذاكرة (In-memory Data Store) يدعم هياكل بيانات مختلفة مثل: Strings, Lists, Hashes, Sets. يمكن استخدامه كـ:

  • Cache لتخزين البيانات مؤقتًا لتسريع القراءة.
  • Session Store لتخزين جلسات المستخدمين.
  • Message Broker / Queue لتنفيذ المهام الخلفية.
  • Store بسيط لبعض أنواع البيانات التي تحتاج سرعة عالية.

بما أنه يعمل في الذاكرة، فهو أسرع بكثير من قواعد البيانات التقليدية مثل PostgreSQL أو MySQL، لكن يمكن ضبطه ليكتب على القرص لضمان عدم ضياع البيانات حسب الحاجة.

لماذا نحتاج Redis بجانب Django و FastAPI؟

أطر مثل Django و FastAPI تستطيع العمل بدون Redis، لكنها ستعتمد على:

  • قاعدة البيانات الرئيسية لكل عمليات القراءة والكتابة.
  • الذاكرة الداخلية (In-process Memory) للكاش المؤقت داخل عملية السيرفر فقط.

هذا يسبب عدة مشاكل:

  1. حمل زائد على قاعدة البيانات: كل طلب غالبًا ينفذ استعلامات متكررة مهما كانت النتيجة ثابتة نسبيًا.
  2. عدم مشاركة الكاش بين أكثر من سيرفر: إذا كنت تشغل أكثر من عملية (Workers) أو أكثر من سيرفر خلف Load Balancer، فإن الكاش في ذاكرة العملية لن يكون مشتركًا.
  3. ضياع الكاش بإعادة تشغيل التطبيق: أي كاش مخزّن في الذاكرة يختفي مع إعادة تشغيل السيرفر.

باستخدام Redis Django FastAPI Cache يمكنك تخزين الكاش في مكان مركزي، سريع، مشترك بين كل عمليات التطبيق، ويمكن التحكم في انتهاء صلاحيته (TTL).

استخدام Redis مع Django: الإعدادات الأساسية

1. تثبيت المكتبات المطلوبة

لربط Django مع Redis كـ Cache Backend:

pip install redis django-redis

2. إعداد Redis كـ Cache Backend في Django

في ملف settings.py:

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}

هنا نستخدم قاعدة بيانات Redis رقم 1 (/1) للكاش. يمكنك استخدام قواعد مختلفة لأغراض مختلفة (مثل الجلسات أو الكيوز).

3. مثال لتخزين نتيجة استعلام قاعدة بيانات في Redis

افترض أن لدينا موديل Article ونريد تخزين قائمة المقالات الأكثر قراءة:

from django.core.cache import cache
from django.shortcuts import render
from .models import Article

def popular_articles(request):
    cache_key = "popular_articles"
    articles = cache.get(cache_key)

    if articles is None:
        # استعلام ثقيل أو متكرر
        articles = Article.objects.order_by("-views")[:10]
        # تخزين النتيجة في Redis لمدة 5 دقائق (300 ثانية)
        cache.set(cache_key, articles, timeout=300)

    return render(request, "articles/popular.html", {"articles": articles})

بهذه الطريقة:

  • أول طلب سيضرب قاعدة البيانات.
  • الطلبات التالية خلال 5 دقائق ستستخدم الكاش من Redis، مما يقلل الحمل على قاعدة البيانات بشكل كبير.

4. كاش الصفحات كاملة في Django باستخدام Redis

يمكنك استخدام Decorator cache_page مع Redis Backend:

from django.views.decorators.cache import cache_page
from django.urls import path
from .views import popular_articles

urlpatterns = [
    path("popular/", cache_page(60 * 5)(popular_articles)),  # كاش لمدة 5 دقائق
]

الآن نتيجة الـ View كاملة يتم تخزينها في Redis كـ صفحة HTML جاهزة، وDjango لن ينفذ الكود الداخلي إلا بعد انتهاء مدة الكاش.

5. استخدام Redis لإدارة الجلسات في Django

بدل تخزين الجلسات في قاعدة البيانات، يمكنك استخدام Redis لتسريعها:

SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default"

بهذا الشكل، جلسات المستخدمين تخزن في Redis بدلًا من جدول في قاعدة البيانات، مما يقلل القراءة والكتابة من وإلى الـ DB.

استخدام Redis مع FastAPI: كاش و Queue

1. تثبيت المكتبات

pip install redis fastapi uvicorn

في المشاريع غير المتزامنة (async)، يفضّل استخدام عميل Redis غير متزامن مثل redis>=4 مع Async API أو مكتبات مثل aioredis (إذا كنت تستخدم إصدارات أقدم).

2. إنشاء اتصال Redis في FastAPI

from fastapi import FastAPI, Depends
import redis.asyncio as redis

app = FastAPI()

async def get_redis():
    r = redis.Redis(host="localhost", port=6379, db=1)
    try:
        yield r
    finally:
        await r.close()

@app.on_event("startup")
async def startup_event():
    # يمكن إضافة أي تهيئة هنا لو احتجت
    pass

3. مثال: تخزين نتائج استعلام مكلف في Redis

افترض أن لديك Endpoint يعيد بيانات إحصائية تعتمد على قاعدة البيانات أو خدمة خارجية:

from fastapi import HTTPException
from datetime import timedelta
import json

CACHE_EXPIRE_SECONDS = 300  # خمس دقائق

@app.get("/stats")
async def get_stats(r: redis.Redis = Depends(get_redis)):
    cache_key = "stats:v1"

    cached = await r.get(cache_key)
    if cached:
        # البيانات مخزنة كسلسلة JSON
        return json.loads(cached)

    # هنا مكان استعلام ثقيل أو اتصال بخدمة خارجية
    # مثال مبسط:
    stats_data = {
        "users": 1200,
        "active": 200,
        "orders": 50,
    }

    await r.setex(cache_key, CACHE_EXPIRE_SECONDS, json.dumps(stats_data))
    return stats_data

النتيجة: أول طلب يحسب الإحصائيات، والطلبات التالية خلال 5 دقائق تقرأ مباشرة من Redis، مما يقلل الحمل على قاعدة البيانات أو الخدمات الخارجية.

4. استخدام Redis كـ Queue بسيطة في FastAPI

من الاستخدامات الشائعة لـ Redis مع FastAPI: بناء Queue بسيطة للمهام الخلفية.

@app.post("/send-email")
async def send_email_task(email: str, r: redis.Redis = Depends(get_redis)):
    task = {"type": "send_email", "email": email}
    await r.lpush("tasks_queue", json.dumps(task))
    return {"detail": "task_queued"}

ثم Worker منفصل (اسكربت بايثون) يقوم بسحب المهام من الكيو:

import asyncio
import redis.asyncio as redis
import json

async def worker():
    r = redis.Redis(host="localhost", port=6379, db=2)
    while True:
        task_data = await r.brpop("tasks_queue", timeout=0)
        _, task_json = task_data
        task = json.loads(task_json)
        if task["type"] == "send_email":
            print(f"Sending email to {task['email']}")
            # تنفيذ الارسال الفعلي...
        await asyncio.sleep(0)

if __name__ == "__main__":
    asyncio.run(worker())

بهذا الأسلوب، يمكن لتطبيق FastAPI معالجة الطلبات بسرعة (فقط يدفع المهمة للكيو)، بينما يقوم الـ Worker بتنفيذ المهام الثقيلة في الخلفية.

مقارنة: Redis Cache مقابل الذاكرة الداخلية (In-Memory)

1. الكاش داخل الذاكرة (In-Process Cache)

بعض المطورين يستخدمون متغيرات Global أو Dict داخل الكود لتخزين الكاش:

cache_local = {}

def get_data():
    if "key" in cache_local:
        return cache_local["key"]
    data = heavy_db_query()
    cache_local["key"] = data
    return data

هذه الطريقة لها عيوب واضحة:

  • الكاش لا يُشارك بين أكثر من Process (مثل Gunicorn workers) أو أكثر من سيرفر.
  • يُفقد الكاش مع كل Restart.
  • لا توجد إدارة جيدة لحجم الكاش أو سياسة الحذف (Eviction Policy).

2. مزايا Redis كـ Cache مركزي

  • مشترك بين كل العمليات والسيرفرات: أي Worker أو خدمة تستخدم نفس Redis يمكنها قراءة نفس الكاش.
  • سياسات إزالة (Eviction): Redis يدعم سياسات مثل LRU، LFU، وغيرها عند امتلاء الذاكرة.
  • دعم TTL: يمكنك ضبط وقت انتهاء لكل مفتاح بسهولة.
  • مرونة في الهياكل: يمكن تخزين هياكل بيانات معقدة، ليس فقط Strings.
  • يمكن تشغيله في Cluster أو Replica لضمان الاعتمادية وقابلية التوسع.

أفضل الممارسات لاستخدام Redis مع Django و FastAPI

1. اختيار ما يجب تخزينه في الكاش

  • النتائج المتكررة التي لا تتغير كثيرًا (مثل إحصائيات، قوائم Top، صفحات عامة).
  • نتائج الاستعلامات الثقيلة أو ذات الـ JOINs الكثيرة.
  • الاستجابات التي يمكن قبول تأخر تحديثها لبضع ثواني أو دقائق.

تجنب تخزين بيانات تتغير باستمرار أو ذات طبيعة حساسة جدًا تحتاج إلى تحديث لحظي إلا إذا كان الكاش قصير المدى جدًا.

2. تصميم المفاتيح (Cache Keys) بعناية

  • استخدم أسماء واضحة مثل: user:<id>:profile، article:list:popular.
  • إذا كان المحتوى يعتمد على اللغة أو الباراميتر، ضمّنها في المفتاح: home_page:lang:ar.
  • احرص على أن تكون المفاتيح قصيرة قدر الإمكان لتقليل استهلاك الذاكرة.

3. تحديد مدة صلاحية مناسبة (TTL)

  • صفحات عامة قد تتحمل كاش من 1–10 دقائق.
  • بيانات شبه لحظية (مثل أسعار، إحصائيات Dashboards) يمكن تخزينها من 5–60 ثانية حسب الحاجة.
  • جلسات المستخدمين غالبًا بين 30 دقيقة إلى عدة ساعات.

4. مراقبة Redis وقياس الأداء

  • تابع استخدام الذاكرة (Memory Usage) واحذر من استهلاكها بالكامل.
  • استخدم أوامر مثل INFO وMONITOR أو أدوات الـ Dashboard.
  • اختبر نسبة Cache Hit مقابل Cache Miss لتقييم فاعلية الكاش.

مثال عملي يجمع Django و FastAPI مع Redis

سيناريو شائع:

  • تطبيق Django مسؤول عن لوحة التحكم والـ APIs التقليدية.
  • خدمة FastAPI صغيرة مسؤولة عن خدمات عالية الأداء أو WebSocket.
  • Redis يعمل كوسيط مشترك للكاش والجلسات والكيوز.

في هذه الحالة:

  1. يستخدم Django Redis كـ Cache Backend وSession Store.
  2. تقوم خدمة FastAPI بقراءة نفس بعض البيانات من Redis لتسريع الـ Endpoints.
  3. كلاهما يرسل مهام إلى Queue في Redis، وWorkers مشتركة تسحب المهام وتنفذها.

هذا التصميم يقلل بشكل كبير من الحمل على قاعدة البيانات الرئيسية، ويجعل التطبيق قابلاً للتوسع (Scale Out) عن طريق إضافة مزيد من الـ Workers والسيرفرات بدون تعقيدات كبيرة.

خلاصة

استخدام Redis Django FastAPI Cache خطوة أساسية لأي تطبيق يسعى إلى أداء عالي وقابلية للتوسع. Redis ليس فقط مخزن كاش، بل منصة متكاملة:

  • تخزين نتائج الاستعلامات وصفحات HTML الجاهزة.
  • إدارة الجلسات في Django بسرعة أعلى من قواعد البيانات التقليدية.
  • تطبيق Queue بسيطة للمهام الخلفية مع FastAPI أو Django.
  • مشاركة الكاش بين عدة خدمات وسيرفرات بسهولة.

إذا كنت تعمل على API كبير باستخدام Django REST، يمكنك الاستفادة من الكاش كما شرحنا هنا بالتوازي مع ما تعلمته في دليل شامل لإطار Django REST مع أمثلة، لتصل لأقصى أداء ممكن مع تقليل الحمل على قاعدة البيانات.

ابدأ بخطوات بسيطة: كاش لاستعلامات ثقيلة أو صفحات عامة، ثم تدريجيًا وسّع استخدام Redis للجلسات والكيوز. مع الوقت، ستلاحظ فرقًا واضحًا في استجابة التطبيق واستهلاك موارد السيرفر.

حول المحتوى:

شرح استخدام Redis لتخزين النتائج، صفحات الكاش، إدارة الجلسات، والكيوز، مع أمثلة مباشرة ومقارنة مع الذاكرة الداخلية.

هل كان هذا مفيدًا لك؟

أضف تعليقك