Comprende las operaciones de lectura y escritura a gran escala

Consulta este documento que te permitirá tomar decisiones fundamentadas sobre la arquitectura de tus aplicaciones para lograr un alto rendimiento y una alta confiabilidad. En este documento, se incluyen temas avanzados de Firestore. Si estás comenzando a usar Firestore, consulta la guía de inicio rápido.

Firestore es una base de datos flexible y escalable para el desarrollo de servidores, dispositivos móviles y la Web desde Firebase y Google Cloud. Comenzar a usar Firestore y escribir aplicaciones enriquecidas y potentes es muy sencillo.

Para asegurarte de que tus aplicaciones sigan funcionando bien a medida que aumentan el tamaño de la base de datos y el tráfico, es útil comprender la mecánica de las operaciones de lectura y escritura en el backend de Firestore. También debes comprender la interacción entre tus operaciones de lectura y escritura con la capa de almacenamiento y las restricciones subyacentes que pueden afectar el rendimiento.

Consulta las siguientes secciones para conocer las prácticas recomendadas antes de diseñar tu aplicación.

Comprende los componentes de alto nivel

En el siguiente diagrama, se muestran los componentes de alto nivel que participan en una solicitud a la API de Firestore.

Componentes de alto nivel

SDK de Firestore y bibliotecas cliente

Firestore admite SDK y bibliotecas cliente para diferentes plataformas. Si bien una app puede realizar llamadas directas de HTTP y RPC a la API de Firestore, las bibliotecas cliente proporcionan una capa de abstracción para simplificar el uso de la API y aplicar prácticas recomendadas. También pueden proporcionar funciones adicionales, como acceso sin conexión, cachés, etcétera.

Google Front End (GFE)

Este es un servicio de infraestructura común entre todos los servicios de Google Cloud. GFE acepta solicitudes entrantes y las reenvía al servicio de Google relevante (en este contexto, el servicio de Firestore). También proporciona otras funciones importantes, como protección contra ataques de denegación del servicio.

Servicio de Firestore

El servicio de Firestore realiza verificaciones, como la autenticación, la autorización, las verificaciones de cuota y las reglas de seguridad, en la solicitud a la API, y también administra transacciones. Este servicio de Firestore incluye un cliente de almacenamiento que interactúa con la capa de almacenamiento para las operaciones de lectura y escritura de datos.

Capa de almacenamiento de Firestore

La capa de almacenamiento de Firestore se encarga de almacenar los datos y metadatos, así como las funciones asociadas de bases de datos que proporciona Firestore. En las siguientes secciones, se describe cómo se organizan los datos en la capa de almacenamiento de Firestore y cómo escala el sistema. Aprender cómo están organizados los datos puede ayudarte a diseñar un modelo escalable y a comprender mejor las prácticas recomendadas de Firestore.

Rangos y divisiones clave

Firestore es una base de datos NoSQL orientada a documentos. Los datos se almacenan en documentos, que se organizan en jerarquías de colecciones. La jerarquía de la colección y el ID del documento se traducen a una sola clave para cada documento. Los documentos se almacenan y ordenan de manera lógica y lexicográfica con esta clave única. Usamos el término rango de claves para hacer referencia a un rango de claves lexicográficamente contiguo.

Una base de datos típica de Firestore es demasiado grande para caber en una sola máquina física. También hay situaciones en las que la carga de trabajo en los datos es demasiado pesada para que una máquina la pueda manejar. Para manejar cargas de trabajo grandes, Firestore particiona los datos en secciones independientes que se pueden almacenar y entregar desde varias máquinas o servidores de almacenamiento. Estas particiones se realizan en las tablas de la base de datos, en bloques de rangos de claves llamados divisiones.

Replicación síncrona

Es importante tener en cuenta que la base de datos siempre se replica de forma automática y síncrona. Las divisiones de datos tienen réplicas en diferentes zonas para que estén disponibles incluso cuando una zona se vuelve inaccesible. El algoritmo Paxos administra la replicación coherente en las diferentes copias de la división para lograr un consenso. Se elige una réplica de cada división para que actúe como líder de Paxos, cuya responsabilidad consiste en administrar las operaciones de escritura en esa división. La replicación síncrona te permite leer siempre la versión más reciente de los datos de Firestore.

