انواع کش کردن در Django مبتنی بر وبسایت
کش کردن یکی از مهم ترین تکنیک ها برای بهبود عملکرد و سرعت برنامه های وب است. در این مقاله، تمام روش های کش کردن در جنگو را به صورت کامل بررسی می کنیم.
۱. پیکربندی بک اند کش
اولین قدم برای استفاده از کش، انتخاب و پیکربندی بک اند مناسب است:
# settings.py
# استفاده از دیتابیس هایی مثل redis:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.redis.RedisCache',
'LOCATION': 'redis://127.0.0.1:6379/1',
'OPTIONS': {
'CLIENT_CLASS': 'django_redis.client.DefaultClient',
}
}
}
# یا برای توسعه محلی و داخل رم:
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'LOCATION': 'unique-snowflake',
}
}
بک اندهای مختلف کش در جنگو:
- Memcached: سریع ترین گزینه برای production
- Redis: محبوب ترین و انعطاف پذیر
- Local Memory: فقط برای توسعه محلی
- Database: کش کردن در دیتابیس
- Dummy: غیرفعال کردن کش
۲. کش کردن کل سایت (Per-Site Caching)
این ساده ترین روش است که کل صفحات سایت را کش می کند و به طور کلی تمام صفحات را در نظر میگیرد که در حالت عادی برای سایت هایی به کار برده می شود که بیشتر مطالب ایستا هستن و بخش هایی که دارای query هستند نیز به صفحات اصلی سایت بر میگردند و بقیه موارد از طریق ادمین جنگو مدیریت خواهند شد (به طور کلی این مدل کش کردن توصیه نمی شود):
# settings.py
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware', # اول
'django.middleware.common.CommonMiddleware',
'django.middleware.cache.FetchFromCacheMiddleware', # آخر
]
CACHE_MIDDLEWARE_SECONDS = 60 * 15 # ۱۵ دقیقه
CACHE_MIDDLEWARE_KEY_PREFIX = 'myblog'
مزایا:
- پیاده سازی بسیار ساده
- نیاز به تغییر کد ندارد
معایب:
- همه صفحات کش می شوند
- کاربران لاگین شده هم کش می شوند
- کنترل کمی روی محتوای کش شده
۳. کش کردن هر View (Per-View Caching)
این محبوب ترین روش برای کش کردن صفحات خاص است.
۳.۱ در تابعی (FBV)
from django.views.decorators.cache import cache_page
@cache_page(60 * 15) # ۱۵ دقیقه
def post_list(request):
posts = Post.objects.all()
return render(request, 'blog/list.html', {'posts': posts})
۳.۲ در کلاسی (CBV)
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
@method_decorator(cache_page(60 * 15), name='dispatch')
class PostListView(ListView):
model = Post
template_name = 'blog/list.html'
۳.۳ در urls.py
from django.views.decorators.cache import cache_page
from .views import PostListView
urlpatterns = [
path('blog/', cache_page(60 * 15)(PostListView.as_view())),
]
۴. کش کردن با Vary Headers
برای تفکیک کش کاربران لاگین شده و مهمان:
from django.views.decorators.cache import cache_page, vary_on_cookie
from django.views.decorators.vary import vary_on_headers
# کش جدا برای هر کاربر
@cache_page(60 * 15)
@vary_on_cookie
def my_view(request):
...
# یا در CBV
@method_decorator(vary_on_cookie, name='dispatch')
@method_decorator(cache_page(60 * 15), name='dispatch')
class PostListView(ListView):
...
با این روش، هر کاربر کش جداگانه ای دارد و محتوای شخصی سازی شده نمایش داده می شود. به شکلی که ممکن است بعضی کاربران اطلاعاتی مرتبط با خود داشته باشند مثل نام و دسترسی و تمام آیتم هایی که خریداری و یا فعال شده باشند.
۵. کش کردن بخش های قالب (Template Fragment)
می توانید فقط بخش هایی از قالب را کش کنید، در مواردی که طراحی به صورت componenet و با template tag ها صورت میگیرد این مورد می تواند چه در تابع و چه در template پیاده سازی شود:
{% load cache %}
<!-- کش برای ۱۵ دقیقه (۹۰۰ ثانیه) -->
{% cache 900 sidebar_tags %}
<div class="sidebar">
{% for tag in popular_tags %}
<span class="badge">{{ tag.name }}</span>
{% endfor %}
</div>
{% endcache %}
<!-- کش جدا برای هر کاربر -->
{% cache 900 user_dashboard user.id %}
<div class="user-info">
<h3>خوش آمدید، {{ user.username }}</h3>
</div>
{% endcache %}
۶. کش کردن سطح پایین (Low-Level Cache API)
کنترل کامل روی کش با استفاده از API سطح پایین، این مدل ساده ترین و سر راست ترین مدل کش کردن به زبان پایتون در فریمورک جنگو است که می توانید هر جایی که لازم هست از آن بهره ببرید:
from django.core.cache import cache
# تنظیم کش
cache.set('my_key', 'my_value', 60 * 15) # ۱۵ دقیقه
# دریافت کش
value = cache.get('my_key')
# دریافت یا تنظیم (اگر وجود نداشت)
value = cache.get_or_set('my_key', expensive_function, 60 * 15)
# چند کلید
result = cache.get_many(['key1', 'key2'])
cache.set_many({'key1': 'val1', 'key2': 'val2'}, 60 * 15)
# حذف
cache.delete('my_key')
cache.clear() # پاک کردن همه
۷. Cached Property
این مدل کش کردن در لایه مدل اتفاق می افتاد به طوری که گاهی اوقات نیاز است که محاسباتی در این لایه انجام شود و به مراتب ممکن است زمان بر و یا کوئری هایی را شامل شود که تحت این شرایط می توان به شکل زیر انجام داد:
from django.utils.functional import cached_property
class Post(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
@cached_property
def word_count(self):
# فقط یکبار محاسبه میشود
return len(self.content.split())
@cached_property
def reading_time(self):
return self.word_count // 200 # دقیقه
نحوه استفاده:
post = Post.objects.first()
print(post.word_count) # محاسبه میشود
print(post.word_count) # از کش برمیگردد (سریعتر)
۸. کش کردن QuerySet
۸.۱ با استفاده از Prefetch و Select Related
class PostListView(ListView):
# کاهش تعداد کوئریها به دیتابیس
queryset = Post.objects.prefetch_related('tags').select_related('author')
۸.۲ با استفاده از کتابخانه django-cacheops
# settings.py
CACHEOPS_REDIS = 'redis://127.0.0.1:6379/0'
CACHEOPS_ENABLED = True
این کتابخانه به صورت خودکار QuerySet ها را کش می کند و عملکرد بسیار بالایی دارد.
۹. کش کردن با Decorator سفارشی
می توانید decorator های سفارشی برای نیازهای خاص خود بسازید:
# utils/cache_decorators.py
from django.core.cache import cache
import functools
def cache_view(timeout=60 * 15, vary_on_user=True):
def decorator(view_func):
@functools.wraps(view_func)
def wrapper(request, *args, **kwargs):
# تولید کلید کش
key = f"view_{request.path}_{request.GET.urlencode()}"
if vary_on_user and request.user.is_authenticated:
key = f"{key}_user_{request.user.id}"
result = cache.get(key)
if result is not None:
return result
result = view_func(request, *args, **kwargs)
cache.set(key, result, timeout)
return result
return wrapper
return decorator
۱۰. Invalid کردن کش
وقتی داده ها تغییر می کنند، باید کش را پاک کنید.
۱۰.۱ با استفاده از سیگنال ها
# signals.py
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from .models import Post
@receiver(post_save, sender=Post)
def clear_post_cache(sender, instance, **kwargs):
from django.core.cache import cache
cache.delete('post_list')
cache.delete(f'post_detail_{instance.slug}')
@receiver(post_delete, sender=Post)
def clear_post_cache_on_delete(sender, instance, **kwargs):
clear_post_cache(sender, instance, **kwargs)
۱۰.۲ با استفاده از Redis Pattern
from django_redis import get_redis_connection
def invalidate_pattern(pattern):
conn = get_redis_connection("default")
keys = conn.keys(f"*{pattern}*")
if keys:
conn.delete(*keys)
# استفاده
invalidate_pattern('post_list')
بهترین روش برای وبلاگ
برای یک وبلاگ معمولی، ترکیب زیر پیشنهاد می شود:
# views.py
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page, vary_on_cookie
@method_decorator(vary_on_cookie, name='dispatch')
@method_decorator(cache_page(60 * 15), name='dispatch')
class PostListView(ListView):
model = Post
template_name = 'blog/list.html'
@method_decorator(vary_on_cookie, name='dispatch')
@method_decorator(cache_page(60 * 30), name='dispatch')
class PostDetailView(DetailView):
model = Post
template_name = 'blog/detail.html'
<!-- templates/blog/sidebar.html -->
{% load cache %}
{% cache 3600 popular_tags %}
<!-- لیست تگهای محبوب -->
{% endcache %}
# signals.py
from django.db.models.signals import post_save, post_delete
from django.dispatch import receiver
from .models import Post
@receiver(post_save, sender=Post)
@receiver(post_delete, sender=Post)
def clear_cache(sender, instance, **kwargs):
from django.core.cache import cache
cache.clear() # یا کلیدهای خاص
نتیجه گیری
کش کردن ابزار قدرتمندی برای بهبود عملکرد برنامه های جنگو است. بسته به نیاز پروژه خود، از روش های مختلف استفاده کنید:
- سایت ساده و استاتیک: از Per-site caching استفاده کنید
- صفحات پر بازدید: از Per-view با Vary headers استفاده کنید
- کامپوننت های تکراری: از Template fragment caching استفاده کنید
- داده های پیچیده: از Low-level API یا کتابخانه هایی مثل cacheops استفاده کنید
فراموش نکنید که کش را در زمان مناسب invalid کنید تا کاربران محتوای قدیمی نبینند!