Le prestazioni dell'applicazione sono vitali per il successo del tuo prodotto. In un ambiente in cui gli utenti si aspettano tempi di risposta del sito Web inferiori a un secondo, le conseguenze di un'applicazione lenta possono essere misurate in dollari e centesimi. Anche se non vendi nulla, i caricamenti rapidi delle pagine migliorano l'esperienza di visita del tuo sito.
Tutto ciò che accade sul server dal momento in cui riceve una richiesta al momento in cui restituisce una risposta aumenta il tempo necessario per caricare una pagina. Come regola generale, maggiore è l'elaborazione che puoi eliminare sul server, più veloce sarà l'esecuzione dell'applicazione. La memorizzazione nella cache dei dati dopo che sono stati elaborati e la successiva pubblicazione dalla cache alla successiva richiesta è un modo per alleviare lo stress sul server. In questo tutorial esploreremo alcuni dei fattori che ostacolano la tua applicazione e dimostreremo come implementare la memorizzazione nella cache con Redis per contrastarne gli effetti.
Bonus gratuito: Fai clic qui per accedere a una guida gratuita alle risorse di apprendimento di Django (PDF) che mostra suggerimenti e trucchi, nonché le insidie comuni da evitare durante la creazione di applicazioni Web Python + Django.
Cos'è Redis?
Redis è un archivio di strutture dati in memoria che può essere utilizzato come motore di memorizzazione nella cache. Poiché mantiene i dati nella RAM, Redis può consegnarli molto rapidamente. Redis non è l'unico prodotto che possiamo utilizzare per la memorizzazione nella cache. Memcached è un altro popolare sistema di memorizzazione nella cache in memoria, ma molte persone concordano sul fatto che Redis sia superiore a Memcached nella maggior parte dei casi. Personalmente, ci piace quanto sia facile configurare e utilizzare Redis per altri scopi come Redis Queue.
Per iniziare
Abbiamo creato un'applicazione di esempio per farvi conoscere il concetto di memorizzazione nella cache. La nostra applicazione utilizza:
- Django (v1.9.8)
- Barra degli strumenti di debug di Django (v1.4)
- django-redis (v4.4.3)
- Redis (v3.2.0)
Installa l'app
Prima di clonare il repository, installa virtualenvwrapper, se non lo hai già. Questo è uno strumento che ti consente di installare le dipendenze Python specifiche di cui il tuo progetto ha bisogno, consentendoti di indirizzare le versioni e le librerie richieste dalla tua app in isolamento.
Quindi, cambia le directory in cui tieni i progetti e clona il repository dell'app di esempio. Una volta terminato, cambia le directory nel repository clonato, quindi crea un nuovo ambiente virtuale per l'app di esempio utilizzando mkvirtualenv
comando:
$ mkvirtualenv django-redis
(django-redis)$
NOTA: Creazione di un ambiente virtuale con mkvirtualenv
lo attiva anche.
Installa tutte le dipendenze Python richieste con pip
, quindi controlla il seguente tag:
(django-redis)$ git checkout tags/1
Completa la configurazione dell'app di esempio creando il database e popolandolo con dati di esempio. Assicurati di creare anche un superutente, in modo da poter accedere al sito di amministrazione. Segui gli esempi di codice seguenti e quindi prova a eseguire l'app per assicurarti che funzioni correttamente. Visita la pagina di amministrazione nel browser per confermare che i dati siano stati caricati correttamente.
(django-redis)$ python manage.py makemigrations cookbook
(django-redis)$ python manage.py migrate
(django-redis)$ python manage.py createsuperuser
(django-redis)$ python manage.py loaddata cookbook/fixtures/cookbook.json
(django-redis)$ python manage.py runserver
Dopo aver avviato l'app Django, passa all'installazione di Redis.
Installa Redis
Scarica e installa Redis utilizzando le istruzioni fornite nella documentazione. In alternativa, puoi installare Redis utilizzando un gestore di pacchetti come apt-get o homebrew a seconda del tuo sistema operativo.
Esegui il server Redis da una nuova finestra di terminale.
$ redis-server
Quindi, avvia l'interfaccia della riga di comando (CLI) di Redis in una finestra di terminale diversa e verifica che si connetta al server Redis. Utilizzeremo la CLI Redis per ispezionare le chiavi che aggiungiamo alla cache.
$ redis-cli ping
PONG
Redis fornisce un'API con vari comandi che uno sviluppatore può utilizzare per agire sul datastore. Django usa django-redis per eseguire comandi in Redis.
Osservando la nostra app di esempio in un editor di testo, possiamo vedere la configurazione di Redis in settings.py file. Definiamo una cache predefinita con CACHES
impostazione, utilizzando un django-redis integrato cache come nostro backend. Redis viene eseguito sulla porta 6379 per impostazione predefinita e indichiamo quella posizione nelle nostre impostazioni. Un'ultima cosa da menzionare è che django-redis aggiunge i nomi delle chiavi con un prefisso e una versione per aiutare a distinguere chiavi simili. In questo caso, abbiamo definito il prefisso “esempio”.
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": "redis://127.0.0.1:6379/1",
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient"
},
"KEY_PREFIX": "example"
}
}
NOTA :Sebbene abbiamo configurato il backend della cache, nessuna delle funzioni di visualizzazione ha implementato la memorizzazione nella cache.
Rendimento dell'app
Come accennato all'inizio di questo tutorial, tutto ciò che fa il server per elaborare una richiesta rallenta il tempo di caricamento dell'applicazione. Il sovraccarico di elaborazione dell'esecuzione della logica aziendale e dei modelli di rendering può essere significativo. La latenza di rete influisce sul tempo necessario per interrogare un database. Questi fattori entrano in gioco ogni volta che un client invia una richiesta HTTP al server. Quando gli utenti avviano molte richieste al secondo, gli effetti sulle prestazioni diventano evidenti mentre il server lavora per elaborarle tutte.
Quando implementiamo la memorizzazione nella cache, consentiamo al server di elaborare una richiesta una volta e quindi la memorizziamo nella nostra cache. Poiché le richieste per lo stesso URL vengono ricevute dalla nostra applicazione, il server estrae i risultati dalla cache invece di elaborarli nuovamente ogni volta. In genere, impostiamo un'ora in cui vivere i risultati memorizzati nella cache, in modo che i dati possano essere aggiornati periodicamente, un passaggio importante da implementare per evitare la pubblicazione di dati obsoleti.
Dovresti considerare di memorizzare nella cache il risultato di una richiesta quando si verificano i seguenti casi:
- il rendering della pagina comporta molte query di database e/o logica di business,
- la pagina è visitata frequentemente dai tuoi utenti,
- i dati sono gli stessi per ogni utente,
- e i dati non cambiano spesso.
Inizia misurando le prestazioni
Inizia testando la velocità di ogni pagina della tua applicazione confrontando la velocità con cui la tua applicazione restituisce una risposta dopo aver ricevuto una richiesta.
Per raggiungere questo obiettivo, faremo esplodere ogni pagina con una raffica di richieste utilizzando loadtest, un generatore di carico HTTP e quindi presteremo molta attenzione alla frequenza delle richieste. Visita il link sopra per installare. Una volta installato, verifica i risultati con il /cookbook/
Percorso URL:
$ loadtest -n 100 -k http://localhost:8000/cookbook/
Si noti che stiamo elaborando circa 16 richieste al secondo:
Requests per second: 16
Quando osserviamo cosa sta facendo il codice, possiamo prendere decisioni su come apportare modifiche per migliorare le prestazioni. L'applicazione effettua 3 chiamate di rete a un database con ogni richiesta a /cookbook/
e ogni chiamata richiede tempo per aprire una connessione ed eseguire una query. Visita il /cookbook/
URL nel tuo browser ed espandi la scheda Django Debug Toolbar per confermare questo comportamento. Trova il menu denominato "SQL" e leggi il numero di query:
ricettario/servizi.py
from cookbook.models import Recipe
def get_recipes():
# Queries 3 tables: cookbook_recipe, cookbook_ingredient,
# and cookbook_food.
return list(Recipe.objects.prefetch_related('ingredient_set__food'))
ricettario/views.py
from django.shortcuts import render
from cookbook.services import get_recipes
def recipes_view(request):
return render(request, 'cookbook/recipes.html', {
'recipes': get_recipes()
})
L'applicazione esegue anche il rendering di un modello con una logica potenzialmente costosa.
<html>
<head>
<title>Recipes</title>
</head>
<body>
{% for recipe in recipes %}
<h1>{{ recipe.name }}</h1>
<p>{{ recipe.desc }}</p>
<h2>Ingredients</h2>
<ul>
{% for ingredient in recipe.ingredient_set.all %}
<li>{{ ingredient.desc }}</li>
{% endfor %}
</ul>
<h2>Instructions</h2>
<p>{{ recipe.instructions }}</p>
{% endfor %}
</body>
</html>
Implementa la memorizzazione nella cache
Immagina il numero totale di chiamate di rete che la nostra applicazione effettuerà quando gli utenti inizieranno a visitare il nostro sito. Se 1.000 utenti raggiungono l'API che recupera le ricette del libro di cucina, la nostra applicazione interrogherà il database 3.000 volte e verrà visualizzato un nuovo modello con ogni richiesta. Quel numero cresce solo con la scalabilità della nostra applicazione. Fortunatamente, questa vista è un ottimo candidato per la memorizzazione nella cache. Le ricette in un libro di cucina cambiano raramente, se mai. Inoltre, poiché la visualizzazione dei libri di cucina è il tema centrale dell'app, è garantito che l'API che recupera le ricette venga chiamata frequentemente.
Nell'esempio seguente, modifichiamo la funzione di visualizzazione per utilizzare la memorizzazione nella cache. Quando la funzione viene eseguita, controlla se la chiave di visualizzazione è nella cache. Se la chiave esiste, l'app recupera i dati dalla cache e li restituisce. In caso contrario, Django interroga il database e quindi memorizza il risultato nella cache con la chiave di visualizzazione. La prima volta che questa funzione viene eseguita, Django interrogherà il database e visualizzerà il modello, quindi eseguirà anche una chiamata di rete a Redis per archiviare i dati nella cache. Ogni successiva chiamata alla funzione ignorerà completamente il database e la logica aziendale e interrogherà la cache Redis.
esempio/impostazioni.py
# Cache time to live is 15 minutes.
CACHE_TTL = 60 * 15
ricettario/views.py
from django.conf import settings
from django.core.cache.backends.base import DEFAULT_TIMEOUT
from django.shortcuts import render
from django.views.decorators.cache import cache_page
from cookbook.services import get_recipes
CACHE_TTL = getattr(settings, 'CACHE_TTL', DEFAULT_TIMEOUT)
@cache_page(CACHE_TTL)
def recipes_view(request):
return render(request, 'cookbook/recipes.html', {
'recipes': get_recipes()
})
Nota che abbiamo aggiunto il @cache_page()
decoratore alla funzione di visualizzazione, insieme a un tempo da vivere. Visita il /cookbook/
URL di nuovo ed esaminare la Django Debug Toolbar. Vediamo che vengono effettuate 3 query al database e 3 chiamate alla cache per verificare la chiave e quindi salvarla. Django salva due chiavi (1 chiave per l'intestazione e 1 chiave per il contenuto della pagina renderizzata). Ricarica la pagina e osserva come cambia l'attività della pagina. La seconda volta, vengono effettuate 0 chiamate al database e 2 chiamate alla cache. La nostra pagina viene ora servita dalla cache!
Quando eseguiamo nuovamente i nostri test delle prestazioni, vediamo che la nostra applicazione si sta caricando più velocemente.
$ loadtest -n 100 -k http://localhost:8000/cookbook/
La memorizzazione nella cache ha migliorato il carico totale e ora stiamo risolvendo 21 richieste al secondo, 5 in più rispetto alla linea di base:
Requests per second: 21
Ispezione di Redis con la CLI
A questo punto possiamo usare la CLI Redis per vedere cosa viene memorizzato sul server Redis. Nella riga di comando di Redis, inserisci i keys *
comando, che restituisce tutte le chiavi corrispondenti a qualsiasi modello. Dovresti vedere una chiave chiamata "example:1:views.decorators.cache.cache_page". Ricorda, "example" è il nostro prefisso chiave, "1" è la versione e "views.decorators.cache.cache_page" è il nome che Django dà alla chiave. Copia il nome della chiave e inseriscilo con get
comando. Dovresti vedere la stringa HTML renderizzata.
$ redis-cli -n 1
127.0.0.1:6379[1]> keys *
1) "example:1:views.decorators.cache.cache_header"
2) "example:1:views.decorators.cache.cache_page"
127.0.0.1:6379[1]> get "example:1:views.decorators.cache.cache_page"
NOTA: Esegui il flushall
comando sulla CLI Redis per cancellare tutte le chiavi dall'archivio dati. Quindi, puoi ripetere i passaggi di questo tutorial senza dover attendere la scadenza della cache.
Concludi
L'elaborazione delle richieste HTTP è costosa e tale costo aumenta man mano che la tua applicazione cresce in popolarità. In alcuni casi, puoi ridurre notevolmente la quantità di elaborazione del tuo server implementando la memorizzazione nella cache. Questo tutorial ha toccato le basi della memorizzazione nella cache in Django con Redis, ma ha solo sfiorato la superficie di un argomento complesso.
L'implementazione della memorizzazione nella cache in un'applicazione robusta presenta molte insidie e problemi. Controllare ciò che viene memorizzato nella cache e per quanto tempo è difficile. L'invalidazione della cache è una delle cose difficili in Informatica. Garantire che i dati privati siano accessibili solo agli utenti previsti è un problema di sicurezza e deve essere gestito con molta attenzione durante la memorizzazione nella cache.
Bonus gratuito: Fai clic qui per accedere a una guida gratuita alle risorse di apprendimento di Django (PDF) che mostra suggerimenti e trucchi, nonché le insidie comuni da evitare durante la creazione di applicazioni Web Python + Django.
Gioca con il codice sorgente nell'applicazione di esempio e mentre continui a sviluppare con Django, ricorda di tenere sempre a mente le prestazioni.