El resultado general es un sistema escalable y con alta disponibilidad que proporciona latencias bajas para las operaciones de lectura y escritura a gran escala, independientemente de las cargas de trabajo pesadas.

Diseño de datos

Firestore es una base de datos de documentos sin esquemas. Sin embargo, de forma interna, dispone los datos principalmente en dos tablas con estilo de base de datos relacional, en su capa de almacenamiento, de la siguiente manera:

  • Tabla de documentos: Los documentos se almacenan en esta tabla.
  • Tabla de índices: En esta tabla se almacenan las entradas que permiten obtener resultados ordenados por valor de índice de forma eficiente.

En el siguiente diagrama, se muestra cómo se verían las tablas de una base de datos de Firestore con las divisiones. Las divisiones se replican en tres zonas diferentes y cada una tiene un líder de Paxos asignado.

Diseño de datos

Región única o multirregión

Cuando creas una base de datos, debes seleccionar una región o multirregión.

Una ubicación regional única es una ubicación geográfica específica, como us-west1. Las divisiones de datos de una base de datos de Firestore tienen réplicas en diferentes zonas dentro de la región seleccionada, como se explicó anteriormente.

Una ubicación multirregional consiste en un conjunto definido de regiones en las que se almacenan réplicas de la base de datos. En una implementación multirregional de Firestore, dos de las regiones tienen réplicas completas de todos los datos de la base de datos. Una tercera región tiene una réplica testigo que no mantiene un conjunto completo de datos, pero participa en la replicación. Cuando se replican los datos entre múltiples regiones, estos se pueden seguir escribiendo y leyendo, incluso con la pérdida de toda una región.

Para obtener más información sobre las ubicaciones de una región, consulta Ubicaciones de Firestore.

Región única frente a multirregión

Comprende el ciclo de vida de una operación de escritura en Firestore

Para escribir datos, un cliente de Firestore puede crear, actualizar o borrar un solo documento. Una operación de escritura en un solo documento requiere de la actualización atómica del documento y sus entradas de índice asociadas en la capa de almacenamiento. Firestore también admite operaciones atómicas que constan de varias operaciones de lectura o escritura en uno o más documentos.

Para todos los tipos de operaciones de escritura, Firestore proporciona las propiedades ACID (atomicidad, coherencia, aislamiento y durabilidad) de las bases de datos relacionales. Firestore también proporciona serialización, lo que significa que todas las transacciones aparecen como si se ejecutaran en serie.

Pasos de alto nivel en una transacción de escritura

Cuando el cliente de Firestore emite una operación de escritura o confirma una transacción, mediante cualquiera de los métodos mencionados, esta se ejecuta de forma interna como una transacción de lectura y escritura de la base de datos en la capa de almacenamiento. La transacción permite que Firestore proporcione las propiedades ACID mencionadas anteriormente.

En el primer paso de una transacción, Firestore lee el documento existente y determina las mutaciones que se deben realizar a los datos en la tabla Documentos.

Esto también incluye realizar las actualizaciones necesarias en la tabla Índices de la siguiente manera:

  • Los campos que se agregan a los documentos necesitan las inserciones correspondientes en la tabla Índices.
  • Los campos que se quitan de los documentos necesitan las eliminaciones correspondientes en la tabla Índices.
  • Los campos que se modifican en los documentos necesitan eliminaciones (para valores anteriores), así como inserciones (para valores nuevos) en la tabla Índices.

Para calcular las mutaciones antes mencionadas, Firestore lee la configuración de indexación del proyecto. Esta configuración almacena información sobre los índices de un proyecto. Firestore usa dos tipos de índices: los de campo único y los compuestos. Para obtener información detallada sobre los índices creados en Firestore, consulta Tipos de índices en Firestore.

Una vez que se calculan las mutaciones, Firestore las recopila dentro de una transacción y, luego, las confirma.

Comprende una transacción de escritura en la capa de almacenamiento

