Comprendre les lectures et les écritures à grande échelle

Lisez ce document pour prendre des décisions éclairées concernant la conception de l'architecture de vos applications afin de garantir des performances et une fiabilité élevées. Ce document inclut des sujets avancés sur Cloud Firestore. Si vous débutez avec Cloud Firestore, consultez plutôt le guide de démarrage rapide.

Cloud Firestore est une base de données flexible et évolutive conçue par Firebase et Google Cloud pour le développement d'applications mobiles, Web et serveur. La prise en main de Cloud Firestore et le développement d'applications riches et performantes sont très simples.

Pour vous assurer que vos applications continuent de fonctionner correctement à mesure que la taille de votre base de données et le trafic augmentent, il est utile de comprendre le mécanisme des lectures et des écritures dans le backend Cloud Firestore. Vous devez également comprendre l'interaction de vos lectures et écritures avec la couche de stockage et les contraintes sous-jacentes pouvant affecter les performances.

Consultez les sections suivantes pour connaître les bonnes pratiques à suivre avant d'élaborer l'architecture de votre application.

Comprendre les composants de haut niveau

Le diagramme suivant présente les composants de haut niveau impliqués dans une requête API Cloud Firestore.

Composants de haut niveau

SDK Cloud Firestore et bibliothèques clientes

Cloud Firestore est compatible avec les SDK et les bibliothèques clientes pour différentes plates-formes. Bien qu'une application puisse effectuer des appels HTTP et RPC directs à l'API Cloud Firestore, les bibliothèques clientes fournissent une couche d'abstraction pour simplifier l'utilisation de l'API et implémenter les bonnes pratiques. Ils peuvent également fournir des fonctionnalités supplémentaires telles que l'accès hors connexion, les caches, etc.

Google Front End (GFE)

Il s'agit d'un service d'infrastructure commun à tous les services Google Cloud. Le GFE accepte les requêtes entrantes et les transfère au service Google approprié (service Cloud Firestore dans ce contexte). Il fournit également d'autres fonctionnalités importantes, y compris une protection contre les attaques par déni de service.

Service Cloud Firestore

Le service Cloud Firestore effectue des vérifications sur la requête API, y compris l'authentification, l'autorisation, les vérifications de quota et les règles de sécurité, et gère également les transactions. Ce service Cloud Firestore inclut un client de stockage qui interagit avec la couche de stockage pour les lectures et écritures de données.

Couche de stockage Cloud Firestore

La couche de stockage Cloud Firestore est chargée de stocker à la fois les données et les métadonnées, ainsi que les fonctionnalités de base de données associées fournies par Cloud Firestore. Les sections suivantes décrivent comment les données sont organisées dans la couche de stockage Cloud Firestore et comment le système évolue. En savoir plus sur l'organisation des données peut vous aider à concevoir un modèle de données évolutif et à mieux comprendre les bonnes pratiques dans Cloud Firestore.

Plages et divisions de clés

Cloud Firestore est une base de données NoSQL orientée documents. Vous stockez les données dans des documents, qui sont organisés en hiérarchies de collections. La hiérarchie de collection et l'ID de document sont traduits en une seule clé pour chaque document. Les documents sont stockés et triés de manière logique et lexicographique par cette seule clé. Nous utilisons le terme "plage de clés" pour désigner une plage de clés contiguës au niveau lexicographique.

Une base de données Cloud Firestore type est trop volumineuse pour tenir sur une seule machine physique. Il se peut également que la charge de travail sur les données soit trop lourde à traiter pour une seule machine. Pour gérer de lourdes charges de travail, Cloud Firestore partitionne les données en éléments distincts pouvant être stockés et diffusés à partir de plusieurs machines ou serveurs de stockage. Ces partitions sont créées dans les tables de base de données en blocs de plages de clés appelées "splits".

Réplication synchrone

