Viele Anwendungen stellen beim ersten Seitenaufbau allen Nutzern dieselben Inhalte zur Verfügung. So kann eine Nachrichtenseite beispielsweise die neuesten Meldungen oder eine E-Commerce-Website die Bestseller anzeigen.
Wenn diese Inhalte von Cloud Firestore bereitgestellt werden, wird für jeden Nutzer eine neue Anfrage für dieselben Ergebnisse gesendet, wenn er die Anwendung lädt. Da diese Ergebnisse nicht zwischen Nutzern zwischengespeichert werden, ist die Anwendung langsamer und teurer als nötig.
Lösung: Bundles
Mit Cloud Firestore-Bundles können Sie Daten-Bundles aus häufigen Abfrageergebnissen im Backend mit dem Firebase Admin SDK zusammenstellen und diese vorab berechneten Blobs, die in einem CDN zwischengespeichert sind, bereitstellen. So wird die erste Seite für Ihre Nutzer viel schneller geladen und die Kosten für Cloud Firestore-Anfragen sinken.
In dieser Anleitung verwenden wir Cloud Functions zum Generieren von Bundles und Firebase Hosting zum dynamischen Zwischenspeichern und Bereitstellen von Bundle-Inhalten. Weitere Informationen zu Paketen finden Sie in den Anleitungen.
Erstellen Sie zuerst eine einfache öffentliche HTTP-Funktion, um die 50 neuesten „Stories“ abzufragen und das Ergebnis als Bundle bereitzustellen.
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())); } }
Konfigurieren Sie als Nächstes Firebase Hosting, um diese Cloud-Funktion bereitzustellen und im Cache zu speichern. Dazu müssen Sie firebase.json
ändern. Bei dieser Konfiguration stellt das Firebase Hosting-CDN die Bundle-Inhalte gemäß den von der Cloud-Funktion festgelegten Cache-Einstellungen bereit. Wenn der Cache abläuft, werden die Inhalte aktualisiert, indem die Funktion noch einmal ausgelöst wird.
firebase.json
{
"hosting": {
// ...
"rewrites": [{
"source": "/createBundle",
"function": "createBundle"
}]
},
// ...
}
Rufen Sie schließlich in Ihrer Webanwendung die gebündelten Inhalte aus dem CDN ab und laden Sie sie in das Firestore SDK.
// 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
// ...
}
Geschätzte Einsparung
Nehmen wir an,eine Nachrichtenwebsite hat 100.000 Nutzer pro Tag und jeder Nutzer lädt beim ersten Aufruf dieselben 50 Top-Nachrichten. Ohne Caching würde dies zu 50 × 100.000 = 5.000.000 Dokumentlesevorgängen pro Tag aus Cloud Firestore führen.
Nehmen wir nun an, dass die Website die oben beschriebene Technik verwendet und diese 50 Ergebnisse bis zu 5 Minuten lang im Cache speichert. Anstatt die Abfrageergebnisse für jeden Nutzer zu laden, werden sie also genau 12 Mal pro Stunde geladen. Unabhängig davon, wie viele Nutzer auf die Website zugreifen, bleibt die Anzahl der Anfragen an Cloud Firestore gleich. Anstelle von 5.000.000 Dokumentlesevorgängen würden auf dieser Seite 12 × 24 × 50 = 14.400 Dokumentlesevorgänge pro Tag verwendet. Die geringen zusätzlichen Kosten für Firebase Hosting und Cloud Functions werden durch die Cloud Firestore-Kosteneinsparungen mehr als ausgeglichen.
Während der Entwickler von den Kosteneinsparungen profitiert, ist der größte Nutznießer der Nutzer. Wenn Sie diese 50 Dokumente über das Firebase Hosting-CDN anstatt direkt über Cloud Firestore laden, kann die Ladezeit für den Inhalt der Seite um 100–200 ms oder mehr verkürzt werden. Studien haben wiederholt gezeigt, dass schnelle Seiten zu zufriedeneren Nutzern führen.