Como se mencionó anteriormente, una operación de escritura en Firestore implica una transacción de lectura y escritura en la capa de almacenamiento. Según el diseño de los datos, una operación de escritura puede implicar una o más divisiones, como se ve en el diseño de datos.

En el siguiente diagrama, la base de datos de Firestore tiene ocho divisiones (marcadas del 1 al 8) alojadas en tres servidores de almacenamiento diferentes dentro de una sola zona, y cada división se replica en 3(o más) zonas diferentes. Cada división tiene un líder de Paxos, que puede estar en una zona diferente para distintas divisiones.

División de base de datos de Firestore

Considera una base de datos de Firestore que tenga la colección Restaurants de la siguiente manera:

Colección de restaurantes

El cliente de Firestore solicita el siguiente cambio para un documento de la colección Restaurant mediante la actualización del valor del campo priceCategory.

Cambio para un documento de una colección

Los siguientes pasos de alto nivel describen lo que sucede como parte de la operación de escritura:

  1. Crea una transacción de lectura y escritura.
  2. Lee el documento restaurant1 de la colección Restaurants que aparece en la tabla Documentos de la capa de almacenamiento.
  3. Lee los índices del documento de la tabla Índices.
  4. Calcula las mutaciones que se realizarán en los datos. En este caso, hay cinco mutaciones:
    • M1: Actualiza la fila de restaurant1 en la tabla Documentos para reflejar el cambio en el valor del campo priceCategory.
    • M2 y M3: Borran las filas del valor anterior de priceCategory en la tabla Índices para los índices ascendentes y descendentes.
    • M4 y M5: Insertan las filas del valor nuevo de priceCategory en la tabla Índices para los índices ascendentes y descendentes.
  5. Confirma estas mutaciones.

El cliente de almacenamiento en el servicio de Firestore busca las divisiones que poseen las claves de las filas que se cambiarán. Consideremos un caso en el que la División 3 entrega la M1 y la División 6 entrega de la M2 a la M5. De esta forma, se genera una transacción distribuida que incluye todas estas divisiones como participantes. Las divisiones de los participantes también pueden incluir cualquier otra división de la que se hayan leído datos anteriormente como parte de la transacción de lectura y escritura.

En los siguientes pasos, se describe lo que sucede como parte de la confirmación:

  1. El cliente de almacenamiento emite una confirmación. La confirmación contiene mutaciones de la M1 a la M5.
  2. Las divisiones 3 y 6 son los participantes de esta transacción. Se elige a uno de los participantes como el coordinador, como en la División 3. El trabajo del coordinador consiste en asegurarse de que la transacción se confirme o se anule de forma automática en todos los participantes.
    • Las réplicas líderes de estas divisiones son responsables del trabajo que realizan los participantes y coordinadores.
  3. Cada participante y coordinador ejecuta un algoritmo Paxos con sus respectivas réplicas.
    • El líder ejecuta un algoritmo Paxos con las réplicas. El quórum se logra si la mayoría de las réplicas responden con una respuesta ok to commit al líder.
    • Luego, cada participante notifica al coordinador cuando esté preparado (primera fase de la confirmación en dos fases). Si algún participante no puede confirmar la transacción, se anula toda la transacción (aborts).
  4. Una vez que el coordinador conoce a todos los participantes, incluido él mismo, está preparado para comunicar el resultado de la transacción de accept a todos los participantes (segunda fase de la confirmación en dos fases). En esta fase, cada participante registra la decisión de confirmación en un almacenamiento estable para confirmarla.
  5. El coordinador responde al cliente de almacenamiento en Firestore que se confirmó la transacción. Al mismo tiempo, el coordinador y todos los participantes aplican las mutaciones a los datos.

Confirmación del ciclo de vida

Cuando la base de datos de Firestore es pequeña, puede suceder que una sola división posea todas las claves de las mutaciones de la M1 a la M5. En ese caso, solo hay un participante en la transacción y no se requiere la confirmación en dos fases mencionada anteriormente, por lo que las operaciones de escritura se agilizan.

Operaciones de escritura multirregionales