Notez que la base de données est toujours répliquée automatiquement et de manière synchrone. Les données sont réparties en plusieurs zones, et des réplicas sont créés pour qu'elles restent disponibles même si une zone devient inaccessible. La réplication cohérente sur les différentes copies de la partition est gérée par l'algorithme de consensus Paxos. Une instance dupliquée de chaque partition est élue pour agir en tant que variante optimale Paxos, qui est chargée de gérer les écritures sur cette partition. La réplication synchrone vous permet de toujours être en mesure de lire la dernière version des données à partir de Cloud Firestore.

Le résultat global est un système évolutif et hautement disponible qui offre des latences faibles pour les lectures et les écritures, indépendamment des charges de travail importantes et à très grande échelle.

Mise en page des données

Cloud Firestore est une base de données de documents sans schéma. Toutefois, en interne, il organise les données principalement dans deux tables de style base de données relationnelle dans sa couche de stockage, comme suit:

  • Table Documents: les documents sont stockés dans cette table.
  • Tableau Indexes (Index) : les entrées d'index qui permettent d'obtenir des résultats efficaces et triés par valeur d'index sont stockées dans ce tableau.

Le diagramme suivant montre à quoi peuvent ressembler les tables d'une base de données Cloud Firestore avec les divisions. Les partitions sont répliquées dans trois zones différentes, et une variante optimale Paxos est attribuée à chacune d'elles.

Disposition des données

Région unique ou multirégionale

Lorsque vous créez une base de données, vous devez sélectionner une région ou une multirégion.

Un emplacement régional correspond à un emplacement géographique spécifique, comme us-west1. Comme expliqué précédemment, les divisions de données d'une base de données Cloud Firestore comportent des instances répliquées dans différentes zones de la région sélectionnée.

Un emplacement multirégional se compose d'un ensemble défini de régions dans lesquelles les répliques de la base de données sont stockées. Dans un déploiement multirégional de Cloud Firestore, deux des régions disposent de répliques complètes de l'ensemble des données de la base de données. Une troisième région dispose d'un remplacement témoin qui ne conserve pas un ensemble complet de données, mais qui participe à la réplication. En répliquant les données entre plusieurs régions, vous pouvez les écrire et les lire même en cas de perte d'une région entière.

Pour en savoir plus sur les emplacements d'une région, consultez la section Emplacements Cloud Firestore.

Région unique ou multirégionale

Comprendre la durée de vie d'une écriture dans Cloud Firestore

Un client Cloud Firestore peut écrire des données en créant, en mettant à jour ou en supprimant un seul document. Une écriture dans un seul document nécessite une mise à jour atomique du document et de ses entrées d'index associées dans la couche de stockage. Cloud Firestore accepte également les opérations atomiques composées de plusieurs lectures et/ou écritures sur un ou plusieurs documents.

Pour tous les types d'écritures, Cloud Firestore fournit les propriétés ACID (atomicité, cohérence, isolation et durabilité) des bases de données relationnelles. Cloud Firestore fournit également la sérialisabilité, ce qui signifie que toutes les transactions apparaissent comme si elles étaient exécutées dans un ordre séquentiel.

Principales étapes d'une transaction d'écriture

Lorsque le client Cloud Firestore effectue une écriture ou valide une transaction à l'aide de l'une des méthodes mentionnées précédemment, cette opération est exécutée en interne en tant que transaction de base de données en lecture-écriture dans la couche de stockage. La transaction permet à Cloud Firestore de fournir les propriétés ACID mentionnées précédemment.

Lors de la première étape d'une transaction, Cloud Firestore lit le document existant et détermine les mutations à apporter aux données de la table "Documents".

Cela implique également d'apporter les mises à jour nécessaires au tableau des index, comme suit:

  • Les champs ajoutés aux documents doivent être insérés dans le tableau "Indexes".
  • Les champs supprimés des documents doivent être supprimés de la table "Indexes".
  • Les champs en cours de modification dans les documents nécessitent à la fois des suppressions (pour les anciennes valeurs) et des insertions (pour les nouvelles valeurs) dans la table d'index.

Pour calculer les mutations mentionnées précédemment, Cloud Firestore lit la configuration d'indexation du projet. La configuration d'indexation stocke des informations sur les index d'un projet. Cloud Firestore utilise deux types d'index: les index à champ unique et les index composites. Pour en savoir plus sur les index créés dans Cloud Firestore, consultez la section Types d'index dans Cloud Firestore.

