Wiele aplikacji serwuje tę samą treść wszystkim użytkownikom przy pierwszym wczytaniu strony. Na przykład witryna z wiadomościami może wyświetlać najnowsze artykuły, a sklep internetowy – najlepiej sprzedające się produkty.
Jeśli te treści są wyświetlane za pomocą adresu Cloud Firestore, każdy użytkownik podczas wczytywania aplikacji wyśle nowe zapytanie dla tych samych wyników. Ponieważ te wyniki nie są przechowywane w pamięci podręcznej między użytkownikami, aplikacja działa wolniej i jest droższa, niż powinna.
Rozwiązanie: pakiety
Pakiety Cloud Firestore umożliwiają tworzenie pakietów danych na podstawie typowych wyników zapytań w backendzie za pomocą pakietu Firebase Admin SDK i udostępnianie tych wstępnie obliczonych obiektów blob w pamięci podręcznej sieci CDN. Dzięki temu użytkownicy mogą znacznie szybciej wczytać treści, a Ty możesz obniżyć koszty zapytań Cloud Firestore.
W tym przewodniku używamy interfejsu Cloud Functions do generowania pakietów, a Firebase Hosting do dynamicznego buforowania i wyświetlania pakietów treści. Więcej informacji o pakietach znajdziesz w przewodnikach.
Najpierw utwórz prostą publiczną funkcję HTTP, aby zapytać o 50 najnowszych „stories” i zwrócić wynik jako pakiet.
Node.js
exports.createBundle = functions.https.onRequest(async (request, response) => { // Query the 50 latest stories const latestStories = await db.collection('stories') .orderBy('timestamp', 'desc') .limit(50) .get(); // Build the bundle from the query results const bundleBuffer = db.bundle('latest-stories') .add('latest-stories-query', latestStories) .build(); // Cache the response for up to 5 minutes; // see https://firebase.google.com/docs/hosting/manage-cache response.set('Cache-Control', 'public, max-age=300, s-maxage=600'); response.end(bundleBuffer); });
Java
package com.example; import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.firestore.Firestore; import com.google.cloud.firestore.FirestoreBundle; import com.google.cloud.firestore.Query.Direction; import com.google.cloud.firestore.QuerySnapshot; import com.google.cloud.functions.HttpFunction; import com.google.cloud.functions.HttpRequest; import com.google.cloud.functions.HttpResponse; import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseOptions; import com.google.firebase.cloud.FirestoreClient; import java.io.BufferedWriter; import java.io.IOException; public class ExampleFunction implements HttpFunction { public static FirebaseApp initializeFirebase() throws IOException { if (FirebaseApp.getApps().isEmpty()) { FirebaseOptions options = FirebaseOptions.builder() .setCredentials(GoogleCredentials.getApplicationDefault()) .setProjectId("YOUR-PROJECT-ID") .build(); FirebaseApp.initializeApp(options); } return FirebaseApp.getInstance(); } @Override public void service(HttpRequest request, HttpResponse response) throws Exception { // Get a Firestore instance FirebaseApp app = initializeFirebase(); Firestore db = FirestoreClient.getFirestore(app); // Query the 50 latest stories QuerySnapshot latestStories = db.collection("stories") .orderBy("timestamp", Direction.DESCENDING) .limit(50) .get() .get(); // Build the bundle from the query results FirestoreBundle bundle = db.bundleBuilder("latest-stores") .add("latest-stories-query", latestStories) .build(); // Cache the response for up to 5 minutes // see https://firebase.google.com/docs/hosting/manage-cache response.appendHeader("Cache-Control", "public, max-age=300, s-maxage=600"); // Write the bundle to the HTTP response BufferedWriter writer = response.getWriter(); writer.write(new String(bundle.toByteBuffer().array())); } }
Następnie skonfiguruj Hosting Firebase, aby serwować i przechowywać w pamięci podręcznej tę funkcję Cloud Functions, modyfikując plik firebase.json
. W tej konfiguracji sieć Firebase Hosting CDN będzie wyświetlać zawartość pakietu zgodnie z ustawieniami pamięci podręcznej określonymi przez funkcję Cloud. Gdy pamięć podręczna wygaśnie, funkcja ponownie odświeży treści.
firebase.json
{
"hosting": {
// ...
"rewrites": [{
"source": "/createBundle",
"function": "createBundle"
}]
},
// ...
}
W swojej aplikacji internetowej pobierz zawartość z pakietu z CDN i załaduj ją do pakietu SDK Firestore.
// If you are using module bundlers.
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/firestore/bundle" // This line enables bundle loading as a side effect.
async function fetchFromBundle() {
// Fetch the bundle from Firebase Hosting, if the CDN cache is hit the 'X-Cache'
// response header will be set to 'HIT'
const resp = await fetch('/createBundle');
// Load the bundle contents into the Firestore SDK
await db.loadBundle(resp.body);
// Query the results from the cache
// Note: omitting "source: cache" will query the Firestore backend.
const query = await db.namedQuery('latest-stories-query');
const storiesSnap = await query.get({ source: 'cache' });
// Use the results
// ...
}
Szacowane oszczędności
Weźmy pod uwagę witrynę z wiadomościami, która ma 100 tys. użytkowników dziennie, a każdy z nich wczytuje te same 50 najpopularniejszych artykułów podczas pierwszego załadowania. Bez buforowania oznaczałoby to 50 x 100 000 = 5 000 000 odczytów dokumentów Cloud Firestore dziennie.
Załóżmy teraz, że witryna stosuje opisaną wyżej technikę i przechowuje w pamięci podręcznej te 50 wyników przez maksymalnie 5 minut. Zamiast wczytywać wyniki zapytań dla każdego użytkownika, wyniki są wczytywane dokładnie 12 razy na godzinę. Niezależnie od tego, ilu użytkowników odwiedza witrynę, liczba zapytań do Cloud Firestore pozostaje taka sama. Zamiast 5 000 000 odczytów dokumentów ta strona wykorzysta 12 x 24 x 50 = 14 400 odczytów dokumentów dziennie. Niewielkie dodatkowe koszty związane z Cloud Functions i Hostingiem Firebase są łatwo rekompensowane przez oszczędności Cloud Firestore.
Deweloper korzysta na oszczędnościach, ale największym beneficjentem jest użytkownik. Wczytywanie tych 50 dokumentów z CDN Hostingu Firebase, a nie bezpośrednio z Cloud Firestore, może skrócić czas wczytywania treści strony o 100–200 ms lub więcej. Badania pokazują, że szybkie strony są zadowolone z użytkowników.