Comprendre les opérations de lecture et d'écriture à 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 aborde des sujets avancés concernant Firestore. Si vous débutez avec Firestore, consultez le guide de démarrage rapide.

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 opérations de lecture et d'écriture dans le backend Firestore. Vous devez également comprendre l'interaction de vos opérations de lecture et d'écriture avec la couche de stockage, ainsi que les contraintes sous-jacentes qui peuvent affecter les performances.

Avant de concevoir l'architecture de votre application, consultez les bonnes pratiques décrites dans les sections suivantes.

Comprendre les composants de haut niveau

Le schéma suivant illustre les composants de haut niveau impliqués dans une requête API Firestore.

Composants de haut niveau

SDK, bibliothèques clientes et pilotes

Firestore est compatible avec les SDK, les bibliothèques clientes et les pilotes pour différentes plates-formes.

Google Front End (GFE)

Il s'agit d'un service d'infrastructure commun à tous les Google Cloud services. Le GFE accepte les requêtes entrantes et les transmet au service Google approprié (le service Firestore dans ce contexte).

Service Firestore

Le service Firestore effectue des vérifications sur la requête API, y compris l'authentification, l'autorisation et les vérifications de quota, et gère également les transactions. Ce service Firestore inclut un client de stockage qui interagit avec la couche de stockage pour les opérations de lecture et d'écriture des données.

Couche de stockage Firestore

La couche de stockage Firestore est chargée de stocker 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.

Plages de clés et partitions

Firestore est une base de données NoSQL orientée documents. Vous stockez les données dans des documents organisés en collections. Le nom de la collection et l'ID du document forment une clé unique pour un document. Les documents d'une même collection sont stockés ensemble dans un espace de clés. Dans cet espace de clés, l'ID du document est haché. Le terme plage de clés fait référence à une plage contiguë de clés dans le stockage.

Firestore partitionne automatiquement les données des collections sur plusieurs serveurs de stockage. Ces partitions sont appelées partitions.

Les documents peuvent générer des entrées d'index qui sont classées par ordre lexicographique et participent au même type de fractionnement et de placement que les données du document.

Réplication synchrone

Chaque écriture est répliquée de manière synchrone sur une majorité d'instances dupliquées à l'aide de Paxos. Une instance dupliquée par partition est considérée comme principale et coordonne le processus de réplication. En cas de défaillance de l'instance principale, une nouvelle instance est élue. Les instances dupliquées sont situées dans différentes zones pour résister aux éventuelles défaillances de zone. Le résultat global est un système évolutif et disponibilité élevée qui offre de faibles latences pour les opérations de lecture et d'écriture, quelles que soient les charges de travail importantes et à très grande échelle.

Région unique ou multirégionale

Lorsque vous créez une base de données, vous devez sélectionner un emplacement régional unique ou un emplacement multirégional.

Un emplacement régional unique est un emplacement géographique spécifique, tel que us-west1. Les partitions de données d'une base de données Firestore comportent des instances dupliquées 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 Firestore stocke des instances dupliquées de la base de données. Dans un déploiement multirégional de Firestore, deux régions disposent d'instances dupliquées complètes de l'ensemble des données de la base de données. Une troisième région dispose d'une instance dupliquée témoin qui ne conserve pas un ensemble complet de données, mais participe à la réplication. Les données peuvent être écrites et lues même en cas de perte d'une région entière, car Firestore réplique les données entre plusieurs régions.

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

Région unique ou multirégion

Comprendre le cycle de vie d'une écriture

Un pilote peut écrire des données en créant, en mettant à jour ou en supprimant un seul document. L'é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 composées de plusieurs opérations de lecture et d'écriture dans 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 une sérialisabilité, ce qui signifie que toutes les transactions apparaissent comme si elles étaient exécutées dans un ordre sériel.

Étapes générales d'une transaction d'écriture

Lorsque le pilote émet une écriture ou valide une transaction à l'aide de l'une des méthodes mentionnées précédemment, elle est exécutée en interne en tant que transaction de lecture-écriture de base de données 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 du document.

Cela inclut également la mise à jour des index pertinents :

  • Les champs indexés ajoutés aux documents doivent être insérés dans les index.
  • Les champs indexés supprimés des documents doivent être supprimés des index.
  • Les champs indexés modifiés dans les documents doivent être supprimés (pour les anciennes valeurs) et insérés (pour les nouvelles valeurs) dans les 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.

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 de lecture-écriture dans la couche de stockage. En fonction de la mise en page des données, une écriture peut impliquer une ou plusieurs partitions.

Dans le schéma suivant, la base de données Firestore comporte huit partitions (marquées de 1 à 8) hébergées sur trois serveurs de stockage différents dans une seule zone, et chaque partition est répliquée dans trois zones différentes(ou plus). Chaque partition possède une instance principale Paxos, qui peut se trouver dans une zone différente pour différentes partitions.

Fractionnement de la base de données Firestore

Prenons l'exemple d'une base de données Firestore dont la collection Restaurants est la suivante :

Collection de restaurants

Le pilote demande la modification suivante d'un document de la collection Restaurant en mettant à jour la valeur du champ priceCategory.

Modification apportée à un document dans une 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 dans la collection Restaurants.
  3. Lisez les index du document.
  4. Calculez les mutations à apporter aux données. Dans ce cas, il existe cinq mutations :
    • M1 : mettez à jour la ligne de restaurant1 pour refléter la modification de la valeur du champ priceCategory.
    • M2 et M3 : supprimez les anciennes entrées d'index pour priceCategory.
    • M4 et M5 : ajoutez de nouvelles entrées d'index pour priceCategory.
  5. Validez ces mutations.