Une fois les mutations calculées, Cloud Firestore les collecte dans une transaction, puis les valide.

Comprendre une transaction d'écriture dans la couche de stockage

Comme indiqué précédemment, une écriture dans Cloud Firestore implique une transaction en lecture-écriture dans la couche de stockage. Selon la mise en page des données, une écriture peut impliquer une ou plusieurs divisions, comme illustré dans la mise en page des données.

Dans le schéma suivant, la base de données Cloud Firestore comporte huit divisions (1 à 8) hébergées sur trois serveurs de stockage différents dans une même zone, et chaque division est répliquée dans trois zones différentes(ou plus). Chaque partition a un leader Paxos, qui peut se trouver dans une zone différente pour chaque partition.

<span class=Division de la base de données Cloud Firestore">

Prenons l'exemple d'une base de données Cloud Firestore contenant la collection Restaurants comme suit:

Collection de restaurants

Le client Cloud Firestore demande la modification suivante pour un document de la collection Restaurant en mettant à jour la valeur du champ priceCategory.

Passer à un document de la collection

Les étapes générales suivantes décrivent ce qui se passe lors de l'écriture:

  1. Créez une transaction en lecture-écriture.
  2. Lisez le document restaurant1 de la collection Restaurants dans la table Documents de la couche de stockage.
  3. Lisez les index du document à partir de la table Index.
  4. Calculez les mutations à apporter aux données. Dans ce cas, il existe cinq mutations :
    • M1: Mettez à jour la ligne de restaurant1 dans le tableau Documents pour refléter la modification de la valeur du champ priceCategory.
    • M2 et M3: supprimez les lignes correspondant à l'ancienne valeur priceCategory dans le tableau Indexes (Index) pour les index ascendants et descendants.
    • M4 et M5: insérez les lignes pour la nouvelle valeur de priceCategory dans le tableau Index pour les index ascendants et descendants.
  5. Effectuez un commit sur ces mutations.

Le client de stockage du service Cloud Firestore recherche les divisions qui possèdent les clés des lignes à modifier. Prenons l'exemple où la partition 3 sert M1 et la partition 6 sert M2 à M5. Une transaction distribuée implique toutes ces divisions en tant que participants. Les fractionnements des participants peuvent également inclure tout autre fractionnement à partir duquel des données ont été lues précédemment dans le cadre de la transaction en lecture-écriture.

Les étapes suivantes décrivent ce qui se passe lors du commit:

  1. Le client de stockage émet une validation. Le commit contient les mutations M1 à M5.
  2. Les partitions 3 et 6 sont les participants à cette transaction. L'un des participants est choisi comme coordinateur, par exemple le groupe 3. Celui-ci a pour tâche de s'assurer que la transaction est validée ou annulée de manière atomique pour tous les participants.
    • Les répliques principales de ces divisions sont responsables du travail effectué par les participants et les coordinateurs.
  3. Chaque participant et coordinateur exécute un algorithme Paxos avec ses réplications respectives.
    • Le leader exécute un algorithme Paxos avec les répliques. Le quorum est atteint si la plupart des réplicas répondent à la variante optimale avec une réponse ok to commit.
    • Chaque participant informe ensuite le coordinateur lorsqu'il est prêt (première phase de la validation en deux phases). Si l'un des participants ne peut pas valider la transaction, l'intégralité de la transaction aborts.
  4. Une fois que le coordinateur sait que tous les participants, y compris lui-même, sont prêts, il communique le résultat de la transaction accept à tous les participants (deuxième phase de la validation en deux phases). Au cours de cette phase, chaque participant enregistre la décision de validation dans un stockage stable, et la transaction est validée.
  5. Le coordinateur répond au client de stockage dans Cloud Firestore que la transaction a été validée. En parallèle, le coordinateur et tous les participants appliquent les mutations aux données.

Cycle de vie des commits

Lorsque la base de données Cloud Firestore est petite, il est possible qu'une seule division possède toutes les clés des mutations M1 à M5. Dans ce cas, il n'y a qu'un seul participant à la transaction et le commit en deux phases mentionné précédemment n'est pas nécessaire, ce qui accélère les écritures.

