Memorizzazione nella cache NDB

NDB gestisce le cache per te. Esistono due livelli di memorizzazione nella cache: una cache contestuale e un gateway al servizio di memorizzazione nella cache standard di App Engine, memcache. Entrambe le cache sono abilitate per impostazione predefinita per tutti i tipi di entità, ma possono essere configurate per soddisfare esigenze avanzate. Inoltre, NDB implementa una funzionalità chiamata batch automatico, che cerca di raggruppare le operazioni per ridurre al minimo i viaggi di ritorno del server.

Introduzione

La memorizzazione nella cache è utile per la maggior parte dei tipi di applicazioni. NDB memorizza automaticamente nella cache i dati che scrive o legge (a meno che un'applicazione non li configuri). La lettura dalla cache è più veloce rispetto alla lettura da Datastore.

Puoi modificare il comportamento di memorizzazione nella cache di molte funzioni NDB passando argomenti Opzioni di contesto. Ad esempio, potresti chiamare key.get(use_cache=False, use_memcache=False) per bypassare la memorizzazione nella cache. Puoi anche modificare il criterio di memorizzazione nella cache predefinito in un contesto NDB come descritto di seguito.

Attenzione: quando utilizzi il visualizzatore Datastore della console di amministrazione per modificare i contenuti del datastore, i valori memorizzati nella cache non vengono aggiornati. Di conseguenza, la cache potrebbe non essere coerente. Generalmente non è un problema per la cache contestuale. Per memcache, consigliamo di utilizzare la console di amministrazione per svuotare la cache.

Oggetti di contesto

La gestione della cache utilizza una classe denominata Context: ogni thread e ogni transazione viene eseguita in un nuovo contesto. Poiché ogni richiesta HTTP in entrata avvia un nuovo thread, ogni richiesta viene eseguita anche con un nuovo contesto. Per accedere al contesto attuale, utilizza la funzione ndb.get_context().

Attenzione:non ha senso condividere gli oggetti Context tra più thread o richieste. Non salvare il contesto come variabile globale. È possibile memorizzarla in una variabile locale o in thread locale.

Gli oggetti di contesto prevedono metodi per impostare i criteri della cache e manipolare in altro modo la cache.

La cache in-contesto

La cache contestuale viene mantenuta solo per la durata di un singolo thread. Ciò significa che a ogni richiesta HTTP in entrata viene assegnata una nuova cache contestuale ed è "visibile" solo al codice che gestisce la richiesta. Se l'applicazione genera altri thread durante la gestione di una richiesta, questi thread avranno anche una nuova cache contestuale separata.

La cache contestuale è veloce e risiede in memoria. Quando una funzione NDB scrive nel Datastore, scrive anche nella cache del contesto. Quando una funzione NDB legge un'entità, controlla prima la cache contestuale. Se l'entità viene trovata in questa posizione, non viene eseguita alcuna interazione con Datastore.

Quando una funzione NDB esegue query sul Datastore, l'elenco dei risultati viene recuperato dal Datastore. Tuttavia, se un singolo risultato si trova nella cache contestuale, viene utilizzato al posto del valore recuperato dalla query Datastore. I risultati delle query vengono scritti nella cache contestuale se il criterio della cache lo indica (ma mai in Memcache).

Con l'esecuzione di query a lunga esecuzione in attività in background, è possibile che la cache contestuale utilizzi grandi quantità di memoria. Questo perché la cache conserva una copia di ogni entità recuperata o archiviata nel contesto attuale. Per evitare eccezioni di memoria nelle attività di lunga durata, puoi disabilitare la cache o impostare un criterio che escluda le entità che consumano più memoria.

Memcache

Memcache è il servizio di memorizzazione nella cache standard di App Engine, molto più veloce di Datastore, ma più lento rispetto alla cache contestuale (millisecondi anziché microsecondi).

Per impostazione predefinita, un contesto non transazionale memorizza nella cache tutte le entità in memcache. Tutti i contesti di un'applicazione utilizzano lo stesso server memcache e visualizzano un insieme coerente di valori memorizzati nella cache.

Memcache non supporta le transazioni. Di conseguenza, potrebbe essere apportato un aggiornamento sia a Datastore sia a memcache solo a uno dei due. Per mantenere la coerenza in questi casi (probabilmente a scapito delle prestazioni), l'entità aggiornata viene eliminata da memcache e quindi scritta in Datastore. Un'operazione di lettura successiva troverà l'entità mancante in memcache, la recupererà da Datastore e quindi la aggiornerà in memcache come effetto collaterale della lettura. Inoltre, NDB legge all'interno delle transazioni ignorando la memcache.

Quando le entità vengono scritte all'interno di una transazione, memcache non viene utilizzata; quando viene eseguito il commit della transazione, il relativo contesto tenta di eliminare tutte le entità da memcache. Tuttavia, tieni presente che alcuni errori potrebbero impedire queste eliminazioni.

Funzioni relative ai criteri

La memorizzazione nella cache automatica è comoda per la maggior parte delle applicazioni, ma forse la tua applicazione è insolita e vuoi disattivare la memorizzazione nella cache automatica per alcune o tutte le entità. Puoi controllare il comportamento delle cache impostando funzioni di criterio. È presente una funzione di criterio per la cache in-process, impostata con

context = ndb.get_context()
context.set_cache_policy(func)

e un'altra per memcache, impostata con

context = ndb.get_context()
context.set_memcache_policy(func)

Ogni funzione dei criteri accetta una chiave e restituisce un risultato booleano. Se restituisce False, l'entità identificata da tale chiave non verrà salvata nella cache corrispondente. Ad esempio, per bypassare la cache in-process per tutte le entità Account, puoi scrivere

context = ndb.get_context()
context.set_cache_policy(lambda key: key.kind() != 'Account')

(Tuttavia, continua a leggere per trovare un modo più semplice per realizzare la stessa cosa.) Per praticità, puoi passare True o False invece di una funzione che restituisce sempre lo stesso valore. I criteri predefiniti memorizzano nella cache tutte le entità.

Esiste anche una funzione dei criteri del Datastore che regola le entità scritte nel datastore:

context = ndb.get_context()
context.set_datastore_policy(func)

Funziona in modo simile alle funzioni della cache contestuale e dei criteri memcache: se la funzione dei criteri del Datastore restituisce False per una determinata chiave, l'entità corrispondente non verrà scritta nel Datastore. (Può essere scritto nella cache in-process o in memcache se le funzioni dei criteri lo consentono). Questo può essere utile nei casi in cui disponi di dati di tipo entità che vuoi memorizzare nella cache, ma che non è necessario archiviare in Datastore. Come per i criteri della cache, puoi passare True o False anziché una funzione che restituisce sempre lo stesso valore.

Memcache fa scadere automaticamente gli elementi in caso di utilizzo intensivo della memoria. Puoi impostare una funzione dei criteri di timeout memcache per determinare la durata massima di un'entità nella cache:

context = ndb.get_context()
context.set_memcache_timeout_policy(func)

Questa funzione viene chiamata con un argomento chiave e deve restituire un numero intero che specifica la durata massima in secondi; 0 o None significa indefinito (purché il server memcache abbia memoria sufficiente). Per praticità, puoi semplicemente passare una costante intera invece di una funzione che restituisce sempre lo stesso valore. Per ulteriori informazioni sui timeout, consulta la documentazione relativa alle memcache.

Nota:non esiste un criterio di durata distinto per la cache contestuale: la durata della cache è uguale a quella del suo contesto, ovvero una singola richiesta HTTP in entrata. Tuttavia, puoi svuotare la cache in-process chiamando
context = ndb.get_context()
context.clear_cache()

Un contesto completamente nuovo inizia con una cache in-process vuota.

Sebbene le funzioni dei criteri siano molto flessibili, in pratica la maggior parte dei criteri è semplice. Ad esempio,

  • Non memorizzare nella cache entità che appartengono a una classe di modello specifica.
  • Imposta il timeout memcache per le entità in questa classe del modello su 30 secondi.
  • Le entità in questa classe di modello non devono essere scritte in Datastore.

Per evitare di dover scrivere e aggiornare continuamente funzioni di criterio basilari (o peggio ancora, sovrascrivere i criteri per ogni operazione utilizzando le opzioni di contesto), le funzioni dei criteri predefinite ottengono la classe del modello dalla chiave passata e cercano quindi variabili di classe specifiche nella classe del modello:

Variabile di classe Tipo Descrizione
_use_cache bool Specifica se archiviare le entità nella cache in elaborazione; sostituisce il criterio della cache in-process predefinito.
_use_memcache bool Specifica se archiviare le entità in memcache; sostituisce il criterio memcache predefinito.
_use_datastore bool Specifica se archiviare le entità nel datastore; sostituisce il criterio Datastore predefinito.
_memcache_timeout int Durata massima delle entità in memcache; sostituisce il criterio di timeout predefinito di memcache.

Nota:si tratta di una funzionalità della funzione dei criteri predefiniti per ogni criterio. Se specifichi la tua funzione di criterio, ma vuoi anche utilizzare il criterio predefinito, richiama esplicitamente le funzioni del criterio predefinito come metodi statici della classe Context:

  • default_cache_policy(key)
  • default_memcache_policy(key)
  • default_datastore_policy(key)
  • default_memcache_timeout_policy(key)