Comprendre les lectures et les écritures à grande échelle
Lisez ce document pour prendre des décisions éclairées sur l'architecture de vos applications afin d'obtenir des performances et une fiabilité élevées. Ce document inclut des sujets avancés sur Firestore. Si vous débutez avec Firestore, consultez plutôt le guide de démarrage rapide.
Créé par Firebase et Google Cloud, Firestore est une base de données flexible et évolutive conçue pour le développement mobile, Web et serveur. Il est très facile de se lancer avec Firestore et d'écrire des applications riches et performantes.
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 fonctionnement des lectures et des écritures dans le backend Firestore. Vous devez également comprendre l'interaction de vos lectures et écritures avec la couche de stockage et les contraintes sous-jacentes qui peuvent 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 Firestore.
SDK Firestore et bibliothèques clientes
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 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 Firestore dans ce contexte). Il offre également d'autres fonctionnalités importantes, y compris une protection contre les attaques par déni de service.
Service Firestore
Le service Firestore effectue des vérifications de 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 Firestore inclut un client de stockage qui interagit avec la couche de stockage pour les lectures et les écritures de données.
Couche de stockage Firestore
La couche de stockage 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 Firestore. Les sections suivantes décrivent comment les données sont organisées dans la couche de stockage Firestore et comment le système évolue. Comprendre comment les données sont organisées peut vous aider à concevoir un modèle de données évolutif et à mieux comprendre les bonnes pratiques dans Firestore.
Plage de clés et fractionnement
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 Firestore typique est trop volumineuse pour être stockée 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, Firestore partitionne les données en éléments distincts qui peuvent ê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
Il est important de noter 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 lire la dernière version des données de Firestore.
Le résultat global est un système évolutif et disponibilité élevée 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
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 de manière efficace 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 Firestore avec les divisions. Les partitions sont répliquées dans trois zones différentes, et une variante optimale Paxos est attribuée à chaque partition.
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. Les divisions de données d'une base de données Firestore ont des répliques dans différentes zones de la région sélectionnée, comme expliqué précédemment.
Un emplacement multirégional se compose d'un ensemble défini de régions dans lesquelles les réplicas de la base de données sont stockés. Dans un déploiement multirégional de 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 page Emplacements Firestore.
Comprendre la durée de vie d'une écriture dans Firestore
Un client 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 la mise à jour atomique du document et de ses entrées d'index associées dans la couche de stockage. Firestore accepte également les opérations atomiques consistant en plusieurs lectures et/ou écritures sur un ou plusieurs documents.
Pour tous les types d'écritures, Firestore fournit les propriétés ACID (atomicité, cohérence, isolation et durabilité) des bases de données relationnelles. 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.
Étapes générales d'une transaction d'écriture
Lorsque le client 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 à Firestore de fournir les propriétés ACID mentionnées précédemment.
Lors de la première étape d'une transaction, Firestore lit le document existant et détermine les mutations à apporter aux données de la table "Documents".
Vous devez également mettre à jour la table "Indexes" 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 qui sont modifiés dans les documents nécessitent à la fois des suppressions (pour les anciennes valeurs) et des insertions (pour les nouvelles valeurs) dans le tableau des index.
Pour calculer les mutations mentionnées précédemment, Firestore lit la configuration d'indexation du projet. La configuration d'indexation stocke des informations sur les index d'un projet. Firestore utilise deux types d'index: les index à champ unique et les index composites. Pour en savoir plus sur les index créés dans Firestore, consultez Types d'index dans Firestore.
Une fois les mutations calculées, 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 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 Firestore comporte huit divisions (1 à 8) hébergées sur trois serveurs de stockage différents dans une même zone. Chaque division est répliquée dans trois zones(ou plus). Chaque partition a un leader Paxos, qui peut se trouver dans une zone différente pour chaque partition.
Prenons l'exemple d'une base de données Firestore dont la collection Restaurants
se présente comme suit:
Le client Firestore demande la modification suivante à un document de la collection Restaurant
en mettant à jour la valeur du champ priceCategory
.
Les étapes générales suivantes décrivent ce qui se passe lors de l'écriture:
- Créez une transaction en lecture-écriture.
- Lisez le document
restaurant1
de la collectionRestaurants
dans la table Documents de la couche de stockage. - Lisez les index du document dans la table Indexes (Index).
- 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 champpriceCategory
. - 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.
- M1: Mettez à jour la ligne de
- Effectuez un commit sur ces mutations.
Le client de stockage du service Firestore recherche les partitions propriétaires des clés des lignes à modifier. Prenons l'exemple d'une situation 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:
- Le client de stockage émet une validation. Le commit contient les mutations M1 à M5.
- 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éplicas principaux de ces partitions sont responsables du travail effectué par les participants et les coordinateurs.
- 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 avertit 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
.
- 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
- 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. - Le coordinateur répond au client de stockage dans Firestore que la transaction a été validée. Parallèlement, le coordinateur et tous les participants appliquent les mutations aux données.
Lorsque la base de données Firestore est petite, il est possible qu'un seul fractionnement 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.
Écritures dans plusieurs régions
Dans un déploiement multirégional, la répartition des réplicas 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 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ù provient le trafic vers le serveur Firestore. Cette décision de leadership réduit le délai aller-retour de la communication entre le client de stockage dans Firestore et le leader du réplica (ou coordinateur pour les transactions multi-fractionnées).
Chaque écriture dans Firestore implique également une interaction avec le moteur en temps réel de 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 Firestore
Cette section présente les lectures autonomes hors temps réel dans Firestore. En interne, le serveur Firestore gère la plupart de ces requêtes en deux grandes étapes:
- Analyse de plage unique sur la table Indexes
- Recherches ponctuelles dans le tableau Documents en fonction du résultat de l'analyse précédente
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 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 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 Firestore.
Lectures fortes
Par défaut, les lectures Firestore sont fortement cohérentes. Cette cohérence forte signifie qu'une lecture Firestore renvoie la dernière version des données qui reflète toutes les écritures qui ont été effectuées jusqu'au début de la lecture.
Lecture avec division unique
Le client de stockage de 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 de suffisamment d'informations pour diffuser la lecture, et elle le fait.
- La division 3 n'est pas sûre d'avoir vu les dernières données. 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.
Firestore renvoie ensuite la réponse à son client.
Lecture multi-partitionnée
Lorsque les lectures doivent être effectuées à partir de plusieurs divisions, le même mécanisme s'applique à toutes les divisions. Une fois les données renvoyées à partir de toutes les divisions, le client de stockage de Firestore combine les résultats. Firestore répond ensuite à son client avec ces données.
Lectures non actualisées
Les lectures strictes sont le mode par défaut dans Firestore. Cependant, cela entraîne une latence potentiellement plus élevée en raison de la communication qui peut être requise avec le leader. Votre application Firestore n'a souvent pas besoin de lire la dernière version des données, et la 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 obsolètes à 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 obtenir des performances nettement meilleures, 15 secondes est une valeur d'obsolescence raisonnable. Même pour les lectures obsolètes, les lignes générées sont cohérentes entre elles.
Éviter les zones à forte activité
Les divisions dans Firestore sont automatiquement divisées en parties plus petites pour répartir la distribution du trafic vers davantage de serveurs de stockage si nécessaire ou lorsque l'espace de clés augmente. Les répartitions 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 permettent aux bases de données Firestore de s'adapter automatiquement à une augmentation de la charge de trafic ou de la taille de la base de données. Toutefois, certaines limites s'appliquent, comme indiqué ci-dessous.
La répartition du stockage et de la charge prend du temps. Si vous augmentez trop rapidement le trafic, vous risquez de rencontrer des erreurs de latence élevée ou de délai dépassé (appelées communément points chauds), pendant 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 optimale pour qu'elle s'adapte à votre charge de travail.
Bien que des divisions soient créées automatiquement à mesure que la charge augmente, Firestore ne peut diviser une plage de clés que jusqu'à ce qu'il ne serve qu'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 seul 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 point chaud se produit lorsqu'une clé à incrément/décrément séquentiel est utilisée comme ID de document dans Firestore et qu'un nombre particulièrement élevé d'opérations par seconde est effectué. Créer d'autres répartitions ne vous aidera pas, car le pic de trafic sera simplement redirigé vers la nouvelle répartition. Comme 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 que si vous suivez les pratiques décrites ci-dessus, Firestore peut évoluer pour répondre à des charges de travail arbitrairement importantes sans que vous ayez à ajuster la configuration.
Dépannage
Firestore fournit Key Visualizer, un outil de diagnostic conçu pour analyser les schémas d'utilisation et résoudre les problèmes de hotspots.
Étape suivante
- En savoir plus sur les bonnes pratiques
- En savoir plus sur les requêtes en temps réel à grande échelle