Memorizzazione nella cache NDB

NDB gestisce le cache per te. Esistono due livelli di memorizzazione nella cache: una cache in-context 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 in base alle esigenze avanzate. Inoltre, NDB implementa una funzionalità chiamata aggregazione automatica, che tenta di raggruppare le operazioni per ridurre al minimo i viaggi di andata e 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 lo configuri in modo diverso). La lettura dalla cache è più veloce della lettura da Datastore.

Puoi modificare il comportamento della memorizzazione nella cache di molte funzioni NDB passando gli argomenti Opzioni 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 lo visualizzatore di Datastore della Console di amministrazione per modificare i contenuti di Datastore, i valori memorizzati nella cache non verranno aggiornati. Di conseguenza, la cache potrebbe non essere coerente. In genere, questo non è un problema per la cache in-context. Per Memcache, ti 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 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. È sufficiente memorizzarlo in una variabile locale o locale del thread.

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

La cache in-context

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

La cache in-context è veloce e si trova in memoria. Quando una funzione NDB scrive in Datastore, scrive anche nella cache in-context. Quando una funzione NDB legge un'entità, controlla prima la cache in-context. Se l'entità viene trovata lì, non viene eseguita alcuna interazione con Datastore.

Quando una funzione NDB esegue una query su Datastore, l'elenco dei risultati viene recuperato da Datastore. Tuttavia, se un singolo risultato si trova nella cache in-context, viene utilizzato al posto del valore recuperato dalla query Datastore. I risultati delle query vengono riscritti nella cache in-context se i criteri della cache lo prevedono (ma mai in Memcache).

Con l'esecuzione di query che richiedono molto tempo nelle attività in background, è possibile che la cache in-context consumerà grandi quantità di memoria. Questo perché la cache conserva una copia di ogni entità recuperata o archiviata nel contesto corrente. Per evitare eccezioni di memoria nelle attività che richiedono molto tempo, puoi disattivare 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-context (millisecondi rispetto a 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 vedono un insieme coerente di valori memorizzati nella cache.

Memcache non supporta le transazioni. Pertanto, un aggiornamento pensato per essere applicato sia a Datastore sia a memcache potrebbe essere applicato solo a uno dei due. Per mantenere la coerenza in questi casi (eventualmente a spese delle prestazioni), l'entità aggiornata viene eliminata da Memcache e poi scritta in Datastore. Un'operazione di lettura successiva troverà l'entità mancante in memcache, la recupererà da Datastore e poi la aggiornerà in memcache come effetto collaterale della lettura. Inoltre, le letture NDB all'interno delle transazioni ignorano Memcache.

Quando le entità vengono scritte all'interno di una transazione, memcache non viene utilizzato. Quando la transazione viene confermata, il relativo contesto tenterà di eliminare tutte queste entità da memcache. Tieni presente, tuttavia, 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 nella cache automatica per alcune o tutte le entità. Puoi controllare il comportamento delle cache impostando le funzioni di criteri. Esiste una funzione di criteri 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 di 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 ignorare 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 scoprire un modo più semplice per ottenere lo stesso risultato. Per comodità, puoi passare True o False instead of a function that always returns the same value. I criteri predefiniti memorizzano nella cache tutte le entità.

Esiste anche una funzione di criteri di Datastore che regola le entità scritte nel Datastore stesso:

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

Funziona come le funzioni dei criteri della cache in-context e memcache: se la funzione del criterio Datastore restituisce False per una determinata chiave, l'entità corrispondente non verrà scritta in Datastore. Potrebbe essere scritto nella cache in-process o in memcache se le relative funzioni di criteri lo consentono. Questo può essere utile nei casi in cui hai dati simili a entità che vuoi memorizzare nella cache, ma che non devi archiviare in Datastore. Come per i criteri della cache, puoi passare True o False anziché una funzione che restituisce sempre lo stesso valore.

Memcache scade automaticamente gli elementi in caso di pressione sulla memoria. Puoi impostare una funzione di 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 valore 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 anziché una funzione che restituisce sempre lo stesso valore. Per ulteriori informazioni sui timeout, consulta la documentazione di Memcache.

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

Un contesto nuovo di zecca 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 le entità appartenenti a una classe di modelli specifica.
  • Imposta il timeout di memcache per le entità in questa classe di modello su 30 secondi.
  • Le entità di questa classe di modelli non devono essere scritte in Datastore.

Per semplificarti la scrittura e l'aggiornamento continuo di funzioni di criteri banali (o, peggio, l'override dei criteri per ogni operazione utilizzando le opzioni di contesto), le funzioni di criteri predefinite ottengono la classe del modello dalla chiave passata e poi cercano variabili di classe specifiche nella classe del modello:

Variabile di classe Tipo Descrizione
_use_cache bool Specifica se memorizzare le entità nella cache in-process; sostituisce il criterio della cache in-process predefinito.
_use_memcache bool Specifica se memorizzare 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 memcache predefinito.

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

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