En una implementación multirregional, la distribución de réplicas entre regiones aumenta la disponibilidad, aunque esto conlleva un costo de rendimiento. La comunicación entre réplicas en diferentes regiones ralentiza más los tiempos de ida y vuelta. Por lo tanto, la latencia del modelo de referencia para las operaciones de Firestore es ligeramente mayor en comparación con las implementaciones de una sola región.

Configuramos las réplicas de modo que el liderazgo de las divisiones siempre permanezca en la región principal. La región principal es la que recibe tráfico del servidor de Firestore. Esta decisión de liderazgo reduce el retraso de ida y vuelta en la comunicación entre el cliente de almacenamiento en Firestore y el líder de réplica (o coordinador de las transacciones de división múltiple).

Cada operación de escritura en Firestore también implica la interacción con el motor en tiempo real en Firestore. Para obtener más información sobre las consultas en tiempo real, consulta Comprende las consultas en tiempo real a gran escala.

Comprende el ciclo de vida de una operación de lectura en Firestore

En esta sección, se profundiza en las operaciones de lectura independientes que no se ejecutan en tiempo real de Firestore. De forma interna, el servidor de Firestore controla la mayoría de estas consultas en dos etapas principales:

  1. Un único análisis de rango en la tabla Índices
  2. Búsquedas de puntos en la tabla Documentos basadas en el resultado del análisis anterior
Es posible que ciertas consultas requieran menos procesamiento (por ejemplo, consultas keys-only para el modo Datastore) o más procesamiento (por ejemplo, consultas IN) en Firestore.

Las operaciones de lectura de datos de la capa de almacenamiento se realizan internamente mediante una transacción de base de datos para garantizar lecturas coherentes. Sin embargo, a diferencia de las transacciones que se usan para las operaciones de escritura, estas no aceptan bloqueos. En su lugar, funcionan eligiendo una marca de tiempo y, luego, ejecutando todas las lecturas en esa marca de tiempo. Dado que no adquieren bloqueos, no bloquean las transacciones simultáneas de lectura y escritura. Para ejecutar esta transacción, el cliente de almacenamiento en Firestore especifica un límite de marca de tiempo que le indica a la capa de almacenamiento cómo elegir una marca de tiempo de lectura. El tipo de límite de marca de tiempo que elige el cliente de almacenamiento en Firestore se determina mediante las opciones de lectura para la solicitud de lectura.

Comprende una transacción de lectura en la capa de almacenamiento

En esta sección, se describen los tipos de operaciones de lectura y cómo se procesan en la capa de almacenamiento en Firestore.

Operaciones de lectura sólidas

Según la configuración predeterminada, las operaciones de lectura de Firestore tienen una coherencia sólida. Esta coherencia sólida significa que una operación de lectura de Firestore muestra la versión más reciente de los datos que reflejan todas las operaciones de escritura confirmadas hasta el inicio de la operación de lectura.

Operaciones de lectura única divididas

El cliente de almacenamiento en Firestore busca las divisiones que poseen las claves de las filas que se leerán. Supongamos que necesitas ejecutar una operación de lectura de la División 3 de la sección anterior. El cliente envía la solicitud de lectura a la réplica más cercana para reducir la latencia de ida y vuelta.

En este punto, pueden ocurrir los siguientes casos según la réplica elegida:

  • La solicitud de lectura se dirige a una réplica líder (Zona A).
    • Dado que el líder está siempre actualizado, la operación de lectura puede continuar directamente.
  • La solicitud de lectura se dirige a una réplica no líder (como la zona B).
    • Debido al estado interno de la división 3, se puede saber que contiene suficiente información para entregar la operación de lectura, y así lo hace.
    • La División 3 no está segura de haber visto los datos más recientes. Le envía un mensaje al líder solicitando la marca de tiempo de la última transacción que debe aplicar para entregar la lectura. Una vez que se aplica la transacción, la operación de lectura puede continuar.

Luego, Firestore le muestra la respuesta a su cliente.

Operaciones de lectura de división múltiple

