Cómo equilibrar la coherencia sólida y eventual con Google Cloud Datastore

Cómo brindar una experiencia del usuario coherente y aprovechar el modelo de coherencia eventual para escalar conjuntos de datos grandes

En este documento, se describe cómo lograr una coherencia sólida a fin de brindar una experiencia del usuario positiva, mediante el modelo de coherencia eventual de Google Cloud Datastore para manejar grandes cantidades de datos y usuarios.

Este documento está diseñado para ingenieros y arquitectos de software que deseen desarrollar soluciones en Cloud Datastore. Como ayuda para los lectores que están más familiarizados con las bases de datos relacionales que con sistemas no relacionales como el de Cloud Datastore, este documento menciona los conceptos análogos de las bases de datos relacionales. Se da por sentado que el lector tiene conocimientos básicos de Cloud Datastore. La forma más fácil de incursionar en Cloud Datastore es mediante Google App Engine, usando uno de los lenguajes compatibles. Si todavía no usaste App Engine, sugerimos que leas la guía de introducción y la sección Cómo almacenar datos correspondiente a uno de esos lenguajes. Si bien se usan fragmentos de código de Python como ejemplo, no es necesario que tengas experiencia en Python para entender este documento.

Nota: Los fragmentos de código de este artículo usan la biblioteca cliente Python DB, que ya no se aconseja. Recomendamos enfáticamente a los desarrolladores de aplicaciones nuevas que usen la biblioteca cliente NDB, que brinda varios beneficios que no están disponibles con la otra, como el almacenamiento en caché automático de entidades mediante la API de Memcache. Si actualmente usas la biblioteca cliente DB anterior, lee la guía de migración de DB a NDB.

Contenido

NoSQL y la coherencia eventual
Coherencia eventual en Cloud Datastore
Consulta principal y grupo de entidad
Limitaciones del grupo de entidad y la consulta principal
Alternativas a las consultas principales
Cómo reducir el tiempo para lograr una coherencia completa
Conclusión
Recursos adicionales

NoSQL y la coherencia eventual

Las bases de datos no relacionales, también conocidas como bases de datos NoSQL, surgieron en los últimos años como una alternativa a las bases de datos relacionales. Cloud Datastore es una de las bases de datos no relacionales más utilizadas en la industria. En 2013, Cloud Datastore procesó 4.5 billones de transacciones por mes (entrada de blog de Google Cloud Platform). Cloud Datastore les ofrece a los desarrolladores una forma más simple de almacenar datos y acceder a ellos. Su esquema flexible se correlaciona de manera natural con los lenguajes de programación y los orientados a objetos. Además, ofrece algunas funciones para las que las bases de datos relacionales no son idóneas, como altos niveles de rendimiento y confiabilidad a muy gran escala.

Diseñar un sistema que aproveche las bases de datos no relacionales puede ser un desafío para los desarrolladores que están más acostumbrados a las bases de datos relacionales, ya que posiblemente no estén muy familiarizados con algunas de sus características y prácticas. Si bien el modelo de programación de Cloud Datastore es simple, es muy importante conocer estas características. El tema principal en este documento es cómo programar para una de esas características: la coherencia eventual.

¿Qué es la coherencia eventual?

La coherencia eventual es una garantía teórica de que, mientras no se realicen actualizaciones nuevas en una entidad, todas sus lecturas mostrarán el último valor actualizado finalmente. El sistema de nombres de dominio (DNS) de Internet es un buen ejemplo de un sistema con un modelo de coherencia eventual. Los servidores DNS no siempre reflejan los valores más recientes, que se almacenan en caché y se replican en muchos directorios de Internet. Replicar los valores modificados en todos los clientes y servidores DNS lleva cierto tiempo. Sin embargo, el sistema DNS es tan eficaz que se convirtió una de las bases de Internet. Es un sistema con alta disponibilidad que demostró tener una escalabilidad extraordinaria, lo que permite realizar consultas en más de cien millones de dispositivos en toda la Internet.

En la figura 1, se ilustra el concepto de replicación con coherencia eventual. El diagrama muestra que, si bien siempre se pueden leer las réplicas, es posible que algunas no sean coherentes con la escritura más reciente en el nodo de origen en un momento determinado. En el diagrama, el Nodo A es el de origen y los nodos B y C son réplicas.

Figura 1: Descripción conceptual de replicación con coherencia eventual

