Prácticas recomendadas de Cloud Datastore

Puedes usar las prácticas recomendadas que se mencionan aquí como referencia rápida de lo que debes tener en cuenta a la hora de compilar una aplicación que usa Datastore. Si estás comenzando a usar Datastore, es posible que esta página no sea el mejor punto de partida, ya que no se enseñan los conceptos básicos del uso de Datastore. Si eres un usuario nuevo, te sugerimos que empieces con Comienza a usar Datastore.

General

  • Usa siempre caracteres UTF-8 para nombres de espacio de nombres, nombres de categorías, nombres de propiedades y nombres de claves personalizadas. Los caracteres que no son UTF-8 que se usan en esos nombres pueden interferir en la funcionalidad de Datastore. Por ejemplo, usar un carácter que no es UTF-8 en un nombre de propiedad podría impedir la creación de un índice que use esta propiedad.
  • No uses una barra diagonal (/) en nombres de categorías de claves personalizadas. Las barras diagonales podrían interferir en funcionalidades futuras si se usan en estos nombres.
  • Evita almacenar información sensible en un ID del proyecto de Cloud. El ID del proyecto de Cloud podría conservarse aún después de que finalice tu proyecto.

Llamadas a la API

  • Usa operaciones por lotes en lugar de operaciones individuales para tus lecturas, escrituras y eliminaciones. Las operaciones por lotes son más eficientes debido a que realizan varias operaciones con la misma sobrecarga que una operación individual.
  • Si una transacción falla, intenta revertir la transacción. 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 reversión puede fallar, por lo tanto, debe considerarse solo como un último recurso.
  • Utiliza llamadas asíncronas en vez de síncronas cuando sea posible. Las llamadas asíncronas minimizan el impacto en la latencia. Por ejemplo, considera una aplicación que necesita el resultado de un lookup() síncrono y los resultados de una consulta antes de que pueda procesar una respuesta. Si la lookup() y la consulta no tienen una dependencia de datos, no es necesario esperar de forma síncrona hasta que la lookup() se complete antes de iniciar la consulta.

Entidades

  • Agrupa datos muy relacionados entre sí en grupos de entidad. Los grupos de entidad habilitan las consultas principales, que muestran resultados con coherencia sólida. Las consultas principales también realizan un análisis rápido en busca de grupos de entidad con E/S mínima, debido a que las entidades de estos grupos están almacenadas en lugares físicamente cercanos en los servidores de Datastore.
  • Evita escribir a un grupo de entidad más de una vez por segundo. Escribir en una frecuencia sostenida por encima del límite hace que las lecturas de coherencia eventual aumenten la eventualidad, provoca que se agote el tiempo de espera de las lecturas de coherencia sólida y genera un rendimiento general de tu aplicación más lento. Una escritura por lotes o transaccional en un grupo de entidad cuenta solo como una escritura individual en 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 podría afectar la latencia de Datastore.

Claves

  • Los nombres de las claves se generan de forma automática si no se proporcionaron al momento de la creación de la entidad. Se asignan de modo que estén distribuidas de forma uniforme en el espacio de claves.
  • Para una clave que usa un nombre personalizado, usa siempre caracteres UTF-8, excepto la barra diagonal (/). Los caracteres que no son UTF-8 interfieren en varios procesos, como la importación de una copia de seguridad de Datastore en Google BigQuery. Una barra diagonal puede interferir en funcionalidades futuras.
  • Para una clave que use un ID numérico:
    • No uses un número negativo para el ID. Un ID negativo puede interferir en el ordenamiento.
    • No uses el valor 0(cero) para el ID. Si lo haces, obtendrás un ID asignado de manera automática.
    • Si deseas asignar de forma manual tus propios ID numéricos a las entidades que creas, haz que tu aplicación obtenga un bloque de ID con el método allocateIds(). Eso evitará que Datastore asigne uno de tus ID numéricos manuales a otra entidad.
  • Si asignas tu propio ID numérico manual o nombre personalizado a las entidades que creas, no uses valores que aumentan de forma monótona, como:

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

    Si una aplicación genera un tráfico alto, esa numeración secuencial podría dar lugar a hotspots que afectan la latencia de Datastore. Para evitar el problema de los ID numéricos secuenciales, obtén ID numéricos con el método allocateIds(). El método allocateIds() genera secuencias bien distribuidas de ID numéricos.

  • Cuando especificas una clave o almacenas el nombre generado, puedes realizar más adelante una lookup() coherente en esa entidad sin necesidad de emitir una consulta para encontrar la entidad.

