Almacenamiento en caché NDB

NDB gestiona cachés para ti. Existen dos niveles de almacenamiento en caché: una caché en contexto y una puerta de enlace 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 a fin de satisfacer necesidades avanzadas. Además, NDB implementa una característica llamada procesamiento por lotes automático, que trata de agrupar las operaciones para minimizar los viajes de ida y vuelta al servidor.

Introducción

El almacenamiento en caché ayuda a la mayoría de los tipos de aplicaciones. NDB almacena en caché automáticamente los datos que escribe o lee (a menos que una aplicación lo configure para no hacerlo). La lectura desde la memoria caché es más rápida que la lectura desde el almacén de datos.

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

Precaución: Cuando utilizas el lector del almacén de datos de la Consola del administrador para modificar los contenidos del almacén de datos, los valores almacenados en caché no se actualizarán. Por lo tanto, tu caché puede ser incoherente. Para la caché en contexto, por lo general, esto no es un problema. En Memcache, recomendamos usar la Consola del administrador para vaciar la caché.

Objetos de contexto

La administración de caché usa una clase llamada Context: cada subproceso y cada transacción se ejecutan en un contexto nuevo. Como cada solicitud HTTP entrante inicia un nuevo subproceso, cada una de ellas se ejecuta también con un nuevo contexto. 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 una variable global! Almacenarlo en una variable local o en un subproceso local es correcto.

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

La caché en contexto

La caché en contexto se mantiene solo durante un subproceso. Esto significa que cada solicitud HTTP entrante recibe una nueva caché en contexto y es "visible" solo para el código que maneja esa solicitud. Si tu aplicación genera subprocesos adicionales mientras maneja una solicitud, esos subprocesos también tendrán una caché nueva en contexto separada.

La caché en contexto es rápida, ya que vive en la memoria. Cuando una función NDB escribe en el almacén de datos, también escribe en la caché en contexto. Cuando una función NDB lee una entidad, primero verifica la caché en contexto. Si la entidad se encuentra allí, no tiene lugar la interacción con el almacén de datos.

Cuando una función NDB consulta el almacén de datos, la lista de resultados se recupera de allí. Sin embargo, si hay algún resultado individual en la caché en contexto, se usa ese resultado en lugar del valor recuperado de la consulta del almacén de datos. Los resultados de la consulta se vuelven a escribir en la caché en contexto si la política de caché así lo indica (pero nunca en Memcache).

La memoria caché en contexto puede consumir grandes cantidades de memoria con la ejecución de consultas de larga duración en tareas en segundo plano. 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 establecer una política que excluya las entidades que consumen más memoria.

Memcache

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

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

Memcache no admite transacciones. Por lo tanto, una actualización que se debe aplicar tanto al almacén de datos como a Memcache podría realizarse solo en uno de los dos. Para mantener la coherencia en tales casos (posiblemente a expensas del rendimiento), la entidad actualizada se borra de Memcache y luego se escribe en el almacén de datos. Una operación de lectura posterior encontrará la entidad que falta en Memcache, la recuperará del almacén de datos y luego la actualizará en Memcache como un efecto secundario de la lectura. Además, las lecturas NDB dentro de las transacciones ignoran Memcache.

Cuando se escriben entidades dentro de una transacción, no se usa Memcache; cuando la transacción se confirma, su contexto intentará borrar todas esas entidades de Memcache. Sin embargo, ten en cuenta que algunas fallas pueden evitar que se produzcan estas eliminaciones.

Funciones de política

El almacenamiento en caché automático es conveniente para la mayoría de las aplicaciones, pero tal vez tu aplicación sea inusual y desees desactivar el almacenamiento en caché automático para algunas o todas las entidades. Puedes controlar el comportamiento de las cachés configurando funciones de políticas. Existe una función de política para la caché en proceso, configurada con

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

y otra para Memcache, configurada 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, si deseas omitir la caché en proceso para todas las entidades de Account, puedes escribir

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

(Sin embargo, sigue leyendo para obtener una manera más fácil de lograr lo mismo). Para tu comodidad, puedes pasar True o False, en lugar de una función que siempre muestre el mismo valor. Las políticas predeterminadas almacenan en caché todas las entidades.

También existe una función de política de almacén de datos que establece qué entidades se escriben en este:

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

Esto funciona de manera similar a como lo hacen las funciones de caché en contexto y de las política de Memcache: si la función de política de Datastore muestra False para una clave determinada, la entidad correspondiente no se escribirá en Datastore. (Puede escribirse en la caché en proceso o en Memcache si sus funciones de política lo permiten). Este comportamiento puede ser útil en los casos para los que tengas datos similares a entidades que te gustaría almacenar en caché, pero que no necesitas almacenar en el almacén de datos. Al igual que con las políticas de caché, puedes pasar True o False, en lugar de una función que siempre muestre el mismo valor.

Memcache expira automáticamente los elementos cuando está bajo presión de memoria. Puedes configurar una función de política de tiempo de espera de Memcache para determinar la vida útil máxima de una entidad en la caché:

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

Se llama a esta función con un argumento de clave y debe mostrar un número entero que especifique la duración máxima en segundos. 0 o None significa indefinido (siempre que el servidor de Memcache tenga suficiente memoria). Para tu comodidad, simplemente puedes pasar una constante en número entero 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 existe una política de vida útil aparte para la caché en contexto: la vida útil de la caché es la misma que la de su contexto, una única solicitud HTTP entrante. Sin embargo, puedes borrar la caché en proceso con el siguiente llamado:
context = ndb.get_context()
context.clear_cache()

Un contexto nuevo comienza con una caché en proceso vacío.

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

  • No almacenes en caché las entidades que pertenecen a una clase de modelo específica.
  • Establece el tiempo de espera de Memcache para las entidades en esta clase de modelo dentro 30 segundos.
  • Las entidades de esta clase de modelo no necesitan escribirse en el almacén de datos.

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

Variable de clase TipoDescripción
_use_cache bool Especifica si se deben almacenar las 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 predeterminada de Memcache.
_use_datastore bool Especifica si se deben almacenar entidades en el almacén de datos; anula la política de almacén de datos predeterminada.
_memcache_timeout int Duración máxima de las entidades en Memcache; anula la política de tiempo de espera de Memcache predeterminada.

Nota: Esta es una característica de la función de política predeterminada para cada política. Si especificas tu propia función de política, pero también deseas usar la política predeterminada como respaldo, llama a las funciones de política predeterminada de manera 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)