Wiele aplikacji wyświetla tę samą treść 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 po wczytaniu aplikacji wyśle nowe zapytanie o te same wyniki. Ponieważ te wyniki nie są buforowane między użytkownikami, aplikacja działa wolniej i jest droższa niż powinna.
Rozwiązanie: pakiety
Cloud Firestore umożliwiają tworzenie pakietów danych z częstych wyników zapytań na backendzie za pomocą pakietu Firebase Admin SDK i udostępnianie tych wstępnie obliczonych obiektów blob zapisanych w pamięci podręcznej w sieci CDN. Dzięki temu użytkownicy mogą znacznie szybciej wczytać stronę po raz pierwszy, a Ty możesz obniżyć Cloud Firestore koszty zapytań.
W tym przewodniku użyjemy Cloud Functions do generowania pakietów i Firebase Hosting do dynamicznego buforowania i wyświetlania treści pakietu. Więcej informacji o pakietach znajdziesz w przewodnikach.
Najpierw utwórz prostą publiczną funkcję HTTP, która będzie wysyłać zapytania o 50 najnowszych „historii” i zwracać wyniki w postaci pakietu.
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 obsługiwał i buforował tę funkcję Cloud Functions, modyfikując plik firebase.json
. W tej konfiguracji Firebase HostingCDN będzie wyświetlać zawartość pakietu zgodnie z ustawieniami pamięci podręcznej skonfigurowanymi przez funkcję Cloud Function. 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 spakowaną zawartość z sieci 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órą odwiedza 100 tys. użytkowników dziennie. Każdy z nich przy pierwszym otwarciu strony wczytuje te same 50 najpopularniejszych artykułów. Bez buforowania spowodowałoby to 50 × 100 000 = 5 000 000 odczytów dokumentów dziennie z Cloud Firestore.
Załóżmy, że witryna zastosuje powyższą technikę i będzie przechowywać w pamięci podręcznej 50 wyników przez maksymalnie 5 minut. Zamiast wczytywać wyniki zapytania dla każdego użytkownika, są one 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 000 000 odczytów dokumentów ta strona wykorzystywałaby 12 x 24 x 50 = 14 400 odczytów dokumentów dziennie. Niewielkie dodatkowe koszty Hostingu Firebase i Cloud Functions są łatwo kompensowane przez oszczędności Cloud Firestore.
Deweloper korzysta z oszczędności, ale największą korzyść odnosi użytkownik. Wczytywanie tych 50 dokumentów z sieci 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ą większe zadowolenie użytkowników.