Almacenamiento en caché de NDB

NDB gestiona las cachés por ti. Hay dos niveles de almacenamiento en caché: una caché en contexto y una pasarela al servicio de almacenamiento en caché estándar de App Engine, Memcache. Ambas cachés están habilitadas de forma predeterminada para todos los tipos de entidades, pero se pueden configurar para satisfacer necesidades avanzadas. Además, NDB implementa una función llamada procesamiento automático por lotes, que intenta agrupar operaciones para minimizar los viajes de ida y vuelta del servidor.

Introducción

El almacenamiento en caché ayuda a la mayoría de los tipos de aplicaciones. NDB almacena automáticamente en caché los datos que escribe o lee (a menos que una aplicación configure que no lo haga). Leer de la caché es más rápido que leer de Datastore.

Puedes modificar el comportamiento de almacenamiento en caché de muchas funciones de NDB pasando argumentos de opciones de contexto. Por ejemplo, puedes llamar a key.get(use_cache=False, use_memcache=False) para saltarte el almacenamiento en caché. También puedes cambiar la política de almacenamiento en caché predeterminada en un contexto de NDB, tal como se describe a continuación.

Precaución: Si usas el Visor de Datastore de la consola de administración para modificar el contenido de Datastore, los valores almacenados en caché no se actualizarán. Por lo tanto, es posible que tu caché no sea coherente. Por lo general, esto no supone un problema para la caché en contexto. En el caso de Memcache, te recomendamos que utilices la consola de administración para vaciar la caché.

Objetos de contexto

La gestión de la caché usa una clase llamada Context: cada hilo y cada transacción se ejecutan en un nuevo contexto. Como cada solicitud HTTP entrante inicia un nuevo subproceso, cada solicitud se ejecuta con un contexto nuevo. Para acceder al contexto actual, usa la función ndb.get_context().

Precaución: No tiene sentido compartir objetos Context entre varios subprocesos o solicitudes. No guardes el contexto como variable global. Puedes almacenarlo en una variable local o local de subproceso.

Los objetos de contexto tienen métodos para definir políticas de caché y manipular la caché de otras formas.

Caché en contexto

La caché contextual solo se mantiene durante la duración de un único hilo. Esto significa que a cada solicitud HTTP entrante se le asigna una nueva caché en contexto y solo es "visible" para el código que gestiona esa solicitud. Si tu aplicación genera subprocesos adicionales al gestionar una solicitud, esos subprocesos también tendrán una caché en contexto nueva e independiente.

La caché contextual es rápida y se almacena en la memoria. Cuando una función de NDB escribe en Datastore, también escribe en la caché en contexto. Cuando una función de NDB lee una entidad, primero comprueba la caché en contexto. Si se encuentra la entidad, no se produce ninguna interacción con Datastore.

Cuando una función de NDB consulta Datastore, la lista de resultados se obtiene de Datastore. Sin embargo, si algún resultado individual está en la caché en contexto, se usará en lugar del valor obtenido de la consulta de Datastore. Los resultados de las consultas se vuelven a escribir en la caché contextual si la política de caché lo indica (pero nunca en Memcache).

Al ejecutar consultas de larga duración en tareas en segundo plano, es posible que la caché en contexto consuma grandes cantidades de memoria. Esto se debe a que la caché mantiene una copia de cada entidad que se recupera o almacena en el contexto actual. Para evitar excepciones de memoria en tareas de larga duración, puedes inhabilitar la caché o definir una política que excluya las entidades que consuman más memoria.

Memcache

Memcache es el servicio de almacenamiento en caché estándar de App Engine, mucho más rápido que Datastore, pero más lento que la caché en contexto (milisegundos frente a microsegundos).

De forma predeterminada, un contexto no transaccional almacena en caché todas las entidades en memcache. Todos los contextos de una aplicación usan el mismo servidor de memcache y ven un conjunto coherente de valores almacenados en caché.

Memcache no admite transacciones. Por lo tanto, es posible que una actualización que se deba aplicar tanto a Datastore como a memcache solo se aplique a uno de los dos. Para mantener la coherencia en estos casos (posiblemente a costa del rendimiento), la entidad actualizada se elimina de la memoria caché y, a continuación, se escribe en Datastore. En una operación de lectura posterior, se comprobará que la entidad no está en Memcache, se recuperará del almacén de datos y, como efecto secundario de la lectura, se actualizará en Memcache. Además, las lecturas de NDB dentro de las transacciones ignoran Memcache.

