Jaki jest najszybszy sposób na napisanie dużej ilości dokumentów do Firestore?

Odpowiedzi:

26

TL; DR: Najszybszym sposobem na utworzenie daty zbiorczej w Firestore jest wykonanie równoległych pojedynczych operacji zapisu.

Napisanie 1000 dokumentów do Firestore wymaga:

  1. ~105.4s podczas korzystania z sekwencyjnych pojedynczych operacji zapisu
  2. ~ 2.8s przy użyciu (2) operacji zapisu wsadowego
  3. ~ 1.5s podczas korzystania z równoległych pojedynczych operacji zapisu

Istnieją trzy typowe sposoby wykonywania dużej liczby operacji zapisu w Firestore.

  1. Wykonaj każdą operację zapisu w kolejności.
  2. Korzystanie z operacji zapisu partiami.
  3. Wykonywanie pojedynczych operacji zapisu równolegle.

Zbadamy kolejno każdą z nich, korzystając z szeregu losowych danych dokumentów.


Poszczególne sekwencyjne operacje zapisu

To jest najprostsze możliwe rozwiązanie:

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

Piszemy każdy dokument po kolei, dopóki nie napisamy każdego dokumentu. I czekamy na zakończenie każdej operacji zapisu, zanim zaczniemy następną.

Przy takim podejściu pisanie 1000 dokumentów zajmuje około 105 sekund, więc przepustowość wynosi około 10 zapisów dokumentów na sekundę .


Korzystanie z operacji zapisu partiami

To jest najbardziej złożone rozwiązanie.

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;
    }
  }
}

Możesz zobaczyć, że tworzymy BatchedWriteobiekt, dzwoniąc batch(), wypełniając go do maksymalnej pojemności 500 dokumentów, a następnie zapisując go w Firestore. Każdemu dokumentowi nadajemy wygenerowaną nazwę, która prawdopodobnie będzie unikatowa (wystarczająca do tego testu).

Przy takim podejściu napisanie 1000 dokumentów zajmuje około 2,8 sekundy, więc przepustowość wynosi około 357 zapisów dokumentów na sekundę .

To jest trochę szybsze niż w przypadku sekwencyjnych indywidualnych zapisów. W rzeczywistości: wielu programistów stosuje to podejście, ponieważ zakłada, że ​​jest ono najszybsze, ale jak pokazały już powyższe wyniki, nie jest to prawdą. A kod jest zdecydowanie najbardziej złożony, ze względu na ograniczenie wielkości partii.


Równoległe pojedyncze operacje zapisu

Dokumentacja Firestore mówi o wydajności dodawania dużej ilości danych :

Do masowego wprowadzania danych użyj biblioteki klienta serwera z równoległymi zapisami pojedynczymi. Zapisy partiami działają lepiej niż zapisy serializowane, ale nie lepiej niż zapisy równoległe.

Możemy to przetestować za pomocą tego kodu:

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

Ten kod uruchamia addoperacje tak szybko, jak to możliwe, a następnie używa Promise.all()do czekania, aż wszystkie zostaną zakończone. Przy takim podejściu operacje mogą przebiegać równolegle.

Przy takim podejściu napisanie 1000 dokumentów zajmuje około 1,5 sekundy, więc przepustowość wynosi około 667 zapisów dokumentów na sekundę .

Różnica nie jest prawie tak duża, jak między dwoma pierwszymi podejściami, ale wciąż jest ponad 1,8 razy szybsza niż zapisy partiami.


Kilka uwag:

  • Pełny kod tego testu można znaleźć na Github .
  • Chociaż test został przeprowadzony w Node.js, prawdopodobnie uzyskasz podobne wyniki na wszystkich platformach obsługiwanych przez Admin SDK.
  • Nie wykonuj jednak wstawień zbiorczych za pomocą zestawów SDK klienta, ponieważ wyniki mogą być bardzo różne i znacznie mniej przewidywalne.
  • Jak zwykle rzeczywista wydajność zależy od komputera, przepustowości i opóźnienia połączenia internetowego oraz wielu innych czynników. Na podstawie tych możesz również zobaczyć różnice w różnicach, chociaż oczekuję, że kolejność pozostanie taka sama.
  • Jeśli masz jakieś wartości odstające we własnych testach lub znajdziesz zupełnie inne wyniki, zostaw komentarz poniżej.
  • Partie zapisują są atomowe. Więc jeśli masz zależności między dokumentami i wszystkie dokumenty muszą być napisane lub żaden z nich nie musi być napisany, powinieneś użyć zapisu partiami.
Frank van Puffelen
źródło
1
To bardzo interesujące, dziękuję za wykonanie pracy! OOC, czy testowałeś równoległe uruchamianie zapisanych partii? Oczywiście w takim przypadku trzeba być jeszcze bardziej pewnym, aby uniknąć umieszczenia dokumentu w obu partiach.
robsiemb
1
Już miałem przetestować równoległe partie zapisu, ale zabrakło mi miejsca (jest to darmowy projekt i byłem zbyt leniwy, aby go zaktualizować). Dzisiaj jest kolejny dzień, więc mogę spróbować i zaktualizować swoją odpowiedź, jeśli jest znacząca.
Frank van Puffelen,
2
@robsiemb Właśnie przetestowałem z równoległymi partiami zapisu. Wydajność jest bardzo podobna do poszczególnych równoległych zapisów, więc powiedziałbym, że są one związane na pierwszym miejscu w moich testach. Oczekuję, że partie zapisów mogą się pogarszać szybciej ze względu na naturę, jaką są przetwarzane na zapleczu. W połączeniu ze znacznie bardziej złożonym kodem nadal zalecałbym używanie ich ze względu na ich atomowość, a nie na postrzeganą, ale nieistniejącą przewagę wydajności.
Frank van Puffelen,
@FrankvanPuffelen zapisy równoległe będą szybsze także wtedy, gdy „ustawię” dokumenty zamiast „dodam” dokumenty? Mam na myśli, db.collection ('cities'). Doc ('LA'). Set (data) zamiast db.collection ('cities'). Add (data)
alek6dj
Wywołanie add()nie robi nic więcej niż generowanie unikalnego identyfikatora (wyłącznie po stronie klienta), po którym następuje set()operacja. Wyniki powinny być takie same. Jeśli nie tego obserwujesz, opublikuj nowe pytanie z minimalną kopią, która odtwarza to, czego próbowałeś.
Frank van Puffelen