Wiele aplikacji wyświetla te same treści wszystkim użytkownikom przy pierwszym wczytaniu 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ą udostępniane z Cloud Firestore, każdy użytkownik po wczytaniu aplikacji wyśle nowe zapytanie o te same wyniki. 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 z wyników typowych zapytań w 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ć koszty zapytań Cloud Firestore.
W tym przewodniku użyjemy narzędzia Cloud Functions do generowania pakietów i Firebase Hosting do dynamicznego buforowania i wyświetlania zawartości pakietów. 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 wyświetlać 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, aby obsługiwał i buforował tę funkcję w Cloud Functions, modyfikując plik firebase.json. Przy tej konfiguracji sieć CDN Firebase Hosting 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 pakiet treści z sieci CDN i wczytaj go 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
Rozważmy witrynę z wiadomościami,którą odwiedza 100 000 użytkowników dziennie. Każdy z nich przy pierwszym wczytaniu strony ładuje te same 50 najważniejszych artykułów. Bez buforowania oznaczałoby to 50 × 100 000 = 5 000 000 odczytów dokumentów dziennie z Cloud Firestore.
Załóżmy teraz, że witryna stosuje opisaną powyżej technikę i buforuje te 50 wyników przez maksymalnie 5 minut. Zamiast wczytywać wyniki zapytania dla każdego użytkownika, wyniki są wczytywane dokładnie 12 razy na godzinę. Niezależnie od tego, ilu użytkowników odwiedzi witrynę, liczba zapytań do Cloud Firestore pozostaje taka sama. Zamiast 5 000 000 odczytów dokumentów ta strona używałaby 12 x 24 x 50 = 14 400 odczytów dokumentów dziennie. Niewielkie dodatkowe koszty Hostingu Firebase i Cloud Functions są łatwo rekompensowane przez oszczędności, które zapewnia Cloud Firestore.
Deweloperzy oszczędzają pieniądze, ale największymi beneficjentami są użytkownicy. Wczytywanie tych 50 dokumentów z sieci 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 wielokrotnie wykazały, że szybkie strony oznaczają bardziej zadowolonych użytkowników.