Quel est le moyen le plus rapide pour écrire un grand nombre de documents dans Firestore?

11

Je dois écrire un grand nombre de documents dans Firestore.

Quel est le moyen le plus rapide de le faire dans Node.js?

Frank van Puffelen
la source

Réponses:

26

TL; DR: Le moyen le plus rapide pour effectuer une création de date en bloc sur Firestore est d'effectuer des opérations d'écriture individuelles parallèles.

Écrire 1000 documents dans Firestore prend:

  1. ~105.4s lors de l'utilisation d'opérations d'écriture individuelles séquentielles
  2. ~ 2.8s lors de l'utilisation de (2) opérations d'écriture par lots
  3. ~ 1.5s lors de l'utilisation d'opérations d'écriture individuelles parallèles

Il existe trois façons courantes d'effectuer un grand nombre d'opérations d'écriture sur Firestore.

  1. Effectuez chaque opération d'écriture individuelle en séquence.
  2. Utilisation d'opérations d'écriture par lots.
  3. Effectuer des opérations d'écriture individuelles en parallèle.

Nous étudierons chacun d'eux tour à tour ci-dessous, en utilisant un tableau de données de document randomisées.


Opérations d'écriture séquentielles individuelles

C'est la solution la plus simple possible:

async function testSequentialIndividualWrites(datas) {
  while (datas.length) {
    await collection.add(datas.shift());
  }
}

Nous écrivons chaque document tour à tour, jusqu'à ce que nous ayons écrit chaque document. Et nous attendons la fin de chaque opération d'écriture avant de commencer la suivante.

L'écriture de 1 000 documents prend environ 105 secondes avec cette approche, donc le débit est d'environ 10 écritures de document par seconde .


Utilisation d'opérations d'écriture par lots

Il s'agit de la solution la plus complexe.

async function testBatchedWrites(datas) {
  let batch = admin.firestore().batch();
  let count = 0;
  while (datas.length) {
    batch.set(collection.doc(Math.random().toString(36).substring(2, 15)), datas.shift());
    if (++count >= 500 || !datas.length) {
      await batch.commit();
      batch = admin.firestore().batch();
      count = 0;
    }
  }
}

Vous pouvez voir que nous créons un BatchedWriteobjet en appelant batch(), le remplissons jusqu'à sa capacité maximale de 500 documents, puis l'écrivons dans Firestore. Nous donnons à chaque document un nom généré qui est relativement susceptible d'être unique (assez bon pour ce test).

L'écriture de 1 000 documents prend environ 2,8 secondes avec cette approche, le débit est donc d'environ 357 documents écrits par seconde .

C'est un peu plus rapide qu'avec les écritures séquentielles individuelles. En fait: de nombreux développeurs utilisent cette approche car ils supposent qu'elle est la plus rapide, mais comme les résultats ci-dessus l'ont déjà montré, ce n'est pas vrai. Et le code est de loin le plus complexe, en raison de la contrainte de taille sur les lots.


Opérations d'écriture individuelles parallèles

La documentation Firestore en dit long sur les performances d'ajout de nombreuses données :

Pour la saisie de données en masse, utilisez une bibliothèque cliente de serveur avec des écritures individuelles parallélisées. Les écritures par lots fonctionnent mieux que les écritures sérialisées mais pas mieux que les écritures parallèles.

Nous pouvons mettre cela à l'épreuve avec ce code:

async function testParallelIndividualWrites(datas) {
  await Promise.all(datas.map((data) => collection.add(data)));
}

Ce code lance les addopérations aussi vite que possible, puis utilise Promise.all()pour attendre qu'elles soient toutes terminées. Avec cette approche, les opérations peuvent s'exécuter en parallèle.

L'écriture de 1 000 documents prend environ 1,5 seconde avec cette approche, le débit est donc d'environ 667 documents écrits par seconde .

La différence n'est pas aussi grande qu'entre les deux premières approches, mais elle est toujours 1,8 fois plus rapide que les écritures par lots.


Quelques notes:

  • Vous pouvez trouver le code complet de ce test sur Github .
  • Bien que le test ait été effectué avec Node.js, vous obtiendrez probablement des résultats similaires sur toutes les plateformes prises en charge par le SDK Admin.
  • Cependant, n'effectuez pas d'insertions en masse à l'aide des kits de développement logiciel client, car les résultats peuvent être très différents et beaucoup moins prévisibles.
  • Comme d'habitude, les performances réelles dépendent de votre machine, de la bande passante et de la latence de votre connexion Internet et de nombreux autres facteurs. Sur la base de ceux-ci, vous pouvez également voir des différences dans les différences, bien que je m'attende à ce que l'ordre reste le même.
  • Si vous avez des valeurs aberrantes dans vos propres tests ou si vous trouvez des résultats complètement différents, laissez un commentaire ci-dessous.
  • Les écritures de lots sont atomiques. Donc, si vous avez des dépendances entre les documents et que tous les documents doivent être écrits, ou qu'aucun d'entre eux ne doit être écrit, vous devez utiliser une écriture par lots.
Frank van Puffelen
la source
1
C'est super intéressant, merci d'avoir fait le travail! OOC, avez-vous testé l'exécution des écritures par lots en parallèle? De toute évidence, dans ce cas, vous devez être encore plus sûr d'éviter tout document dans les deux lots.
robsiemb
1
J'étais sur le point de tester des écritures par lots parallèles, mais je manquais de quota (c'est un projet gratuit et j'étais trop paresseux pour mettre à niveau). Aujourd'hui est un autre jour, donc je pourrais essayer et mettre à jour ma réponse si elle est importante.
Frank van Puffelen
2
@robsiemb Je viens aussi de tester avec des écritures par lots parallèles. Les performances sont très similaires aux écritures parallèles individuelles, donc je dirais qu'elles sont à égalité en premier dans mes tests. Je m'attends à ce que les écritures par lots puissent se détériorer plus rapidement en raison de la nature de leur traitement en back-end. Combiné avec le code beaucoup plus complexe, je recommanderais toujours de les utiliser uniquement pour leur atomicité et non pour l'avantage de performance perçu mais inexistant.
Frank van Puffelen
Les écritures parallélisées @FrankvanPuffelen seront également plus rapides si je "définit" des documents au lieu d '"ajouter" des documents? Je veux dire, db.collection ('cities'). Doc ('LA'). Set (data) au lieu de db.collection ('cities'). Add (data)
alek6dj
L'appel add()ne fait rien de plus que de générer un ID unique (purement côté client), suivi d'une set()opération. Les résultats devraient donc être les mêmes. Si ce n'est pas ce que vous observez, postez une nouvelle question avec l'étui minimal qui reproduit ce que vous avez essayé.
Frank van Puffelen