Le client de stockage du service Firestore recherche les partitions qui possèdent les clés des lignes à modifier. Prenons l'exemple où la partition 3 diffuse M1 et la partition 6 diffuse M2-M5. Il existe une transaction distribuée impliquant toutes ces partitions en tant que participants. Les partitions participantes peuvent également inclure toute autre partition à partir de laquelle des données ont été lues précédemment dans le cadre de la transaction de lecture-écriture.

Les étapes suivantes décrivent ce qui se passe lors de la validation :

  1. Le client de stockage émet une validation. La validation contient les mutations M1-M5.
  2. Les partitions 3 et 6 sont les participants de cette transaction. L'un des participants est choisi comme coordinateur, par exemple la partition 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 instances dupliquées principales de ces partitions sont responsables du travail effectué par les participants et les coordinateurs.
  3. Chaque participant et coordinateur exécute un algorithme Paxos avec ses instances dupliquées respectives.
    • L'instance principale exécute un algorithme Paxos avec les instances dupliquées. Le quorum est atteint si la plupart des instances dupliquées répondent à l'instance principale par une réponse ok to commit.
    • Chaque participant informe ensuite le coordinateur lorsqu'il est préparé (première phase de la validation en deux phases). Si un participant ne peut pas valider la transaction, toute la transaction est aborts.
  4. Une fois que le coordinateur sait que tous les participants, y compris lui-même, sont préparés, il communique le résultat de la transaction accept à tous les participants (deuxième phase de la validation en deux phases). Dans 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 Firestore que la transaction a été validée. Parallèlement, le coordinateur et tous les participants appliquent les mutations aux données.

Cycle de vie des commits

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

Écritures dans une région multirégionale

Dans un déploiement multirégional, la répartition des instances dupliquées entre les régions augmente la disponibilité, mais a un coût en termes de performances. La communication entre les instances dupliquées de différentes régions prend plus de temps. Par conséquent, la latence de base des opérations Firestore est légèrement supérieure à celle des déploiements à région unique.

Nous configurons les instances dupliquées de sorte que la direction des partitions reste toujours dans la région principale. La région principale est celle à partir de laquelle le trafic arrive sur 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 l'instance principale dupliquée (ou le coordinateur pour les transactions à plusieurs partitions).

Comprendre le cycle de vie d'une lecture

Cette section traite des opérations de lecture dans Firestore. Les requêtes, en particulier, sont composées d'un mélange de lectures de documents et d'entrées d'index.

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 garantir des lectures cohérentes. Toutefois, contrairement aux transactions utilisées pour les écritures, ces transactions ne prennent pas de verrous. Au lieu de cela, elles fonctionnent en choisissant un horodatage, puis en exécutant toutes les lectures à cet horodatage. Comme elles n'acquièrent pas de verrous, elles ne bloquent pas les transactions de 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 horodatage 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 leur traitement dans la couche de stockage de Firestore.

Lectures fortes

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

Lecture d'une seule partition

Le client de stockage dans Firestore recherche les partitions qui possèdent les clés des lignes à lire. Supposons qu'il doive effectuer une lecture à partir de la partition 3 de la section précédente. Le client envoie la requête de lecture à l'instance dupliquée la plus proche pour réduire la latence aller-retour.

À ce stade, les cas suivants peuvent se produire en fonction de l'instance dupliquée choisie :

  • La requête de lecture est transmise à une instance dupliquée principale (zone A).
    • Celle-ci étant toujours à jour, la lecture peut se dérouler directement.
  • La requête de lecture est transmise à une instance dupliquée non principale (par exemple, la zone B).
    • La partition 3 peut savoir par son état interne qu'elle dispose de suffisamment d'informations pour diffuser la lecture, et elle le fait.
    • La partition 3 n'est pas sûre d'avoir connaissance des données les plus récentes. Elle envoie un message à l'instance principale pour lui demander l'horodatage de la dernière transaction à appliquer pour pouvoir 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 de plusieurs partitions

Lorsque les lectures doivent être effectuées à partir de plusieurs partitions, le même mécanisme se produit sur toutes les partitions. Une fois les données renvoyées par toutes les partitions, le client de stockage dans Firestore combine les résultats. Firestore répond ensuite à son client avec ces données.

Éviter les hotspots

Les partitions de Firestore sont automatiquement divisées en plus petits morceaux pour répartir le travail de diffusion du trafic sur davantage de serveurs de stockage si nécessaire ou lorsque l'espace de clés s'étend. Les partitions créées pour gérer l'excès de trafic sont conservées pendant environ 24 heures, même si le trafic disparaît. Ainsi, en cas de pics de trafic récurrents, les partitions sont conservées et d'autres partitions sont introduites si nécessaire. Ces mécanismes aident les bases de données Firestore à effectuer un autoscaling en cas d'augmentation de la charge de trafic ou de la taille de la base de données. Toutefois, certaines limites s'appliquent.

Le fractionnement du stockage et de la charge prend du temps, et une augmentation trop rapide du trafic peut entraîner une latence élevée ou des erreurs de dépassement de délai, communément appelées hotspots, pendant que le service s'ajuste. La bonne pratique consiste à répartir les opérations sur la plage de clés, tout en augmentant progressivement le trafic sur une collection dans une base de données.

Bien que les partitions soient créées automatiquement en cas d'augmentation de la charge, Firestore ne peut fractionner une plage de clés que jusqu'à ce qu'elle 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 seul document peuvent entraîner un hotspot sur ce document. Si vous constatez des latences élevées et soutenues sur un seul document, vous devez envisager de modifier votre modèle de données pour fractionner ou répliquer les données sur plusieurs documents.

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

Notez qu'en suivant les pratiques décrites sur cette page, Firestore peut évoluer pour traiter des charges de travail arbitrairement importantes sans que vous ayez à ajuster la configuration.