Stellen Sie gebündelte Firestore-Inhalte von einem CDN bereit

Viele Anwendungen stellen beim ersten Laden der Seite allen Benutzern denselben Inhalt bereit. Beispielsweise kann eine Nachrichtenseite die neuesten Geschichten zeigen oder eine E-Commerce-Seite die meistverkauften Artikel.

Wenn dieser Inhalt von Cloud Firestore bereitgestellt wird, stellt jeder Benutzer beim Laden der Anwendung eine neue Abfrage mit denselben Ergebnissen. Da diese Ergebnisse nicht zwischen Benutzern zwischengespeichert werden, ist die Anwendung langsamer und teurer als nötig.

Lösung: Bundles

Mit Cloud Firestore-Bundles können Sie mithilfe des Firebase Admin SDK Datenpakete aus allgemeinen Abfrageergebnissen im Back-End zusammenstellen und diese vorberechneten Blobs im Cache auf einem CDN bereitstellen. Dies ermöglicht Ihren Benutzern ein viel schnelleres Erstladeerlebnis und reduziert Ihre Cloud Firestore-Abfragekosten.

In diesem Leitfaden verwenden wir Cloud Functions zum Generieren von Bundles und Firebase Hosting zum dynamischen Zwischenspeichern und Bereitstellen von Bundle-Inhalten. Weitere Informationen zu Bundles finden Sie in den Leitfäden .

Erstellen Sie zunächst eine einfache öffentliche HTTP-Funktion, um die 50 neuesten „Stories“ abzufragen und das Ergebnis als Paket 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 zwischenzuspeichern, indem Sie firebase.json ändern. Mit dieser Konfiguration stellt das Firebase Hosting CDN den Bundle-Inhalt gemäß den von der Cloud-Funktion festgelegten Cache-Einstellungen bereit. Wenn der Cache abläuft, wird der Inhalt durch erneutes Auslösen der Funktion aktualisiert.

firebase.json
{
  "hosting": {
    // ...
    "rewrites": [{
      "source": "/createBundle",
      "function": "createBundle"
    }]
  },
  // ...
}

Rufen Sie abschließend in Ihrer Webanwendung den gebündelten Inhalt vom CDN ab und laden Sie ihn 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 Einsparungen

Stellen Sie sich eine Nachrichten-Website vor, die 100.000 Benutzer pro Tag hat und jeder Benutzer beim ersten Laden die gleichen 50 Top-Storys lädt. Ohne Caching würde dies zu 50 x 100.000 = 5.000.000 Dokumentlesevorgängen pro Tag aus Cloud Firestore führen.

Gehen Sie nun davon aus, dass die Website die oben beschriebene Technik anwendet und diese 50 Ergebnisse bis zu 5 Minuten lang zwischenspeichert. Anstatt also die Abfrageergebnisse für jeden Benutzer zu laden, werden die Ergebnisse genau 12 Mal pro Stunde geladen. Unabhängig davon, wie viele Benutzer auf der Website eintreffen, bleibt die Anzahl der Anfragen an Cloud Firestore gleich. Anstelle von 5.000.000 Dokumentlesevorgängen würde diese Seite 12 x 24 x 50 = 14.400 Dokumentlesevorgänge pro Tag erfordern. Die geringen Zusatzkosten für Firebase Hosting und Cloud Functions werden durch die Kosteneinsparungen von Cloud Firestore problemlos ausgeglichen.

Während der Entwickler von den Kosteneinsparungen profitiert, ist der Benutzer der größte Nutznießer. Durch das Laden dieser 50 Dokumente aus dem Firebase Hosting CDN statt direkt aus dem Cloud Firestore kann die Ladezeit für den Inhalt der Seite leicht um 100–200 ms oder mehr verkürzt werden. Studien haben wiederholt gezeigt, dass schnelle Seiten zufriedenere Benutzer bedeuten.