Prácticas recomendadas de Cloud Datastore

Puedes usar las prácticas recomendadas que se indican en este artículo como referencia rápida de lo que debes tener en cuenta al crear una aplicación que use Datastore. Si acabas de empezar a usar Datastore, puede que esta página no sea el mejor punto de partida, ya que no explica los conceptos básicos de cómo usar Datastore. Si eres un usuario nuevo, te recomendamos que empieces con la guía Introducción a Datastore.

General

  • Utiliza siempre caracteres UTF-8 para los nombres de espacios de nombres, nombres de tipos, nombres de propiedades y nombres de claves personalizadas. Los caracteres que no sean UTF-8 utilizados en estos nombres pueden interferir con la funcionalidad de Datastore. Por ejemplo, si el nombre de una propiedad contiene un carácter que no es UTF-8, no se podrá crear un índice que utilice esa propiedad.
  • No utilice barras inclinadas (/) en los nombres de tipo ni en los nombres de clave personalizados. Las barras diagonales de estos nombres podrían interferir con funciones futuras.
  • No almacenes información sensible en un ID de proyecto de Cloud. Es posible que se conserve un ID de proyecto de Cloud más allá de la vida útil de tu proyecto.
  • Como práctica recomendada para cumplir la normativa de datos, le recomendamos que no almacene información sensible en los nombres de las entidades ni en los nombres de las propiedades de las entidades de Datastore.

Llamadas a la API

  • Usa operaciones por lotes para las lecturas, escrituras y eliminaciones en lugar de operaciones individuales. Las operaciones por lotes son más eficientes porque realizan varias operaciones con la misma sobrecarga que una sola operación.
  • Si una transacción falla, asegúrate de intentar revertirla. La reversión minimiza la latencia de reintento de una solicitud diferente que compite por los mismos recursos en una transacción. Ten en cuenta que la propia reversión puede fallar, por lo que la reversión debe ser solo un intento de la mejor manera posible.
  • Utiliza llamadas asíncronas en lugar de llamadas síncronas cuando estén disponibles. Las llamadas asíncronas minimizan el impacto de la latencia. Por ejemplo, supongamos que una aplicación necesita el resultado de una lookup() síncrona y los resultados de una consulta para poder renderizar una respuesta. Si el lookup() y la consulta no tienen una dependencia de datos, no es necesario esperar de forma síncrona hasta que se complete el lookup() antes de iniciar la consulta.

Entidades

  • Agrupa los datos que estén muy relacionados en grupos de entidades. Los grupos de entidades permiten realizar consultas de ancestros, que devuelven resultados coherentes. Las consultas de ancestros también analizan rápidamente un grupo de entidades con un mínimo de operaciones de E/S, ya que las entidades de un grupo de entidades se almacenan en lugares físicamente cercanos en los servidores de Datastore.
  • Evita escribir en un grupo de entidades más de una vez por segundo. Si escribes a una velocidad constante superior a ese límite, las lecturas que se realizan con el tiempo serán más lentas, se agotará el tiempo de espera de las lecturas con coherencia fuerte y el rendimiento general de tu aplicación será más lento. Una escritura por lotes o transaccional en un grupo de entidades cuenta como una sola escritura de cara a este límite.
  • No incluyas la misma entidad (por clave) varias veces en la misma confirmación. Incluir la misma entidad varias veces en la misma confirmación puede afectar a la latencia de Datastore.

Claves

  • Los nombres de las claves se generan automáticamente si no se proporcionan al crear la entidad. Se asignan de forma que se distribuyan uniformemente en el espacio de claves.
  • En el caso de las claves que usan un nombre personalizado, siempre debes usar caracteres UTF-8, excepto la barra diagonal (/). Los caracteres que no sean UTF-8 interfieren en varios procesos, como la importación de una copia de seguridad de Datastore en Google BigQuery. Una barra inclinada podría interferir con funciones futuras.
  • Si la llave usa un ID numérico, haz lo siguiente:
    • No uses un número negativo para el ID. Un ID negativo podría interferir en la ordenación.
    • No utilice el valor 0(cero) para el ID. Si lo haces, se te asignará un ID automáticamente.
    • Si quieres asignar manualmente tus propios IDs numéricos a las entidades que crees, haz que tu aplicación obtenga un bloque de IDs con el método allocateIds(). De esta forma, se evitará que Datastore asigne uno de tus IDs numéricos manuales a otra entidad.
  • Si asignas tu propio ID numérico manual o nombre personalizado a las entidades que creas, no utilices valores que aumenten de forma monótona, como los siguientes:

    1, 2, 3, ,
    "Customer1", "Customer2", "Customer3", .
    "Product 1", "Product 2", "Product 3", .
    

    Si una aplicación genera un gran volumen de tráfico, esta numeración secuencial podría provocar puntos de acceso que afecten a la latencia de Datastore. Para evitar el problema de los IDs numéricos secuenciales, obtén IDs numéricos del método allocateIds(). El método allocateIds() genera secuencias bien distribuidas de IDs numéricos.

  • Si especifica una clave o almacena el nombre generado, podrá realizar una lookup() coherente en esa entidad más adelante sin tener que enviar una consulta para encontrarla.