En caso de que las operaciones de lectura se deban realizar desde varias divisiones, se produce el mismo mecanismo en todas ellas. Una vez que se muestran los datos de todas las divisiones, el cliente de almacenamiento en Firestore combina los resultados. Luego, Firestore responde a este cliente con estos datos.

Operaciones de lectura inactivas

Las operaciones de lectura sólidas son el modo predeterminado en Firestore. Sin embargo, tiene un costo potencial de latencia mayor debido a la comunicación que podría requerir el líder. A menudo, tu aplicación de Firestore no necesita leer la versión más reciente de los datos, y la funcionalidad funciona bien con datos que pueden estar inactivos durante unos segundos.

En ese caso, el cliente puede optar por recibir operaciones de lectura inactivas mediante las opciones de lectura de read_time. En este caso, las operaciones de lectura se realizan porque los datos estaban en read_time, y es probable que la réplica más cercana ya haya verificado que tiene datos en el read_time especificado. Para obtener un rendimiento notablemente mejor, 15 segundos es un valor de inactividad razonable. Incluso para las lecturas inactivas, las filas generadas son coherentes entre sí.

Evita los hotspots

Las divisiones en Firestore se dividen automáticamente en partes más pequeñas para distribuir el trabajo de entregar tráfico a más servidores de almacenamiento cuando sea necesario o cuando se expande el espacio de claves. Las divisiones creadas para manejar el exceso de tráfico se retienen durante aproximadamente 24 horas, incluso si el tráfico desaparece. Por lo tanto, si hay aumentos repentinos de tráfico recurrentes, se mantienen las divisiones y se agregan más divisiones siempre que sea necesario. Estos mecanismos ayudan a las bases de datos de Firestore a escalar de forma automática cuando aumentan la carga de tráfico o el tamaño de la base de datos. Sin embargo, existen algunas limitaciones que se deben tener en cuenta, como se explica a continuación.

Dividir el almacenamiento y la carga lleva tiempo, y aumentar el tráfico demasiado rápido puede causar errores de latencia alta o errores de vencimiento excedido, lo que comúnmente se conoce como hotspots, mientras se ajusta el servicio. La práctica recomendada es distribuir las operaciones dentro del rango de claves, mientras se aumenta el tráfico en una colección, dentro de una base de datos con 500 operaciones por segundo. Después de este aumento gradual, aumenta el tráfico hasta un 50% cada cinco minutos. Este proceso se denomina regla 500/50/5 y posiciona la base de datos para ajustar la escala de manera óptima para satisfacer sus cargas de trabajo.

Aunque las divisiones se crean automáticamente a medida que aumenta la carga, Firestore puede dividir un rango de claves solo hasta que entregue un solo documento con un conjunto exclusivo de servidores de almacenamiento replicado. Como resultado, los volúmenes altos y constantes de las operaciones simultáneas en un solo documento pueden llevar a un hotspot en ese documento. Si encuentras latencias altas constantes en un documento, puedes modificar el modelo de datos para dividir o replicar los datos en múltiples documentos.

Los errores de contención se producen cuando múltiples operaciones intentan realizar operaciones de lectura o escritura en el mismo documento de forma simultánea.

Otro caso especial de generación de hotspots ocurre cuando se usa una clave que aumenta o disminuye de forma secuencial se usa como ID de documento en Firestore y hay una cantidad considerablemente alta de operaciones por segundo. Crear más divisiones no es útil en este caso, ya que el aumento de tráfico simplemente se traslada a la división recién creada. Dado que Firestore indexa automáticamente todos los campos del documento de forma predeterminada, es posible que estos hotspots móviles también se creen en el espacio de índice de un campo de documento que contenga un valor que aumenta o disminuye de forma secuencial, como una marca de tiempo.

Ten en cuenta que, si sigues las prácticas descritas anteriormente, Firestore se puede escalar para entregar cargas de trabajo arbitrariamente grandes sin que tengas que ajustar ninguna configuración.

Soluciona problemas

Firestore proporciona Key Visualizer como una herramienta de diagnóstico diseñada específicamente para analizar los patrones de uso y solucionar problemas relacionados con la generación de hotspots.

Pasos siguientes