En cambio, las bases de datos relacionales tradicionales se diseñaron con base en el concepto de coherencia sólida, que también se denomina coherencia inmediata. Esto significa que todos los observadores de una entidad verán los mismos datos inmediatamente después de una actualización. Esta característica fue una premisa fundamental para muchos desarrolladores que utilizan bases de datos relacionales. Sin embargo, los desarrolladores deben hacer concesiones en cuanto a la escalabilidad y el rendimiento de la aplicación para poder tener una coherencia sólida. Dicho simplemente, es necesario bloquear los datos durante el período de actualización o proceso de replicación para garantizar que ningún otro proceso actualice los mismos datos.

En la figura 2, se muestra una vista conceptual de la topología de implementación y el proceso de replicación con la coherencia sólida. En este diagrama, se observa que las réplicas siempre tienen valores coherentes con el nodo de origen, pero no se puede acceder a ellas antes de que termine la actualización.

Figura 2: Descripción conceptual de replicación con coherencia sólida

Cómo equilibrar la coherencia sólida y eventual

Recientemente, las bases de datos no relacionales se volvieron populares, en particular para las aplicaciones web que requieren escalabilidad alta y un rendimiento con mucha disponibilidad. Con estas bases de datos, los desarrolladores pueden seleccionar un equilibrio óptimo entre coherencia sólida y eventual para cada aplicación. De esta manera, pueden combinar lo mejor de ambos modos. Por ejemplo, en casos prácticos como “saber cuáles de tus amigos están en línea en un momento específico” o “cuántos usuarios le pusieron +1 a tu publicación”, la información no requiere coherencia sólida. Por lo tanto, podemos aprovechar la coherencia eventual para maximizar la escalabilidad y el rendimiento. Hay otros casos prácticos en los que la información requiere coherencia sólida, como saber “si un usuario terminó o no el proceso de facturación” o “cuántos puntos obtuvo un jugador en una batalla”.

En términos generales, podemos decir que la coherencia eventual suele ser el mejor modelo en casos prácticos en los que hay una gran cantidad de entidades. Si una consulta tiene una gran cantidad de resultados, la experiencia del usuario no se verá afectada cuando se incluyan o excluyan entidades específicas. En contraposición, en los casos prácticos en los que hay una cantidad pequeña de entidades en un contexto más acotado, probablemente necesitemos coherencia sólida. Esto se debe a que los usuarios sabrán qué entidades deben incluirse o excluirse, lo cual puede afectar su experiencia.

Por estos motivos, es importante que los desarrolladores comprendan las características de las bases de datos no relacionales de Cloud Datastore. En las siguientes secciones, se explicará cómo combinar los modelos de coherencia sólida y eventual para compilar una aplicación escalable con alta disponibilidad y rendimiento. De esta manera, se cumplirán los requisitos de coherencia para brindar una experiencia del usuario positiva.

Coherencia eventual en Cloud Datastore

Debes seleccionar la API correcta cuando se requiere una vista de los datos con coherencia sólida. En la tabla 1, se muestran las distintas variedades de las API de consultas en Cloud Datastore y sus modelos de coherencia correspondientes.

API de Cloud Datastore

Lectura del valor de la entidad

Lectura de índice

Consulta global

Coherencia eventual

Coherencia eventual

Consulta global de solo clave

N/A

Coherencia eventual

Consulta principal

Coherencia sólida

Coherencia sólida

Búsqueda por clave (get())

Coherencia sólida

N/A

Tabla 1: Consultas y llamadas a get de Cloud Datastore y los comportamientos de coherencia posibles

Las consultas de Cloud Datastore sin principal se denominan consultas globales, y están diseñadas para funcionar con un modelo de coherencia eventual. Esto no garantiza la coherencia sólida. Una consulta global de solo clave es una consulta global que muestra solo las claves de entidades que coinciden con la consulta, pero no muestra los valores de sus atributos. En una consulta principal, el alcance se determina en función de una entidad principal. En las siguientes secciones, se describe detalladamente el comportamiento de cada tipo de coherencia.

Coherencia eventual en la lectura de valores de entidades

A excepción de las consultas principales, es posible que el valor de una entidad actualizada no se muestre inmediatamente cuando se ejecuta una consulta. Para comprender el impacto de la coherencia eventual en la lectura de valores de entidades, imaginemos una situación en la que tenemos una entidad (Jugador) con una propiedad (Puntuación). Supongamos que la Puntuación inicial tiene un valor de 100. Después de un tiempo, este valor se actualiza a 200. Si se ejecuta una consulta global que incluye la misma entidad Jugador en el resultado, es posible que el valor de la propiedad Puntuación de la entidad mostrada siga siendo 100.