Índices

Propiedades

  • Usa siempre caracteres UTF-8 en las propiedades de tipo string. Si hay un carácter que no es UTF-8 en una propiedad de tipo cadena, podría interferir en las consultas. Si necesitas guardar datos con caracteres que no sean UTF-8, utiliza una cadena de bytes.
  • No uses puntos en los nombres de las propiedades. Los puntos en los nombres de las propiedades interfieren con la indexación de las propiedades de entidades insertadas.

Consultas

  • Si solo necesitas acceder a la clave de los resultados de la consulta, usa una consulta para obtener solo las claves. Una consulta de solo claves devuelve resultados con una latencia y un coste menores que si se recuperaran entidades completas.
  • Si solo necesitas acceder a propiedades específicas de una entidad, utiliza una consulta de proyección. Una consulta de proyección devuelve resultados con una latencia y un coste menores que la recuperación de entidades completas.
  • Del mismo modo, si solo necesita acceder a las propiedades que se incluyen en el filtro de consulta (por ejemplo, las que se indican en una cláusula order by), utilice una consulta de proyección.
  • No uses desplazamientos. En su lugar, usa cursores. Si solo usas un desplazamiento, se evitará que se devuelvan las entidades omitidas a tu aplicación, pero estas entidades se seguirán obteniendo internamente. Las entidades omitidas afectan a la latencia de la consulta y se te factura por las operaciones de lectura necesarias para recuperarlas.
  • Si necesitas una coherencia sólida para tus consultas, usa una consulta de ancestro. Para usar las consultas de ancestros, primero debe estructurar sus datos para conseguir una coherencia inmediata. Una consulta de ancestro devuelve resultados con coherencia fuerte. Ten en cuenta que una consulta de solo claves que no sea de ancestro seguida de una lookup() no devuelve resultados sólidos, ya que la consulta de solo claves que no sea de ancestro podría obtener resultados de un índice que no sea coherente en el momento de la consulta.

Diseño para la escalabilidad

Actualizaciones de un solo grupo de entidades

No se debe actualizar demasiado rápido un solo grupo de entidades en Datastore.

Si utiliza Datastore, Google le recomienda que diseñe su aplicación de forma que no necesite actualizar un grupo de entidades más de una vez por segundo. Recuerda que una entidad sin elemento superior ni secundario es su propio grupo de entidades. Si actualiza un grupo de entidades demasiado rápido, las escrituras de Datastore tendrán una latencia más alta, tiempos de espera y otros tipos de errores. Esto se conoce como contención.

A veces, las tasas de escritura de Datastore en un solo grupo de entidades pueden superar el límite de una por segundo, por lo que es posible que las pruebas de carga no muestren este problema.

Tasas de lectura/escritura altas en un intervalo de claves reducido

Evita las altas tasas de lectura o escritura en claves de Datastore que estén cerca lexicográficamente.

Datastore se basa en la base de datos NoSQL de Google, Bigtable, y está sujeto a las características de rendimiento de Bigtable. Bigtable se escala fragmentando las filas en tablets independientes. Estas filas se ordenan lexicográficamente por clave.

Si usas Datastore, es posible que las escrituras sean lentas debido a una tabla activa si se produce un aumento repentino de la tasa de escritura en un intervalo pequeño de claves que supera la capacidad de un solo servidor de tabla. Bigtable acabará dividiendo el espacio de claves para admitir cargas elevadas.

El límite de lecturas suele ser mucho más alto que el de escrituras, a menos que leas de una sola clave a un ritmo elevado. Bigtable no puede dividir una sola clave en más de una tablet.

Las tablets activas se pueden aplicar a los intervalos de claves que usan tanto las claves de entidad como los índices.

En algunos casos, un punto de acceso de Datastore puede tener un impacto mayor en una aplicación que impedir las lecturas o escrituras en un pequeño intervalo de claves. Por ejemplo, las teclas de acceso rápido se pueden leer o escribir durante el inicio de la instancia, lo que provoca que las solicitudes de carga fallen.

