Catch up on highlights from Firebase at Google I/O 2023. Learn more

Diffusez du contenu Firestore groupé à partir d'un CDN

De nombreuses applications proposent le même contenu à tous les utilisateurs lors du chargement de la première page. Par exemple, un site d'actualités peut afficher les dernières histoires, ou un site de commerce électronique peut afficher les articles les plus vendus.

Si ce contenu est servi à partir de Cloud Firestore, chaque utilisateur émettra une nouvelle requête pour les mêmes résultats lorsqu'il chargera l'application. Étant donné que ces résultats ne sont pas mis en cache entre les utilisateurs, l'application est plus lente et plus coûteuse qu'elle ne devrait l'être.

Solution : forfaits

Les bundles Cloud Firestore vous permettent d'assembler des bundles de données à partir de résultats de requêtes courants sur le backend à l'aide du SDK Firebase Admin, et de servir ces blobs précalculés mis en cache sur un CDN. Cela offre à vos utilisateurs une expérience de premier chargement beaucoup plus rapide et réduit vos coûts de requête Cloud Firestore.

Dans ce guide, nous utiliserons Cloud Functions pour générer des bundles et Firebase Hosting pour mettre en cache et diffuser dynamiquement le contenu du bundle. Plus d'informations sur les offres groupées sont disponibles dans les guides .

Créez d'abord une fonction HTTP publique simple pour interroger les 50 dernières "histoires" et servir le résultat sous forme de bundle.

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()));
  }
}
      

Configurez ensuite Firebase Hosting pour servir et mettre en cache cette fonction Cloud en modifiant firebase.json . Avec cette configuration, le CDN Firebase Hosting servira le contenu du bundle en fonction des paramètres de cache définis par la fonction Cloud. Lorsque le cache expire, il actualise le contenu en déclenchant à nouveau la fonction.

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

Enfin, dans votre application Web, récupérez le contenu groupé du CDN et chargez-le dans le 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
  // ...
}

Économies estimées

Considérez un site Web d'actualités qui reçoit 100 000 utilisateurs par jour et chaque utilisateur charge les mêmes 50 meilleures histoires lors du chargement initial. Sans aucune mise en cache, cela se traduirait par 50 x 100 000 = 5 000 000 lectures de documents par jour à partir de Cloud Firestore.

Supposons maintenant que le site adopte la technique ci-dessus et mette en cache ces 50 résultats pendant 5 minutes maximum. Ainsi, au lieu de charger les résultats de la requête pour chaque utilisateur, les résultats sont chargés exactement 12 fois par heure. Quel que soit le nombre d'utilisateurs qui arrivent sur le site, le nombre de requêtes adressées à Cloud Firestore reste le même. Au lieu de 5 000 000 lectures de documents, cette page utiliserait 12 x 24 x 50 = 14 400 lectures de documents par jour. Les petits coûts supplémentaires pour l'hébergement Firebase et les fonctions cloud sont facilement compensés par les économies de coûts de Cloud Firestore.

Alors que le développeur bénéficie des économies de coûts, le plus grand bénéficiaire est l'utilisateur. Le chargement de ces 50 documents depuis le CDN d'hébergement Firebase plutôt que depuis Cloud Firestore directement peut facilement réduire de 100 à 200 ms ou plus le temps de chargement du contenu de la page. Des études ont montré à plusieurs reprises que des pages rapides signifient des utilisateurs plus heureux.