Este comportamiento se debe a la replicación entre los servidores de Cloud Datastore. Cloud Bigtable y Megastore, las tecnologías subyacentes de Cloud Datastore, administran la replicación (consulta Recursos adicionales para obtener más información sobre Cloud Bigtable y Megastore). La replicación se ejecuta con el algoritmo Paxos, que espera de manera síncrona hasta que la mayoría de las réplicas hayan confirmado la solicitud de actualización. La réplica se actualiza con datos de la solicitud luego de un período determinado. Este período de tiempo suele ser corto, pero no se puede saber con certeza cuánto durará. Es posible que una consulta lea los datos obsoletos si se la ejecuta antes de que finalice la actualización.

En muchos casos, esta actualización habrá llegado a todas las réplicas rápidamente. Sin embargo, existen varios factores, que en conjunto, podrían aumentar el tiempo necesario para lograr la coherencia. Estos factores incluyen cualquier incidente que afecte a todo un centro de datos y requiera el cambio de una gran cantidad de servidores a otro centro de datos. Debido a la variabilidad de estos factores, no es posible establecer con certeza cuánto tiempo debe transcurrir para que se establezca la coherencia completa.

Generalmente, el tiempo necesario para que una consulta muestre el valor más reciente es muy corto. Sin embargo, el tiempo puede ser más prolongado en determinadas situaciones en las que aumenta la latencia de la replicación. Las aplicaciones que utilizan consultas globales de Cloud Datastore deben diseñarse cuidadosamente para que manejen estos casos de manera elegante.

Se puede evitar la coherencia eventual para leer valores de entidades mediante una consulta de solo clave, una consulta principal o una búsqueda por clave (el método get()). A continuación, analizaremos con más detalle estos tipos de consulta diferentes.

Coherencia eventual para leer un índice

Es posible que un índice no esté actualizado al momento de ejecutar una consulta global. Esto significa que, aunque puedas leer los últimos valores de propiedad de las entidades, tal vez no sea posible filtrar la “lista de entidades” incluida en el resultado de la consulta en función de los valores de índice anteriores.

Para comprender el impacto de la coherencia eventual en la lectura de un índice, imagina la situación en la que se ingresa en Cloud Datastore una nueva entidad (Jugador). La entidad tiene una propiedad (Puntuación), cuyo valor inicial es 300. Inmediatamente después de ingresar la entidad, ejecutas una consulta de solo clave para obtener todas las entidades con un valor mayor que 0 en Puntuación. Cabría esperar que la entidad Jugador, que se acaba de ingresar, aparezca en los resultados de la consulta. Pero es posible que, inesperadamente, esta entidad no aparezca en los resultados. Esto puede suceder cuando la tabla de índice de la propiedad Puntuación no está actualizada con el valor nuevo ingresado en el momento en que se ejecuta la consulta.

Recuerda que todas las consultas en Cloud Datastore se ejecutan en las tablas de índice, pero estas tablas se actualizan de forma asíncrona. Básicamente, cada actualización de entidad consta de dos fases. En la primera, la fase de confirmación, se realiza una escritura en el registro de transacciones. En la segunda, se escriben los datos y se actualizan los índices. Si la fase de confirmación se realiza correctamente, la fase de escritura también será exitosa, pero es posible que no suceda inmediatamente. Si consultas una entidad antes de que se actualicen los índices, es posible que veas datos que no son coherentes todavía.

Como resultado de este proceso de dos fases, existe una demora antes de que se puedan ver las últimas actualizaciones de las entidades en las consultas globales. Al igual que en la coherencia eventual de los valores de entidades, la demora suele ser breve, pero podría prolongarse (en casos excepcionales, puede tardar minutos o más).

Puede suceder lo mismo luego de las actualizaciones. Por ejemplo, supongamos que actualizas una entidad existente (Jugador) con un valor nuevo (0) para la propiedad Puntuación, y ejecutas la misma consulta inmediatamente después. Cabría esperar que la entidad no aparezca en los resultados de la consulta, ya que el nuevo valor de Puntuación (0) la excluiría. Sin embargo, debido al mismo comportamiento de actualización de índice asíncrono, todavía es posible que se incluya la entidad en el resultado.

La única forma de evitar la coherencia eventual en la lectura de un índice es usar una consulta principal o el método de búsqueda por clave. Una consulta de solo clave no puede evitar este comportamiento.

Coherencia sólida en la lectura de índices y valores de entidades

En Cloud Datastore, solo hay dos API que proporcionan una vista con coherencia sólida para leer índices y valores de las entidades: (1) el método de búsqueda por clave y (2) la consulta principal. Si la lógica de la aplicación requiere coherencia sólida, el desarrollador deberá utilizar uno de estos métodos para leer las entidades de Cloud Datastore.

Cloud Datastore está diseñado específicamente para proporcionar coherencia sólida en estas API. Cuando llamas a una de ellas, Cloud Datastore limpia todas las actualizaciones pendientes en una de las réplicas y tablas de índice, y luego ejecutará la búsqueda o la consulta principal. De este modo, se mostrará el valor más reciente de la entidad, según la tabla de índice actualizada, con los valores correspondientes a las actualizaciones más recientes.

