Memorizzazione nella cache NDB

NDB gestisce le cache per te. Esistono due livelli di memorizzazione nella cache: una cache contestuale e un gateway per il 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 auto-batch, che tenta di raggruppare le operazioni per ridurre al minimo i round trip dei server.

Introduzione

La memorizzazione nella cache favorisce 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 della memorizzazione nella cache di molte funzioni NDB passando gli 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 verranno aggiornati. Di conseguenza, la tua cache potrebbe essere incoerente. Per la cache contestuale questo generalmente non è un problema. 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 vengono eseguite 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 corrente, utilizza la funzione ndb.get_context().

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

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

La cache contestuale

La cache contestuale persiste 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 ulteriori thread durante la gestione di una richiesta, anche questi avranno una nuova cache nel contesto separata.

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

Quando una funzione NDB esegue una query in Datastore, l'elenco dei risultati viene recuperato da Datastore. Tuttavia, se un singolo risultato si trova nella cache nel contesto, viene utilizzato al posto del valore recuperato dalla query Datastore. I risultati della query vengono riscritti nella cache nel contesto se indicato dal criterio della cache (ma mai in Memcache).

Con l'esecuzione di query a lunga esecuzione in attività in background, è possibile che la cache nel contesto 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 della cache in contesto (millisecondi o 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, un aggiornamento che deve essere applicato sia a Datastore che a memcache potrebbe essere effettuato solo a uno dei due. Per mantenere la coerenza in questi casi (a scapito delle prestazioni), l'entità aggiornata viene eliminata da memcache e quindi scritta in Datastore. Una successiva operazione di lettura troverà l'entità mancante in memcache, la recupererà da Datastore e la aggiornerà in memcache come effetto collaterale della lettura. Inoltre, le letture delle transazioni NDB ignorano la memcache.

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

Funzioni dei criteri

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

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

e un altro per memcache, impostata

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

Ogni funzione del criterio accetta una chiave e restituisce un risultato booleano. Se restituisce False, l'entità identificata da quella chiave non verrà salvata nella cache corrispondente. Ad esempio, per bypassare la cache in-processo 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 scoprire più facilmente lo stesso risultato.) Per comodità, 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 Datastore che regola quali entità vengono scritte in Datastore stesso:

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

Funziona come la cache nel contesto e le funzioni del criterio memcache: se la funzione del criterio Datastore restituisce False per una chiave specifica, l'entità corrispondente non verrà scritta nel Datastore. (Può essere scritto nella cache in-processo o nella memcache se le relative 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 non devi archiviarli in Datastore. Come per i criteri relativi alla cache, puoi passare True o False al posto di una funzione che restituisca sempre lo stesso valore.

memcache fa scadere automaticamente gli elementi in caso di pressione in memoria. Puoi impostare una funzione del criterio 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 (a condizione che il server memcache abbia memoria sufficiente). Per comodità, puoi semplicemente passare una costante intera al posto di una funzione che restituisce sempre lo stesso valore. Consulta la documentazione memcache per ulteriori informazioni sui timeout.

Nota: non esiste un criterio di durata separato per la cache contestuale: la durata della cache corrisponde a quella del contesto, ovvero di una singola richiesta HTTP in entrata. Tuttavia, puoi svuotare la cache in corso richiamando
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, nella pratica la maggior parte dei criteri è semplice. Ad esempio:

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

Per evitare il lavoro di scrittura e aggiornamento continuo di funzioni dei criteri banali (o peggio ancora, sovrascrivendo i criteri per ogni operazione utilizzando le opzioni di contesto), le funzioni dei criteri predefiniti recuperano la classe del modello dalla chiave che è stata loro passata, quindi cercano nella classe del modello specifiche variabili di classe:

Variabile di classe Tipo Descrizione
_use_cache bool Specifica se archiviare le entità nella cache in-process; sostituisce il criterio predefinito relativo alla cache in-process.
_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 per le entità in memcache; sostituisce il criterio di timeout memcache predefinito.

Nota: questa è una funzionalità della funzione del criterio predefinito per ogni criterio. Se specifichi la tua funzione del criterio, ma vuoi anche ricorrere al criterio predefinito, chiama le funzioni del criterio predefinito in modo esplicito come metodi statici di classe Context:

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