Écriture dans un emplacement multirégional

Dans un déploiement multirégional, la répartition des instances répliquées entre les régions augmente la disponibilité, mais a un impact sur les performances. La communication entre les réplicas dans différentes régions prend plus de temps. Par conséquent, la latence de référence pour les opérations Cloud Firestore est légèrement supérieure par rapport aux déploiements dans une seule région.

Nous configurons les réplicas de manière à ce que la responsabilité des répartitions reste toujours dans la région principale. La région principale est celle d'où le trafic est reçu par le serveur Cloud Firestore. Cette décision de la direction réduit le délai aller-retour dans la communication entre le client de stockage dans Cloud Firestore et le responsable de l'instance répliquée (ou le coordinateur pour les transactions multi-splits).

Chaque écriture dans Cloud Firestore implique également une interaction avec le moteur en temps réel de Cloud Firestore. Pour en savoir plus sur les requêtes en temps réel, consultez Comprendre les requêtes en temps réel à grande échelle.

Comprendre le cycle de vie d'une lecture dans Cloud Firestore

Cette section décrit les lectures autonomes hors temps réel dans Cloud Firestore. En interne, le serveur Cloud Firestore traite la plupart de ces requêtes en deux étapes principales:

  1. Analyse de plage unique sur la table Indexes
  2. Recherches de points dans la table Documents en fonction du résultat de l'analyse précédente
Certaines requêtes peuvent nécessiter moins ou plus de traitement (par exemple, les requêtes IN) dans Cloud Firestore.

Les lectures de données à partir de la couche de stockage sont effectuées en interne à l'aide d'une transaction de base de données pour assurer la cohérence des lectures. Toutefois, contrairement aux transactions utilisées pour les écritures, ces transactions ne sont pas verrouillées. Au lieu de cela, elles choisissent un code temporel, puis exécutent toutes les lectures à ce code temporel. Comme elles n'acquièrent pas de verrous, elles ne bloquent pas les transactions en lecture-écriture simultanées. Pour exécuter cette transaction, le client de stockage dans Cloud Firestore spécifie une limite d'horodatage, qui indique à la couche de stockage comment choisir un code temporel de lecture. Le type de limite d'horodatage choisi par le client de stockage dans Cloud Firestore est déterminé par les options de lecture de la requête de lecture.

Comprendre une transaction de lecture dans la couche de stockage

Cette section décrit les types de lectures et la façon dont elles sont traitées dans la couche de stockage de Cloud Firestore.

Lectures fortes

Par défaut, les lectures Cloud Firestore sont fortement cohérentes. Cette cohérence forte signifie qu'une lecture Cloud Firestore renvoie la dernière version des données, qui reflète toutes les écritures validées jusqu'au début de la lecture.

Lecture avec division unique

Le client de stockage dans Cloud Firestore recherche les partitions propriétaires des clés des lignes à lire. Supposons qu'il doit effectuer une lecture à partir de la section 3 de la section précédente. Le client envoie la requête de lecture au réplica le plus proche pour réduire la latence aller-retour.

À ce stade, les cas suivants peuvent se produire en fonction du réplica choisi:

  • La requête de lecture est envoyée à une instance dupliquée principale (zone A).
    • Cette dernière étant toujours à jour, la lecture peut se dérouler directement.
  • La requête de lecture est envoyée à une instance dupliquée non principale (par exemple, la zone B).
    • La scission 3 peut savoir par son état interne qu'elle dispose d'informations suffisantes pour diffuser la lecture, et elle le fait.
    • La division 3 n'est pas sûre d'avoir vu les données les plus récentes. Il envoie un message au leader pour lui demander l'horodatage de la dernière transaction à appliquer pour diffuser la lecture. Une fois que cette transaction est appliquée, la lecture peut avoir lieu.

Cloud Firestore renvoie ensuite la réponse à son client.

Lecture multi-partitionnée