A diferencia de las consultas, la llamada de búsqueda por clave solo muestra una entidad o un conjunto de entidades especificadas por una clave o un conjunto de claves. Esto significa que una consulta principal es la única manera que tiene Cloud Datastore para cumplir con el requisito de coherencia sólida, junto con un requisito de filtro. Sin embargo, las consultas principales no funcionan si no se especifica un grupo de entidad.

Consulta principal y grupo de entidad

Como se mencionó al principio de este documento, una de las ventajas de Cloud Datastore es que los desarrolladores pueden encontrar un equilibrio óptimo entre los modelos de coherencia sólida y eventual. En Cloud Datastore, un grupo de entidad es una unidad con coherencia sólida, transaccionalidad y localidad. Con los grupos de entidad, los desarrolladores pueden definir el alcance de la coherencia sólida entre las entidades en una aplicación. De esta forma, la aplicación puede mantener la coherencia dentro del grupo de entidad y ofrecer a la vez alta escalabilidad, disponibilidad y rendimiento como un sistema completo.

Un grupo de entidad es una jerarquía formada por una entidad raíz y entidades secundarias o sucesoras.[1] Para crear un grupo de entidad, un desarrollador especifica una ruta principal, que es una serie de claves principales que preceden a la clave secundaria. En la figura 3, se ilustra el concepto de grupo de entidad. En este caso, la entidad raíz con la clave “ateam” tiene dos entidades secundarias con las claves “ateam/098745” y “ateam/098746”.

Figura 3: Esquema del concepto de grupo de entidad

Dentro del grupo de entidad, se garantizan las siguientes características:

  • Coherencia sólida
    • Una consulta principal sobre el grupo de entidad mostrará un resultado con coherencia sólida. De esta manera, refleja los valores más recientes de las entidades filtrados según el último estado del índice.
  • Transaccionalidad
    • Cuando se delimita una transacción de manera programática, el grupo de entidad proporciona características ACID (atomicidad, coherencia, aislamiento y durabilidad) en la transacción.
  • Localidad
    • Las entidades de un grupo se almacenan en ubicaciones cercanas físicamente en los servidores de Cloud Datastore, ya que todas las entidades se clasifican y almacenan según el orden lexicográfico de las claves. Esto permite que una consulta principal analice rápidamente el grupo de entidad con un mínimo de E/S.

Una consulta principal es una forma especial de consulta que solo se ejecuta en un grupo de entidad especificado. Se ejecuta con coherencia sólida. Detrás de escena, Cloud Datastore se asegura de que se apliquen todas las replicaciones y actualizaciones de índice pendientes antes de ejecutar la consulta.

Ejemplo de una consulta principal

En esta sección, se describe cómo utilizar grupos de entidad y consultas principales en la práctica. En el siguiente ejemplo, analizaremos el problema de la administración de registros de datos de personas. Supongamos que tenemos un código que agrega una entidad de un tipo específico seguida inmediatamente de una consulta sobre ese tipo. A continuación, este concepto se demuestra con el código de Python de ejemplo.

# Define the Person entity
class Person(db.Model):
    given_name = db.StringProperty()
    surname = db.StringProperty()
    organization = db.StringProperty()
# Add a person and retrieve the list of all people
class MainPage(webapp2.RequestHandler):
    def post(self):
        person = Person(given_name='GI', surname='Joe', organization='ATeam')
        person.put()
        q = db.GqlQuery("SELECT * FROM Person")
        people = []
        for p in q.run():
            people.append({'given_name': p.given_name,
                        'surname': p.surname,
                        'organization': p.organization})

El problema con este código es que, en la mayoría de los casos, no mostrará la entidad que se agregó en la instrucción anterior. Dado que la consulta continúa en la línea que sigue inmediatamente después de la inserción, el índice no se habrá actualizado cuando se ejecute la consulta. Sin embargo, también existe un problema con la validez de este caso práctico: ¿Existe realmente la necesidad de mostrar una lista de todas las personas en una página sin contexto? ¿Y si hay un millón de personas? Se demorará mucho tiempo para mostrar la página.

La naturaleza del caso práctico sugiere que debemos proporcionar contexto para acotar la consulta. En este ejemplo, usaremos el contexto de la organización. De esta forma, podemos usar la organización como un grupo de entidad y ejecutar una consulta principal, que resuelve nuestro problema de coherencia. Esto se demuestra en el siguiente código de Python.

class Organization(db.Model):
    name = db.StringProperty()