Índices

Properties

  • Usa siempre caracteres UTF-8 para propiedades de tipo string. Un carácter que no sea UTF-8 en una propiedad de tipo string podría interferir en las consultas. Si necesitas guardar datos que contengan caracteres que no son UTF-8, usa una string de bytes.
  • No uses puntos en los nombres de las propiedades. Los puntos en los nombres de la propiedad interfieren en la indexación de propiedades de entidad incorporadas.

Consultas

  • Si necesitas acceder solo a la clave desde los resultados de la consulta, usa una consulta de solo claves. Las consultas de solo claves muestran los resultados a una latencia y costo más bajos que cuando se recuperan las entidades completas.
  • Si solo necesitas acceder a propiedades específicas de una entidad, usa una consulta de proyección. Las consultas de proyección muestran los resultados a una latencia y costo más bajos que cuando se recuperan las entidades completas.
  • Del mismo modo, si necesitas acceder solo a las propiedades que están incluidas en el filtro de consulta (por ejemplo, las enumeradas en una cláusula order by), usa una consulta de proyección.
  • No uses compensaciones. En su lugar, utiliza cursores. Utilizar una compensación solo evita que las entidades omitidas se recuperen en tu aplicación, pero igual se recuperan de forma interna. Las entidades omitidas afectan la latencia de la consulta, y se te cobrará por las operaciones de lectura requeridas para recuperarlas.
  • Si necesitas una coherencia sólida para tus consultas, usa una consulta principal (Si quieres usar consultas principales, primero necesitas estructurar tus datos para lograr coherencia sólida). Una consulta principal muestra resultados con coherencia sólida. Ten en cuenta que una consulta solo de claves no principal seguida de una lookup() no muestra resultados sólidos, porque la consulta solo de claves no principal podría obtener resultados de un índice que no es coherente en el momento de la consulta.

Diseña para el escalamiento

Actualizaciones para un grupo de entidad único

Un grupo de entidad único en Datastore no debe actualizarse demasiado rápido.

Si usas Datastore, Google te recomienda diseñar tu aplicación para que no tenga que actualizar un grupo de entidad más de una vez por segundo. Recuerda que una entidad sin entidad superior ni secundaria es su propio grupo de entidad. Si actualizas un grupo de entidad con demasiada rapidez, tus operaciones de escritura de Datastore tendrán tiempos de espera y latencia mayores, además de otros tipos de errores. Esto se conoce como contención.

A veces, las tasas de escritura de Datastore para un grupo de entidad pueden exceder el límite de una por segundo, por lo que las pruebas de carga podrían no mostrar este problema.

Tasas altas de lectura/escritura para un rango pequeño de claves

Evita las tasas altas de lectura o escritura en claves de Datastore que se encuentren cerca de manera lexicográfica.

Datastore se construye sobre la base de datos NoSQL de Google, Bigtable, y está sujeto a las características de rendimiento de Bigtable. Bigtable realiza el escalamiento mediante la fragmentación de filas en tablets separadas, y estas filas se ordenan de manera lexicográfica por clave.

Si estás usando Datastore, puedes obtener operaciones de escritura lentas debido a una sobrecarga de la tablet si hay un aumento repentino en la tasa de escritura en un rango pequeño de claves que excede la capacidad de un servidor de tablet único. En algún momento, Bigtable dividirá el espacio de claves para que admita cargas altas.

El límite de operaciones de lectura suele ser más alto que el de las de escritura, a menos que la lectura sea desde una clave única a una tasa alta. Bigtable no puede separar una clave única en más de una tablet.

Las tablets sobrecargadas se pueden aplicar a los rangos de claves que usan las claves de entidad y los índices.

En algunos casos, un hotspot de Datastore puede causar un impacto mayor en una aplicación que evitar operaciones de lectura o escritura en un rango pequeño de claves. Por ejemplo, las teclas de acceso rápido se pueden leer o escribir durante el inicio de la instancia, lo que causaría que la carga de solicitudes falle.