De forma predeterminada, Datastore asigna claves mediante un algoritmo disperso. Por lo tanto, normalmente no se producirá un exceso de actividad en las escrituras de Datastore si creas entidades nuevas a una alta velocidad de escritura con la política de asignación de ID predeterminada. Hay algunos casos concretos en los que puedes encontrarte con este problema:

  • Si crea entidades nuevas a un ritmo muy alto con la política de asignación de ID secuenciales antigua.

  • Si creas entidades nuevas a un ritmo muy alto y asignas tus propios IDs, que aumentan de forma monótona.

  • Si creas entidades nuevas a un ritmo muy alto para un tipo que antes tenía muy pocas entidades. Bigtable empezará con todas las entidades en el mismo servidor de tablets y tardará un tiempo en dividir el intervalo de claves en servidores de tablets independientes.

  • También se producirá este problema si creas entidades a un ritmo elevado con una propiedad indexada que aumente de forma monótona, como una marca de tiempo, ya que estas propiedades son las claves de las filas de las tablas de índice de Bigtable.

  • Datastore antepone el espacio de nombres y el tipo del grupo de entidades raíz a la clave de fila de Bigtable. Puedes alcanzar un punto de acceso si empiezas a escribir en un nuevo espacio de nombres o tipo sin aumentar el tráfico gradualmente.

Si tienes una propiedad de clave o indexada que aumentará de forma monótona, puedes añadir un hash aleatorio para asegurarte de que las claves se fragmenten en varias tablets.

Del mismo modo, si necesitas consultar una propiedad que aumenta (o disminuye) de forma monótona mediante una ordenación o un filtro, puedes indexar una propiedad nueva. Para ello, añade al valor monótono un prefijo con un valor que tenga una cardinalidad alta en el conjunto de datos, pero que sea común a todas las entidades del ámbito de la consulta que quieras realizar. Por ejemplo, si quieres consultar entradas por marca de tiempo, pero solo necesitas devolver resultados de un usuario a la vez, puedes añadir el ID de usuario como prefijo a la marca de tiempo e indexar esa nueva propiedad. De esta forma, se seguirían permitiendo las consultas y los resultados ordenados para ese usuario, pero la presencia del ID de usuario aseguraría que el índice en sí esté bien fragmentado.

Para obtener una explicación más detallada de este problema, consulta la entrada del blog de Ikai Lan sobre cómo guardar valores que aumentan de forma monótona en Datastore.

Aumentar el tráfico

Aumenta gradualmente el tráfico a los nuevos tipos de Datastore o a las partes del espacio de claves.

Deberías aumentar el tráfico a los nuevos tipos de Datastore de forma gradual para que Bigtable tenga tiempo suficiente para dividir las tablets a medida que aumente el tráfico. Recomendamos un máximo de 500 operaciones por segundo en un nuevo tipo de Datastore y, a continuación, aumentar el tráfico en un 50% cada 5 minutos. En teoría, puedes llegar a 740.000 operaciones por segundo después de 90 minutos con esta programación de aumento. Asegúrate de que las escrituras se distribuyan de forma relativamente uniforme en todo el intervalo de claves. Nuestros ingenieros de fiabilidad de sitios web la denominan "regla 500/50/5".

Este patrón de aumento gradual es especialmente importante si cambias el código para dejar de usar el tipo A y empezar a usar el tipo B. Una forma sencilla de gestionar esta migración es cambiar el código para que lea el tipo B y, si no existe, que lea el tipo A. Sin embargo, esto podría provocar un aumento repentino del tráfico a un nuevo tipo con una parte muy pequeña del espacio de claves. Es posible que Bigtable no pueda dividir las tablets de forma eficiente si el espacio de claves es disperso.

El mismo problema puede producirse si migras tus entidades para que usen otro intervalo de claves del mismo tipo.

La estrategia que uses para migrar entidades a un nuevo tipo o clave dependerá de tu modelo de datos. A continuación, se muestra un ejemplo de estrategia, conocida como "Lecturas paralelas". Deberá determinar si esta estrategia es eficaz para sus datos. Un aspecto importante que se debe tener en cuenta es el impacto económico de las operaciones paralelas durante la migración.

Lee primero la entidad o la clave antiguas. Si no está, puedes leer la nueva entidad o clave. Una tasa alta de lecturas de entidades inexistentes puede provocar un exceso de actividad, por lo que debes asegurarte de aumentar la carga gradualmente. Una estrategia mejor es copiar la entidad antigua en la nueva y, a continuación, eliminar la antigua. Aumenta las lecturas paralelas de forma gradual para asegurarte de que el nuevo espacio de claves esté bien dividido.