class Person(db.Model):
    given_name = db.StringProperty()
    surname = db.StringProperty()
class MainPage(webapp2.RequestHandler):
    def post(self):
        org = Organization.get_or_insert('ateam', name='ATeam')
        person = Person(parent=org)
        person.given_name='GI'
        person.surname='Joe'
        person.put()
        q = db.GqlQuery("SELECT * FROM Person WHERE ANCESTOR IS :1 ", org)
        people = []
        for p in q.run():
            people.append({'given_name': p.given_name,
                        'surname': p.surname})

Ahora que especificamos la organización principal en GqlQuery, la consulta muestra la entidad ingresada. El ejemplo se podría extender para concentrarnos en una persona individual mediante una consulta del nombre de la persona que incluya el valor principal. Como alternativa, esto también se podría haber hecho guardando la clave de entidad y usándola para el desglose con una búsqueda por clave.

Cómo mantener la coherencia entre Memcache y Cloud Datastore

Los grupos de entidad se pueden utilizar como unidad para mantener la coherencia entre las entidades de Memcache y las de Cloud Datastore. Como ejemplo, consideremos una situación en la que cuentas la cantidad de personas de cada equipo y almacenas los valores resultantes en Memcache. Para asegurarnos de que los datos almacenados en caché sean coherentes con los valores más recientes de Cloud Datastore, puedes usar los metadatos del grupo de entidad. Estos metadatos muestran la última versión del grupo de entidad especificado. Puedes comparar el número de versión con el número almacenado en Memcache. Este método te permite detectar un cambio en cualquiera de las entidades del grupo mediante la lectura de un conjunto de metadatos, en vez de analizar todas las entidades del grupo de forma individual.

Limitaciones del grupo de entidad y la consulta principal

El enfoque en el que se usan los grupos de entidad y las consultas principales no es una solución mágica. A continuación, se detallan los dos desafíos que dificultan la aplicación de esta técnica en la práctica.

  1. Existe un límite de escritura de una actualización por segundo por cada grupo de entidad.
  2. No se puede cambiar la relación del grupo de entidad luego de su creación.

Límite de escrituras

Un desafío importante es que el sistema debe estar diseñado para contener la cantidad de actualizaciones (o transacciones) de cada grupo de entidad. El límite admitido es una por segundo por grupo de entidad.[2] En caso de que se necesite una cantidad de actualizaciones superior a ese límite, podría generarse un cuello de botella en el rendimiento del grupo de entidad.

En el ejemplo anterior, es posible que cada organización deba actualizar el registro de cualquier persona de la organización. Imaginemos la situación en la que “ateam” contiene 1,000 personas y cada una tiene una actualización por segundo de alguna de sus propiedades. Como resultado, puede haber hasta 1,000 actualizaciones por segundo en el grupo de entidad, lo cual sería imposible debido al límite de actualizaciones. Esto indica que es importante seleccionar un diseño de grupo de entidad adecuado que tenga en cuenta los requisitos de rendimiento. Este es uno de los desafíos para encontrar el equilibrio óptimo entre los modelos de coherencia eventual y sólida.

Inmutabilidad de las relaciones del grupo de entidad

El segundo desafío es la inmutabilidad de las relaciones del grupo de entidad. Esta relación se constituye de manera estática en función del nombre de la clave. No se puede cambiar después de crear la entidad. La única opción disponible para cambiar la relación es borrar las entidades del grupo y volver a crearlas. Este problema nos impide usar grupos de entidad con el fin de definir alcances ad-hoc para la coherencia o la transaccionalidad de forma dinámica. En cambio, estos alcances están estrechamente relacionados con el grupo de entidad estático definido al momento del diseño.

Imaginemos, por ejemplo, una situación en la que se desea implementar una transferencia bancaria entre dos cuentas. Se trata de una situación de tipo comercial que requiere coherencia sólida y transaccionalidad. Sin embargo, no es posible crear un grupo de entidad a último minuto con estas dos cuentas ni usar un elemento superior global. Ese grupo de entidad generaría un cuello de botella para todo el sistema, lo cual dificultaría la ejecución de otras solicitudes de transferencia bancaria. En conclusión, los grupos de entidad no se pueden utilizar de esta forma.

Existe una forma alternativa para implementar una transferencia bancaria de manera altamente escalable y disponible. En vez de colocar todas las cuentas en un solo grupo de entidad, puedes crear un grupo para cada cuenta. Esto te permite usar transacciones para garantizar las actualizaciones ACID en ambas cuentas bancarias. Las transacciones son una característica de Cloud Datastore que te permite crear conjuntos de operaciones con características ACID para hasta veinticinco grupos de entidad. Ten en cuenta que debes usar las consultas de coherencia sólida, como búsquedas por clave y consultas principales, en una transacción. Para obtener más información sobre las restricciones de las transacciones, consulta la sección Transacciones y grupos de entidad.