De forma predeterminada, Datastore asigna claves mediante un algoritmo de dispersión. Por lo tanto, en general, no encontrarás generación de hotspots en operaciones de escritura de Datastore si creas entidades nuevas con una tasa de escritura alta mediante la política de asignación de ID predeterminada. Existen algunos casos excepcionales en los que puedes encontrar este problema:

  • Si creas entidades nuevas a una tasa muy alta con la política de asignación de ID secuencial heredada.

  • Si creas entidades nuevas a una tasa muy alta y te asignas tus propios ID que aumentan monótonamente.

  • Si creas entidades nuevas a una tasa muy alta para categorías que antes tenían muy pocas entidades existentes. Bigtable comenzará con todas las entidades en un mismo servidor de tablet y le llevará un tiempo dividir el rango de claves en servidores de tablet separados.

  • También verás este problema si creas entidades nuevas a una tasa alta con una propiedad indexada que aumenta de forma monótona, como una marca de tiempo, debido a que estas propiedades son las claves para las filas de las tablas de índice en Bigtable.

  • Datastore antepone el espacio de nombres y el tipo de grupo de entidad raíz a la clave de fila de Bigtable. Puedes encontrar un hotspot si comienzas a escribir en un nuevo espacio de nombres o tipo sin aumentar el tráfico de manera gradual.

Si tienes una clave o una propiedad indexada que aumentará monótonamente, entonces puedes agregar un hash al azar para asegurarte de que las claves se compartan a múltiples tablets.

De la misma manera, si necesitas consultar una propiedad que aumenta (o disminuye) monótonamente con un orden o un filtro, podrías indexar una nueva propiedad, para la cual debes prefijar el valor monótono con un valor que tenga cardinalidad alta a lo largo del conjunto de datos, pero que es común a todas las entidades en el alcance de la consulta que quieres realizar. Por ejemplo, si quieres consultar entradas por marcas de tiempo, pero solo necesitas que muestre resultados para un usuario por vez, puedes prefijar la marca de tiempo con el id del usuario y luego indexar esa nueva propiedad. Esto permitirá consultas y ofrecerá los resultados en orden para ese usuario, pero la presencia del ID de usuario garantizará que el índice esté bien fragmentado.

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

Acelera el tráfico

Acelera el tráfico de manera gradual en nuevos tipos o partes del espacio de claves en Datastore.

Debes aumentar de forma gradual el tráfico a nuevos tipos de Datastore a fin de que Bigtable tenga tiempo suficiente para dividir las tablets a medida que aumenta el tráfico. Recomendamos realizar un máximo de 500 operaciones por segundo en un tipo nuevo de Datastore y, luego, aumentar el tráfico un 50% cada 5 minutos. En teoría, puedes alcanzar 740,000 operaciones por segundo después de usar este programa de aceleración durante 90 minutos. Asegúrate de que las operaciones de escritura se encuentren distribuidas de manera uniforme en todo el rango de claves. Nuestros SRE lo llaman la regla “500/50/5”.

Este patrón de aumento gradual es importante en particular si cambias el código para que deje de usar la categoría A y, en su lugar, use la categoría B. Una manera simple de manejar esta migración es cambiar tu código para que lea la categoría B y, si no existe, luego lea la categoría A. Sin embargo, esto podría provocar un aumento repentino en el tráfico a una categoría nueva con una porción muy pequeña del espacio de claves. Puede que Bigtable no pueda dividir de manera eficiente las tablets si el espacio de claves está disperso.

El mismo problema puede surgir si migras tus entidades para utilizar un rango de claves diferente dentro de la misma categoría.

La estrategia que utilices para migrar entidades a una nueva categoría o clave dependerá de tu nuevo modelo de datos. A continuación, puedes encontrar una estrategia de ejemplo conocida como "Lecturas paralelas". Tendrás que determinar si esta estrategia es eficaz para tus datos o no. Un punto importante a tener en cuenta será el impacto del costo de las operaciones paralelas durante la migración.

Lee primero la entidad o la clave antigua. Si esa falta, entonces puedes leer desde la entidad o clave nueva. Una tasa alta de lecturas de entidades no existentes puede causar una generación de hotspots, por lo tanto, debes asegurarte de incrementar la carga de manera gradual. Una mejor estrategia es copiar la entidad antigua a la nueva y luego borrar la antigua. Acelera las lecturas paralelas de manera gradual para asegurarte de que el nuevo espacio de claves esté divido correctamente.

