Max Ross
Según Wikipedia, el nivel de aislamiento de un sistema de gestión de bases de datos "define cómo y cuándo los cambios realizados por una operación se hacen visibles para otras operaciones simultáneas". El objetivo de este artículo es explicar el aislamiento de consultas y transacciones en Cloud Datastore, que usa App Engine. Después de leer este artículo, entenderás mejor cómo se comportan las lecturas y escrituras simultáneas, tanto dentro como fuera de las transacciones.
Transacciones internas: serializable
Los cuatro niveles de aislamiento, ordenados de mayor a menor, son los siguientes: Serializable, Repeatable Read, Read Committed y Read Uncommitted. Las transacciones de Datastore cumplen el nivel de aislamiento Serializable. Cada transacción está completamente aislada de todas las demás transacciones y operaciones del almacén de datos. Las transacciones de un grupo de entidades determinado se ejecutan de forma serial, una después de otra.
Consulta la sección Aislamiento y coherencia de la documentación de transacciones para obtener más información, así como el artículo de Wikipedia sobre el aislamiento de instantáneas.
Transacciones externas: lectura confirmada
Las operaciones de Datastore fuera de las transacciones se parecen mucho al nivel de aislamiento Read Committed. Las entidades recuperadas del almacén de datos mediante consultas o gets solo verán los datos confirmados. Una entidad recuperada nunca tendrá datos confirmados parcialmente (algunos de antes de una confirmación y otros de después). Sin embargo, la interacción entre las consultas y las transacciones es un poco más sutil, y para entenderla, debemos analizar el proceso de confirmación en profundidad.
Proceso de confirmación
Cuando una confirmación se devuelve correctamente, se garantiza que la transacción se aplicará, pero eso no significa que el resultado de tu escritura sea visible inmediatamente para los lectores. La aplicación de una transacción consiste en dos hitos clave:
- Hito A: el punto en el que se han aplicado los cambios a una entidad.
- Hito B: el punto en el que se han aplicado los cambios en los índices de esa entidad.