Cuando se escriben entidades en una transacción, no se usa memcache. Cuando se confirma la transacción, su contexto intenta eliminar todas las entidades de memcache. Sin embargo, ten en cuenta que algunos errores pueden impedir que se produzcan estas eliminaciones.

Funciones de las políticas

El almacenamiento automático en caché es práctico para la mayoría de las aplicaciones, pero puede que tu aplicación sea inusual y quieras desactivar el almacenamiento automático en caché de algunas o de todas las entidades. Puedes controlar el comportamiento de las cachés configurando funciones de política. Hay una función de política para la caché en proceso, definida con

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

y otra para memcache, definida con

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

Cada función de política acepta una clave y devuelve un resultado booleano. Si devuelve False, la entidad identificada por esa clave no se guardará en la caché correspondiente. Por ejemplo, para saltarte la caché en proceso de todas las entidades Account, puedes escribir lo siguiente:

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

Sin embargo, sigue leyendo para descubrir una forma más sencilla de conseguir lo mismo. Para mayor comodidad, puedes usar True o False en lugar de una función que siempre devuelva el mismo valor. Las políticas predeterminadas almacenan en caché todas las entidades.

También hay una función de política de Datastore que rige qué entidades se escriben en Datastore:

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

Funciona como las funciones de política de caché en contexto y de memcache: si la función de política de Datastore devuelve False para una clave determinada, la entidad correspondiente no se escribirá en Datastore. (Puede escribirse en la caché del proceso o en memcache si las funciones de sus políticas lo permiten). Esto puede ser útil en los casos en los que tengas datos similares a entidades que quieras almacenar en caché, pero que no necesites almacenar en Datastore. Al igual que con las políticas de caché, puedes usar True o False en lugar de una función que siempre devuelva el mismo valor.

Memcache caduca automáticamente los elementos cuando la memoria está sobrecargada. Puedes definir una función de política de tiempo de espera de memcache para determinar el tiempo de vida máximo de una entidad en la caché:

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

Esta función se llama con un argumento de clave y debe devolver un número entero que especifique el tiempo de vida máximo en segundos. 0 o None significa indefinido (siempre que el servidor de memcache tenga suficiente memoria). Para mayor comodidad, puedes pasar una constante entera en lugar de una función que siempre devuelva el mismo valor. Consulta la documentación de memcache para obtener más información sobre los tiempos de espera.

Nota: No hay una política de tiempo de vida independiente para la caché en contexto: el tiempo de vida de la caché es el mismo que el de su contexto, una única solicitud HTTP entrante. Sin embargo, puedes borrar la caché en proceso llamando a
context = ndb.get_context()
context.clear_cache()

Un contexto nuevo empieza con una caché en proceso vacía.

Aunque las funciones de las políticas son muy flexibles, en la práctica, la mayoría de las políticas son sencillas. Por ejemplo,

  • No almacena en caché las entidades que pertenecen a una clase de modelo específica.
  • Define el tiempo de espera de memcache para las entidades de esta clase de modelo en 30 segundos.
  • No es necesario escribir las entidades de esta clase de modelo en Datastore.

Para ahorrarte el trabajo de escribir y actualizar continuamente funciones de políticas triviales (o, lo que es peor, de anular las políticas de cada operación mediante opciones de contexto), las funciones de políticas predeterminadas obtienen la clase de modelo de la clave que se les ha pasado y, a continuación, buscan en la clase de modelo variables de clase específicas:

Variable de clase Tipo Descripción
_use_cache bool Especifica si se deben almacenar entidades en la caché en proceso. Anula la política de caché en proceso predeterminada.
_use_memcache bool Especifica si se deben almacenar entidades en memcache. Anula la política de memcache predeterminada.
_use_datastore bool Especifica si se deben almacenar entidades en el almacén de datos. Anula la política predeterminada de Datastore.
_memcache_timeout int Tiempo de vida máximo de las entidades en memcache. Anula la política de tiempo de espera predeterminada de memcache.

Nota: Esta es una función de la política predeterminada de cada política. Si especificas tu propia función de política, pero también quieres recurrir a la política predeterminada, llama a las funciones de política predeterminadas de forma explícita como métodos estáticos de la clase Context:

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