Wiele aplikacji wyświetla te same treści wszystkim użytkownikom podczas pierwszego wczytania strony. Na przykład witryna z wiadomościami może wyświetlać najnowsze artykuły, a witryna e-commerce – najlepiej sprzedające się produkty.
Jeśli te treści są wyświetlane z Cloud Firestore, każdy użytkownik będzie wysyłać nowe zapytanie o te same wyniki podczas wczytywania aplikacji. Ponieważ wyniki te nie są buforowane między użytkownikami, aplikacja działa wolniej i jest droższa niż powinna.
Rozwiązanie: pakiety
Cloud Firestore pakiety umożliwiają tworzenie pakietów danych z typowych wyników zapytań w backendzie za pomocą pakietu Firebase Admin SDK oraz udostępnianie tych wstępnie obliczonych obiektów BLOB buforowanych w CDN. Dzięki temu użytkownicy mogą znacznie szybciej wczytywać stronę po raz pierwszy, a koszty zapytań Cloud Firestore są niższe.
W tym przewodniku użyjemy Cloud Functions do generowania pakietów i Firebase Hosting do dynamicznego buforowania i wyświetlania treści pakietów. Więcej informacji o pakietach znajdziesz w przewodnikach.
Najpierw utwórz prostą publiczną funkcję HTTP, która będzie wysyłać zapytanie o 50 najnowszych „relacji” i udostępniać 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 tak, aby wyświetlał i buforował tę funkcję w Cloud Functions, modyfikując plik firebase.json. Dzięki tej konfiguracji Firebase Hosting CDN będzie wyświetlać zawartość pakietu zgodnie z ustawieniami pamięci podręcznej określonymi przez funkcję w Cloud Functions. Gdy pamięć podręczna wygaśnie, odświeży zawartość, ponownie wywołując funkcję.
firebase.json
{
"hosting": {
// ...
"rewrites": [{
"source": "/createBundle",
"function": "createBundle"
}]
},
// ...
}
Na koniec w aplikacji internetowej pobierz zawartość pakietu z CDN i wczytaj 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
Załóżmy, że witryna z wiadomościami ma 100 tys. użytkowników dziennie, a każdy z nich podczas pierwszego wczytania strony wczytuje te same 50 najpopularniejszych artykułów. Bez buforowania spowoduje to 50 x 100 tys. = 5 mln odczytów dokumentów dziennie z Cloud Firestore.
Załóżmy teraz, że witryna zastosuje opisaną powyżej technikę i będzie buforować te 50 wyników przez maksymalnie 5 minut. Dzięki temu zamiast wczytywać wyniki zapytania dla każdego użytkownika, wyniki będą wczytywane dokładnie 12 razy na godzinę. Niezależnie od tego, ilu użytkowników odwiedzi witrynę, liczba zapytań do Cloud Firestore pozostanie taka sama. Zamiast 5 mln odczytów dokumentów ta strona będzie używać 12 x 24 x 50 = 14 400 odczytów dokumentów dziennie. Niewielkie dodatkowe koszty Hostingu Firebase i Cloud Functions są łatwo kompensowane przez Cloud Firestore oszczędności.
Oszczędności przynoszą korzyści deweloperowi, ale największym beneficjentem jest użytkownik. Wczytanie tych 50 dokumentów z CDN Hostingu Firebase zamiast bezpośrednio z Cloud Firestore może łatwo skrócić czas wczytywania treści strony o 100–200 ms lub więcej. Badania wielokrotnie wykazały, że szybkie strony oznaczają zadowolonych użytkowników.