Si les lectures doivent être effectuées à partir de plusieurs divisions, le même mécanisme s'applique à toutes les divisions. Une fois que les données ont été renvoyées à partir de tous les fractionnements, le client de stockage dans Cloud Firestore combine les résultats. Cloud Firestore répond ensuite à son client avec ces données.

Lectures non actualisées

Les lectures strictes sont le mode par défaut dans Cloud Firestore. Cependant, cela entraîne une latence potentiellement plus élevée en raison de la communication qui peut être requise avec le leader. Souvent, votre application Cloud Firestore n'a pas besoin de lire la dernière version des données, et cette fonctionnalité fonctionne bien avec des données qui peuvent être obsolètes de quelques secondes.

Dans ce cas, le client peut choisir de recevoir des lectures non actualisées à l'aide des options de lecture read_time. Dans ce cas, les lectures sont effectuées comme si les données se trouvaient à read_time, et il est très probable que le réplica le plus proche ait déjà vérifié qu'il dispose de données à l'read_time spécifié. Pour des performances nettement supérieures, une valeur d'obsolescence raisonnable est de 15 secondes. Même pour les lectures obsolètes, les lignes générées sont cohérentes entre elles.

Éviter les hotspots

Les divisions dans Cloud Firestore sont automatiquement divisées en éléments plus petits pour répartir la tâche de diffusion du trafic sur davantage de serveurs de stockage si nécessaire ou lorsque l'espace de clés se développe. Les divisions créées pour gérer le trafic excédentaire sont conservées pendant environ 24 heures, même si le trafic disparaît. Ainsi, en cas de pics de trafic récurrents, les fractionnements sont maintenus et d'autres sont introduits si nécessaire. Ces mécanismes aident les bases de données Cloud Firestore à s'adapter automatiquement lorsque la charge du trafic ou la taille de la base de données augmente. Toutefois, certaines restrictions s'appliquent, comme indiqué ci-dessous.

La division du stockage et du chargement prend du temps. Une augmentation trop rapide du trafic peut entraîner des erreurs de latence élevée ou de dépassement du délai, communément appelées hotspots, le temps que le service s'ajuste. Il est recommandé de répartir les opérations sur la plage de clés, tout en augmentant le trafic sur une collection dans une base de données avec 500 opérations par seconde. Après cette augmentation progressive, augmentez le trafic jusqu'à 50% toutes les cinq minutes. Ce processus est appelé règle 500/50/5. Il permet de positionner la base de données de manière à l'adapter de manière optimale à votre charge de travail.

Bien que les divisions soient créées automatiquement avec une charge croissante, Cloud Firestore ne peut diviser une plage de clés que jusqu'à ce qu'il diffuse un seul document à l'aide d'un ensemble dédié de serveurs de stockage répliqués. Par conséquent, des volumes élevés et soutenus d'opérations simultanées sur un même document peuvent entraîner un point chaud sur ce document. Si vous rencontrez des latences élevées et durables sur un seul document, envisagez de modifier votre modèle de données pour diviser ou répliquer les données sur plusieurs documents.

Les erreurs de contention se produisent lorsque plusieurs opérations tentent de lire et/ou d'écrire le même document simultanément.

Un autre cas particulier de hotspotting se produit lorsqu'une clé qui augmente ou diminue de manière séquentielle est utilisée comme ID de document dans Cloud Firestore, et que le nombre d'opérations par seconde est considérablement élevé. Créer d'autres répartitions ne vous aidera pas, car le pic de trafic sera simplement redirigé vers la nouvelle répartition. Comme Cloud Firestore indexe automatiquement tous les champs du document par défaut, de tels hotspots mobiles peuvent également être créés dans l'espace d'index pour un champ de document contenant une valeur séquentielle croissante/décroissante, comme un code temporel.

Notez qu'en suivant les pratiques décrites ci-dessus, Cloud Firestore peut s'adapter pour traiter des charges de travail arbitrairement volumineuses sans que vous ayez à ajuster de configuration.

Dépannage

Cloud Firestore fournit Key Visualizer, un outil de diagnostic conçu pour analyser les schémas d'utilisation et résoudre les problèmes de point chaud.

Étape suivante