Alternativas a las consultas principales

Si ya tienes una aplicación con una gran cantidad de entidades almacenadas en Cloud Datastore, podría ser complicado incorporar grupos de entidad luego en una refactorización. Sería necesario borrar todas las entidades y agregarlas en una relación de grupo de entidad. En el modelado de datos de Cloud Datastore, es muy importante tomar una decisión sobre el diseño del grupo de entidad en la primera etapa del diseño de la aplicación. De lo contrario, es posible que estés limitado para lograr un determinado nivel de coherencia durante una refactorización, como realizar una consulta de solo clave seguida de una búsqueda por clave o usar Memcache.

Consulta global de solo clave seguida de una búsqueda por clave

Una consulta global de solo clave es un tipo especial de consulta global que muestra únicamente las claves, sin los valores de las propiedades de las entidades. Como los valores que se muestran son solo claves, la consulta no involucra los valores de una entidad, que podrían tener problemas de coherencia. La combinación de una consulta global de solo clave con el método de búsqueda permite leer los valores de entidades recientes. Debemos señalar que una consulta global de solo clave no puede excluir la posibilidad de un índice no coherente al momento de la consulta, lo que puede dar lugar a que la entidad no se recupere. El resultado de la consulta podría generarse potencialmente en función del filtro de los valores de índice anteriores. En conclusión, un desarrollador puede usar una consulta global de solo clave seguida de una búsqueda por clave solo cuando el requisito de la aplicación permite que el valor del índice no sea coherente al momento de la consulta.

Cómo usar Memcache

El servicio Memcache es volátil, pero tiene coherencia sólida. Cuando se combinan las búsquedas de Memcache y las consultas de Cloud Datastore, es posible crear un sistema que minimice los problemas de coherencia la mayor parte del tiempo.

Como ejemplo, imaginemos la situación de una aplicación de juego que mantiene una lista de entidades Jugador, cada una de las cuales tiene una puntuación mayor que cero.

  • Las solicitudes de inserción o actualización se aplican a la lista de entidades Jugador en Memcache y en Cloud Datastore.
  • Cuando hay solicitudes de consulta, se lee la lista de entidades Jugador de Memcache y se ejecuta una consulta de solo clave en Cloud Datastore cuando la lista no está presente en Memcache.

La lista que se muestra será coherente siempre que la lista en caché esté presente en Memcache. Si se expulsó la entrada o el servicio Memcache no está disponible temporalmente, es posible que el sistema deba leer el valor de una consulta de Cloud Datastore que posiblemente podría mostrar un resultado incoherente. Esta técnica se puede usar para cualquier aplicación que admita un poco de incoherencia.

Estas son algunas recomendaciones para cuando se utiliza Memcache como una capa de almacenamiento en caché para Cloud Datastore.

  • Detecta las excepciones y los errores de Memcache para mantener la coherencia entre el valor de Memcache y el de Cloud Datastore. Si recibes una excepción cuando actualizadas la entrada en Memcache, asegúrate de invalidar la entrada anterior en Memcache. De lo contrario, puede haber valores diferentes para una entidad (un valor anterior en Memcache y uno nuevo en Cloud Datastore).
  • Establece un período de vencimiento en las entradas de Memcache. Recomendamos establecer períodos cortos de vencimiento para minimizar la posibilidad de que se produzca una incoherencia en caso de excepciones de Memcache.
  • Utiliza la función de comparar y establecer cuando actualices las entradas para el control de simultaneidad. Esto ayudará a evitar la interferencia entre actualizaciones simultáneas de la misma entrada.

Migración gradual a grupos de entidad

Las sugerencias detalladas en la sección anterior solo disminuyen la posibilidad de un comportamiento incoherente. Cuando se requiere coherencia sólida, lo ideal es diseñar la aplicación en función de grupos de entidad y consultas principales. Sin embargo, en algunos casos no es factible migrar una aplicación existente, lo que puede implicar cambiar el modelo de datos y la lógica de aplicación existentes para usar consultas principales en lugar de consultas globales. Una forma de lograrlo es realizar un proceso de transición gradual como el siguiente:

  1. Identifica y prioriza las funciones de la aplicación que necesitan coherencia sólida.
  2. Complementa (no reemplaces) la lógica existente de las funciones insert() o update() con una lógica nueva que use grupos de entidad. De esta manera, las inserciones o actualizaciones posteriores de entidades del modelo anterior y grupos de entidad del modelo nuevo podrán manejarse con la función correspondiente.
  3. Modifica la lógica existente de las funciones de lectura o consulta. Las consultas principales se ejecutan primero si existe un grupo de entidad nuevo para la solicitud. Ejecuta la consulta global del modelo anterior como lógica de resguardo si no hay un grupo de entidad.