Una estrategia posible para acelerar lecturas o escrituras de manera gradual en una categoría nueva es utilizar un hash determinista del ID del usuario para obtener un porcentaje aleatorio de usuarios que escriban nuevas entidades. Asegúrate de que el resultado del hash del ID de usuario no se encuentre sesgado ni por tu función aleatoria ni por el comportamiento de tu usuario.

Mientras tanto, ejecuta un trabajo de Dataflow para copiar todos tus datos de las entidades o claves antiguas a las nuevas. Tu trabajo en lotes debe evitar escrituras de claves secuenciales para impedir hotspots de Bigtable. Cuando tu trabajo en lotes está completo, solo puedes leer desde la nueva ubicación.

Una mejor manera de realizar esta estrategia es migrar lotes pequeños de usuarios al mismo tiempo. Agrega un campo a la entidad de usuario que realiza un seguimiento del estado de la migración de ese usuario. Selecciona un lote de usuarios para migrar en función de un hash del ID de usuario. Un trabajo de Mapreduce o Dataflow migrará las claves para ese lote de usuarios. Los usuarios que tengan una migración en curso usarán lecturas paralelas.

Ten en cuenta que no es fácil revertir el proceso, a menos que realices operaciones de escritura duales de las entidades antiguas y nuevas durante la fase de migración. Esto aumentaría los costos incurridos en Datastore.

Eliminaciones

Evita borrar una gran cantidad de entidades de Datastore en un rango pequeño de claves.

Bigtable vuelve a escribir sus tablas de forma periódica para quitar las entradas borradas y reorganizar los datos a fin de que las operaciones de lectura y escritura sean más eficientes. Este proceso se conoce como compactación.

Si borras una gran cantidad de entidades de Datastore en un rango de claves pequeño, las consultas en esta parte del índice serán más lentas hasta que se complete la compactación. En casos extremos, tus consultas pueden agotar el tiempo de espera antes de mostrar resultados.

Es un antipatrón utilizar un valor de marca de tiempo de un campo indexado para representar el tiempo de caducidad de una entidad. Para recuperar entidades vencidas, necesitarás consultar campos indexados que probablemente se encuentren en una parte superpuesta del espacio de claves con entradas de índice para las entidades borradas más recientes.

Puedes mejorar el rendimiento con "consultas de fragmentación" que agregan una string de longitud fija a la marca de tiempo de vencimiento. El índice está ordenado en la string completa para que las entidades con las mismas marcas de tiempo se puedan localizar a lo largo del rango de claves del índice. Puedes ejecutar múltiples consultas en paralelo para recuperar resultados de cada fragmentación.

Una solución más completa para el problema de la marca de tiempo de vencimiento es un "número de generación", un contador global que se actualiza de manera periódica. El número de generación está agregado en la marca de tiempo de vencimiento para que las consultas se ordenen por número de generación, luego por fragmentación y luego por marca de tiempo. La eliminación de las entidades antiguas ocurre en una generación anterior. El número de generación de una entidad que no se borró debería aumentar. Una vez que la eliminación está completa, avanzas a la próxima generación. Las consultas por una generación antigua no se realizarán correctamente hasta que la compactación esté completa. Podrías tener que esperar que varias generaciones se completen para poder consultar el índice y obtener la lista de entidades que borrar a fin de reducir el riesgo de que falten resultados debido a una coherencia eventual.

Fragmentación y replicación

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

Puedes usar la replicación si necesitas leer una parte del rango de claves a una tasa más alta que la que permite Bigtable. Si utilizas esta estrategia, almacenarás N copias de la misma entidad, lo cual permite una tasa de lectura N veces más alta que la admitida por una entidad única.

Puedes utilizar la fragmentación si necesitas escribir una parte del rango de la clave a una tasa más alta que la permitida por Bigtable. La fragmentación divide una entidad en unidades más pequeñas.

Algunos de los errores más comunes de fragmentación son:

  • Fragmentar con un prefijo de tiempo. Luego de que el tiempo se desplaza al siguiente prefijo, la parte nueva sin dividir se convierte en un hotspot. En cambio, debes desplazar de manera gradual una parte de tu escritura al nuevo prefijo.

  • Fragmentar solo las entidades más sobrecargadas. Si fragmentas una pequeña proporción del número total de entidades, podría no haber filas suficientes entre las entidades sobrecargadas para garantizar que permanezcan en divisiones diferentes.

Próximos pasos