En Cloud Datastore, la transacción suele aplicarse por completo en unos cientos de milisegundos después de que se devuelva la confirmación. Sin embargo, aunque no se aplique por completo, las lecturas, escrituras y consultas de ancestros posteriores siempre reflejarán los resultados de la confirmación, ya que estas operaciones aplican las modificaciones pendientes antes de ejecutarse. Sin embargo, las consultas que abarcan varios grupos de entidades no pueden determinar si hay modificaciones pendientes antes de ejecutarse y pueden devolver resultados obsoletos o aplicados parcialmente.
Una solicitud que busque una entidad actualizada por su clave en un momento posterior al hito A tiene garantizada la versión más reciente de esa entidad. Sin embargo, si una solicitud simultánea ejecuta una consulta cuyo predicado (la cláusula WHERE
, para los fans de SQL y GQL) no se cumple en la entidad antes de la actualización, pero sí en la entidad después de la actualización, la entidad formará parte del conjunto de resultados solo si la consulta se ejecuta después de que la operación de aplicación haya alcanzado el hito B.
Es decir, durante breves periodos, es posible que un conjunto de resultados no incluya una entidad cuyas propiedades, según el resultado de una búsqueda por clave, cumplan el predicado de la consulta. También es posible que un conjunto de resultados incluyan una entidad cuyas propiedades, de nuevo de acuerdo con el resultado de una búsqueda por clave, no consigan satisfacer el predicado de la consulta. Una consulta no puede tener en cuenta las transacciones que se encuentran entre el hito A y el hito B a la hora de decidir qué entidades devolver. Se realizará con datos obsoletos, pero si se realiza una operación get()
en las claves devueltas, siempre se obtendrá la versión más reciente de esa entidad. Esto significa que es posible que no obtengas resultados que coincidan con tu consulta o que obtengas resultados que no coincidan una vez que obtengas la entidad correspondiente.
Hay situaciones en las que se garantiza que las modificaciones pendientes se aplican por completo antes de que se ejecute la consulta, como cualquier consulta de ancestro en Cloud Datastore. En este caso, los resultados de las consultas siempre estarán actualizados y serán coherentes.
Ejemplos
Hemos proporcionado una explicación general de cómo interactúan las actualizaciones y las consultas simultáneas, pero, si eres como yo, te resultará más fácil entender estos conceptos con ejemplos concretos. Veamos algunos ejemplos. Empezaremos con algunos ejemplos sencillos y terminaremos con los más interesantes.
Supongamos que tenemos una aplicación que almacena entidades Person. Un elemento Person tiene las siguientes propiedades:
- Nombre
- Altura
Esta aplicación admite las siguientes operaciones:
updatePerson()
getTallPeople()
, que devuelve todas las personas que miden más de 183 cm.
Tenemos dos entidades de persona en el almacén de datos:
- Alberto, que mide 1,72 metros.
- Bernardo, que mide 1,85 metros.
Ejemplo 1: aumento de la estatura de Alberto
Supongamos que una aplicación recibe dos solicitudes prácticamente al mismo tiempo. La primera de ellas consiste en cambiar la estatura de Alberto de 1,72 metros a 1,87 metros. ¡Menudo estirón! La segunda solicitud llama a getTallPeople(). ¿Qué devuelve getTallPeople()?
La respuesta depende de la relación entre los dos hitos de confirmación activados por la solicitud 1 y la consulta getTallPeople() ejecutada por la solicitud 2. Supongamos que el caso es el siguiente:
- Solicitud 1,
put()
- Solicitud 2,
getTallPeople()
- Solicitud 1,
put()
-->commit()
- Solicitud 1,
put()
-->commit()
-->hito A - Solicitud 1,
put()
-->commit()
-->hito B
En este caso, getTallPeople()
solo devolverá "Bob". ¿Por qué? Esto se debe a que aún no se ha aplicado la actualización de Adam que aumenta su altura, por lo que el cambio aún no es visible en la consulta que emitimos en la solicitud 2.
Supongamos ahora que el caso es el siguiente:
- Solicitud 1,
put()
- Solicitud 1,
put()
-->commit()
- Solicitud 1,
put()
-->commit()
-->hito A - Solicitud 2,
getTallPeople()
- Solicitud 1,
put()
-->commit()
-->hito B
En este caso, la consulta se ejecuta antes de que la solicitud 1 alcance el hito B, por lo que aún no se han aplicado las actualizaciones a los índices de Person. Por lo tanto, getTallPeople() solo devuelve a Bob. Este es un ejemplo de un conjunto de resultados que excluye una entidad cuyas propiedades cumplen el predicado de la consulta.
Ejemplo 2: Acortar el nombre de Bob (lo siento, Bob)
En este ejemplo, la solicitud 1 hará algo diferente. En lugar de aumentar la altura de Adam de 173 cm a 188 cm, reducirá la altura de Bob de 185 cm a 165 cm. Una vez más, ¿qué significa
getTallPeople()
- Solicitud 1,
put()
- Solicitud 2,
getTallPeople()
- Solicitud 1,
put()
-->commit()
- Solicitud 1,
put()
-->commit()
-->hito A - Solicitud 1,
put()
-->commit()
-->hito B
En este caso, getTallPeople()
solo devolverá Bob. ¿Por qué? Como
la actualización de Bob que reduce su altura aún no se ha confirmado,
el cambio aún no es visible para la consulta que emitimos en la solicitud 2.
Supongamos ahora que el caso es el siguiente:
- Solicitud 1,
put()
- Solicitud 1,
put()
-->commit()
- Solicitud 1,
put()
-->commit()
-->hito A - Solicitud 1,
put()
-->commit()
-->hito B - Solicitud 2,
getTallPeople()
En este caso, getTallPeople()
no devolverá ningún valor. ¿Por qué? Esto se debe a que la actualización de Bob que reduce su altura se ha confirmado cuando emitimos nuestra consulta en la solicitud 2.
Supongamos ahora que el caso es el siguiente:
- Solicitud 1,
put()
- Solicitud 1,
put()
-->commit()
- Solicitud 1,
put()
-->commit()
-->hito A - Solicitud 2,
getTallPeople()
- Solicitud 1,
put()
-->commit()
-->hito B
En este caso, la consulta se ejecuta antes del hito B, por lo que aún no se han aplicado las actualizaciones a los índices de Person. Por lo tanto,
getTallPeople()
sigue devolviendo Bob, pero la propiedad height de la entidad Person
que se devuelve es el valor actualizado: 65. Este es un ejemplo de un conjunto de resultados que incluye una entidad cuyas propiedades no cumplen el predicado de la consulta.
Conclusión
Como puedes ver en los ejemplos anteriores, el nivel de aislamiento de las transacciones de Cloud Datastore es muy similar al de lectura confirmada. Por supuesto, hay diferencias significativas, pero ahora que conoces estas diferencias y los motivos que las explican, deberías estar en una mejor posición para tomar decisiones de diseño inteligentes relacionadas con el almacén de datos en tus aplicaciones.