Una posible estrategia para aumentar gradualmente las lecturas o escrituras en un tipo nuevo es usar un hash determinista del ID de usuario para obtener un porcentaje aleatorio de usuarios que escriban entidades nuevas. Asegúrate de que el resultado del hash del ID de usuario no se vea afectado por tu función aleatoria ni por el comportamiento del usuario.

Mientras tanto, ejecuta un trabajo de Dataflow para copiar todos tus datos de las entidades o claves antiguas a las nuevas. Tu trabajo por lotes debe evitar las escrituras en claves secuenciales para evitar los puntos de acceso de Bigtable. Cuando se complete el trabajo por lotes, solo podrás leer desde la nueva ubicación.

Una mejora de esta estrategia es migrar pequeños grupos de usuarios a la vez. Añade un campo a la entidad de usuario que registre el estado de migración de ese usuario. Selecciona un lote de usuarios para migrar en función de un hash del ID de usuario. Una tarea de MapReduce o Dataflow migrará las claves de ese lote de usuarios. Los usuarios que tengan una migración en curso usarán lecturas paralelas.

Ten en cuenta que no podrás revertir fácilmente los cambios a menos que hagas escrituras duales de las entidades antiguas y nuevas durante la fase de migración. Esto aumentaría los costes de Datastore.

Eliminaciones

Evita eliminar un gran número de entidades de Datastore en un intervalo de claves reducido.

Bigtable reescribe periódicamente sus tablas para eliminar las entradas eliminadas y reorganizar los datos de forma que las lecturas y las escrituras sean más eficientes. Este proceso se conoce como compactación.

Si eliminas un gran número de entidades de Datastore en un intervalo pequeño de claves, las consultas en esta parte del índice serán más lentas hasta que se complete la compactación. En casos extremos, es posible que tus consultas agoten el tiempo de espera antes de devolver resultados.

No es recomendable usar un valor de marca de tiempo para un campo indexado con el fin de representar la hora de vencimiento de una entidad. Para recuperar las entidades caducadas, debe consultar este campo indexado, que probablemente se encuentre en una parte superpuesta del espacio de claves con las entradas de índice de las entidades eliminadas más recientemente.

Puedes mejorar el rendimiento con las "consultas fragmentadas", que anteponen una cadena de longitud fija a la marca de tiempo de vencimiento. El índice se ordena por la cadena completa, de forma que las entidades con la misma marca de tiempo se encuentren en todo el intervalo de claves del índice. Ejecutas varias consultas en paralelo para obtener resultados de cada fragmento.

Una solución más completa para el problema de la marca de tiempo de vencimiento es usar un "número de generación", que es un contador global que se actualiza periódicamente. El número de generación se añade antes de la marca de tiempo de vencimiento para que las consultas se ordenen por número de generación, luego por fragmento y, por último, por marca de tiempo. La eliminación de entidades antiguas se produce en una generación anterior. El número de generación de las entidades que no se hayan eliminado debe incrementarse. Una vez que se haya completado la eliminación, podrás pasar a la siguiente generación. Las consultas en una generación anterior tendrán un rendimiento bajo hasta que se complete la compactación. Puede que tengas que esperar a que se completen varias generaciones antes de consultar el índice para obtener la lista de entidades que se van a eliminar. De esta forma, se reduce el riesgo de que falten resultados debido a la coherencia final.

Fragmentación y replicación

Usa la fragmentación o la replicación para las claves de Datastore activas.

Puedes usar la replicación si necesitas leer una parte del intervalo de claves a una velocidad superior a la que permite Bigtable. Con esta estrategia, almacenarías N copias de la misma entidad, lo que permitiría una tasa de lecturas N veces superior a la que admite una sola entidad.

Puedes usar la fragmentación si necesitas escribir en una parte del intervalo de claves a una velocidad superior a la que permite Bigtable. La fragmentación divide una entidad en partes más pequeñas.

Algunos errores habituales al fragmentar son los siguientes:

  • Partición mediante un prefijo de tiempo. Cuando se pasa al siguiente prefijo, la nueva parte sin dividir se convierte en un punto de acceso. En su lugar, deberías migrar gradualmente una parte de tus escrituras al nuevo prefijo.

  • Particionando solo las entidades más activas. Si fragmentas una pequeña proporción del número total de entidades, es posible que no haya suficientes filas entre las entidades activas para asegurarte de que permanezcan en divisiones diferentes.

Siguientes pasos