Esta estrategia permite realizar una migración gradual de un modelo de datos existente a uno nuevo que use grupos de entidad, con lo que se reduce el riesgo de problemas causados por la coherencia eventual. En la práctica, la aplicación de este enfoque en un sistema real depende de casos prácticos y requisitos específicos.

Plan de resguardo para modo degradado

Actualmente, es difícil detectar de manera programática una situación en la que se deterioró la coherencia de una aplicación. Sin embargo, en caso de que determines por otros medios que se deterioró la coherencia de una aplicación, tal vez puedas implementar un modo degradado que se puede activar y desactivar para inhabilitar algunas áreas en la lógica de la aplicación que requieren coherencia sólida. Por ejemplo, en vez de mostrar un resultado de consulta incoherente en una pantalla de informe de facturación, se puede mostrar un mensaje de mantenimiento para esa pantalla específica. De esta manera, los otros servicios de la aplicación pueden funcionar sin interrupciones y, a su vez, se reduce el impacto en la experiencia del usuario.

Cómo reducir el tiempo para lograr una coherencia completa

En una aplicación de gran envergadura con millones de usuarios o terabytes de entidades de Cloud Datastore, es posible que el uso incorrecto de Cloud Datastore deteriore la coherencia. Estos son algunos ejemplos de prácticas incorrectas:

  • Numeración secuencial en las claves de entidad
  • Cantidad excesiva de índices

Estas prácticas no afectan a las aplicaciones pequeñas. Sin embargo, una vez que la aplicación crece mucho, estas prácticas aumentan la posibilidad de que se necesite más tiempo para lograr la coherencia. Por lo tanto, es mejor evitarlas en las primeras etapas del diseño de la aplicación.

Antipatrón 1: Numeración secuencial de las claves de entidad

Antes de la versión 1.8.1 del SDK de App Engine, Cloud Datastore utilizaba ID compuestos por una breve serie de números enteros en patrones generalmente consecutivos como nombres de clave predeterminados creados automáticamente. En algunos documentos, esto se denomina una “política heredada” para crear entidades que no tengan un nombre de clave especificado en la aplicación. Esta política generaba nombres de clave para entidades con una numeración secuencial (por ejemplo 1000, 1001, 1002). Sin embargo, según lo que analizamos antes, Cloud Datastore almacena las entidades según el orden lexicográfico de los nombres de clave, por lo que resultaba probable que esas entidades se almacenaran en los mismos servidores de Cloud Datastore. Si una aplicación genera mucho tráfico, esa numeración secuencial podría dar lugar a una concentración de operaciones en un servidor específico, lo que puede generar una latencia más larga hasta que se alcance la coherencia.

En la versión 1.8.1 del SDK de App Engine, Cloud Datastore incorporó un nuevo método de numeración de ID con una política predeterminada que utiliza ID dispersos (consulta la documentación de referencia). Esta política predeterminada genera una secuencia aleatoria de ID de hasta 16 dígitos con una distribución aproximadamente uniforme. Con esta política. es probable que el tráfico de la aplicación de gran envergadura se distribuya mejor entre un conjunto de servidores de Cloud Datastore, lo cual reduce el tiempo necesario para alcanzar la coherencia. Se recomienda utilizar la política predeterminada, a menos que tu aplicación requiera específicamente la política heredada.

Si estableces nombres de clave explícitamente para las entidades, el esquema de nombres debe diseñarse de manera tal que se pueda obtener acceso a las entidades de manera uniforme en todo el espacio de nombres de clave. En otras palabras, no concentres el acceso en un rango particular, ya que se organizan según el orden lexicográfico de los nombres de clave. De lo contrario, surgirá el mismo problema que con la numeración secuencial.

Para entender la distribución no uniforme del acceso a través del espacio de claves, considera un ejemplo en el que se crean entidades con nombres de clave secuenciales como los siguientes:

p1 = Person(key_name='0001')
p2 = Person(key_name='0002')
p3 = Person(key_name='0003')
...

El patrón de acceso de la aplicación podría crear un punto de mucha actividad en un rango determinado de nombres de clave (por ejemplo, si se concentra el acceso en las entidades Person creadas recientemente). En este caso, se accederá con más frecuencia a las claves con un ID más alto. Por lo tanto, la carga se concentrará en un servidor específico de Cloud Datastore.

Como alternativa, para comprender la distribución uniforme en el espacio de claves, puedes usar strings aleatorias largas en los nombres de clave. Esto se muestra en el siguiente ejemplo:

p1 = Person(key_name='t9P776g5kAecChuKW4JKCnh44uRvBDhU')
p2 = Person(key_name='hCdVjL2jCzLqRnPdNNcPCAN8Rinug9kq')
p3 = Person(key_name='PaV9fsXCdra7zCMkt7UX3THvFmu6xsUd')
...

De esta forma, las entidades Person creadas recientemente estarán distribuidas por todo el espacio de claves y se alojarán en varios servidores. Este ejemplo da por sentado que existe una cantidad suficientemente grande de entidades Person.

Antipatrón 2: Cantidad excesiva de índices

En Cloud Datastore, cada vez que se actualiza una entidad, se actualizan también todos los índices definidos para ese tipo de entidad (consulta Ciclo de una escritura en Datastore para obtener más detalles). Si una aplicación utiliza muchos índices personalizados, una actualización puede implicar decenas, cientos o incluso miles de actualizaciones de las tablas de índice. En una aplicación de gran envergadura, el uso excesivo de índices personalizados puede aumentar la carga en el servidor y, en consecuencia, aumentar la latencia para lograr la coherencia.

En la mayoría de los casos, los índices personalizados se agregan para cumplir con requisitos de atención al cliente, solución de problemas o tareas de análisis de datos. BigQuery es un motor de consultas altamente escalable, capaz de ejecutar consultas ad-hoc en grandes conjuntos de datos sin índices preconfigurados. Es más adecuado que Cloud Datastore para los casos prácticos que requieren consultas complejas, como atención al cliente, solución de problemas o análisis de datos.

Se puede combinar Cloud Datastore y BigQuery para cumplir con distintos requerimientos comerciales. Utiliza Cloud Datastore para el procesamiento de transacciones en línea (OLTP) necesario en la lógica principal de la aplicación y BigQuery para el procesamiento analítico en línea (OLAP) de las operaciones de backend. Tal vez sea necesario implementar un flujo de exportación de datos continuo desde Cloud Datastore a BigQuery a fin de transferir los datos necesarios para esas consultas.

Además de la implementación alternativa para índices personalizados, también se recomienda especificar explícitamente las propiedades no indexadas (consulta Propiedades y tipos de valores). De forma predeterminada, Cloud Datastore crea una tabla de índice diferente para cada propiedad indexable de un tipo de entidad. Si tienes 100 propiedades en un tipo, habrá 100 tablas de índice en ese tipo y 100 actualizaciones adicionales cada vez que se actualice una entidad. Por lo tanto, se recomienda establecer propiedades no indexadas cuando sea posible, si no se necesitan para una condición de consulta.

Además de reducir la posibilidad de que se requiera más tiempo para alcanzar la coherencia, estas optimizaciones de índice pueden reducir considerablemente los costos de almacenamiento de Cloud Datastore en una aplicación de gran envergadura que utiliza muchos índices.

Conclusión

La coherencia eventual es un elemento fundamental en las bases de datos no relacionales, que permite a los desarrolladores alcanzar un equilibrio óptimo entre escalabilidad, rendimiento y coherencia. Es importante comprender cómo manejar el equilibrio entre la coherencia eventual y sólida para diseñar un modelo de datos óptimo para tu aplicación. En Cloud Datastore, el uso de grupos de entidad y consultas principales es la mejor manera de garantizar una coherencia sólida en un alcance de entidades. Si no puedes incorporar grupos de entidad en tu aplicación debido a las limitaciones mencionadas anteriormente, puedes utilizar las opciones de consultas de solo clave o Memcache. Para aplicaciones de gran envergadura, sigue las recomendaciones, como usar ID no seriales y minimizar la indexación a fin de disminuir el tiempo necesario para lograr la coherencia. Además, es importante combinar Cloud Datastore con BigQuery para cumplir con los requisitos comerciales de las consultas complejas y reducir el uso de los índices de Cloud Datastore tanto como sea posible.

Recursos adicionales

Los siguientes recursos proporcionan más información sobre los temas que analizamos en este documento:




[1] Un grupo de entidad se puede formar especificando solo una clave de la entidad principal o raíz, sin tener que almacenar la entidad principal o raíz en sí, ya que las funciones de grupo de entidad se implementan según las relaciones entre claves.

[2] El límite admitido es una actualización por segundo por grupo de entidad fuera de las transacciones o una transacción por segundo por grupo de entidad. Si agregas varias actualizaciones en una transacción, tendrás un límite de tamaño máximo de 10 MB para la transacción y la tasa máxima de escritura del servidor de Datastore.

¿Te ha resultado útil esta página? Enviar comentarios:

Enviar comentarios sobre...

Documentación de Cloud Datastore