Een cache toevoegen kan een enorme performantieboost zijn voor een website. Django maakt het eenvoudig om een cache toe te voegen, het laat out of the box 3 manieren om te cachen toe:
- Volledige site caching (elke pagina krijgt dezelfde behandeling)
- Per view caching (Er kan per view bepaald worden of er gecached moet worden, en voor hoelang)
- Low level caching (waar individuele objecten of databasequeries gecached kunnen worden.
Als ik bij een test applicatie per-view caching toevoeg als test, kan ik op een VM met 2 cores 600 requests/seconde verwerken in plaats van 200/seconde. In deze post zal ik toelichten hoe je per-view caching kan implementeren binnen Django
De cache configureren
Django ondersteund verschillende backens, zoals Memcached, Redis, File based en zelfs in het programmageheugen zelf (al is dat enkel nuttig tijdens development).
Memcached
Memcached is de aangeraden manier om een cache te implementeren binnen django. Hiervoor zul je wel eerst “pymemcache” moeten installeren via PIP. Eens geïnstalleerd kan je het volgende toevoegen aan settings.py om memcached te gebruiken:
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
"LOCATION": "127.0.0.1:11211",
}
}
Locmem
Ik gebruik Locmem cache voor mijn ontwikkelomgeving. Op deze manier kan ik lokaal testen met caches, zonder een memcached instance te moeten installeren op mijn PC. Een bijkomend voordeel is dat deze cache gewist word bij een herstart van de applicatie, wat automatisch gebeurd tijdens development. Op deze manier is alles altijd “leeg” na een wijziging in de code. Deze cache gebruik je best niet in productie, aangezien er geen garbage collectie is en deze per process is.
CACHES = {
"default": {
"BACKEND": "django.core.cache.backends.locmem.LocMemCache",
}
}
Decorators toevoegen
In de views.py van je applicatie kan je het volgende toevoegen aan een view:
from django.views.decorators.cache import cache_page
@cache_page(3600)
def index(request):
post_list = Post.objects.order_by("-pub_date").filter(pub_date__lte=timezone.now(),draft=False)
return HttpResponse(render(request, "cms/index.html",{"post_list": post_list}))
Als je de pagina nu laad, zal deze in cache worden opgeslagen. Zolang de pagina in cache niet ouder is dan 3600 seconden (1 uur) zal de pagina rechtstreeks uit cache geserveerd worden. Er worden meteen ook cache-control headers gestuurd, zodat downstream caches en de browser de pagina ook cachen.
Automatisch cache entries invalideren bij pagina updates
Het kan soms handig zijn om automatisch cache entries te invalideren als er een wijziging plaatsvind op de site. In dit voorbeeld leeg ik de gehele cache, maar het kan aangepast worden om enkel de gewijzigde pagina’s te verwijderen.
Voeg het volgende toe, ergens onderaan de models.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.core.cache import caches
# this gets triggered both on Page and Post updates
@receiver(post_save, sender=Page)
@receiver(post_save, sender=Post)
def clear_cache(sender, instance, **kwargs):
cache = caches["default"]
cache.clear()
Als we nu een pagina wijzigen (of toevoegen), zal de gehele cache geleegd worden. Op deze manier zien gebruikers wel meteen de laatste wijzigingen.
Niet cachen op de client
Soms willen we wel dat een pagina gecached word, maar enkel op de server en niet bij de client. Om dit te doen kunnen we de @cache_page decorator wrappen in de @never_cache decorator
from django.views.decorators.cache import cache_page,never_cache
@never_cache
@cache_page(3600)
def index(request):
post_list = Post.objects.order_by("-pub_date").filter(pub_date__lte=timezone.now(),draft=False)
return HttpResponse(render(request, "cms/index.html",{"post_list": post_list}))
We kunnen ook custom cache_control headers meegeven (als we bijvoorbeeld de pagina voor 1 uur willen cachen, maar slechts voor 10 minuten bij downstream caches)
from django.views.decorators.cache import cache_page,cache_control
# cache page for 1 hour, let clients cache the page for 10 minutes, and let any downstream server caches cache the page for 20 minutes
@cache_control(max_age=60*10,s_maxage=60*20)
@cache_page(60*60)
def index(request):
post_list = Post.objects.order_by("-pub_date").filter(pub_date__lte=timezone.now(),draft=False)
return HttpResponse(render(request, "cms/index.html",{"post_list": post_list}))