Offri contenuti Firestore in bundle da una CDN

Molte applicazioni offrono lo stesso contenuto a tutti gli utenti al caricamento della prima pagina. Ad esempio, un sito di notizie può mostrare le ultime storie, oppure un sito di e-commerce può mostrare gli articoli più venduti.

Se questo contenuto viene fornito da Cloud Firestore, ogni utente invierà una nuova query per gli stessi risultati quando carica l'applicazione. Poiché questi risultati non vengono memorizzati nella cache tra gli utenti, l'applicazione è più lenta e più costosa di quanto dovrebbe essere.

Soluzione: pacchetti

I bundle Cloud Firestore ti consentono di assemblare bundle di dati dai risultati di query comuni sul back-end utilizzando Firebase Admin SDK e di servire questi BLOB precalcolati memorizzati nella cache su una CDN. Ciò offre ai tuoi utenti un'esperienza di primo caricamento molto più rapida e riduce i costi delle query su Cloud Firestore.

In questa guida utilizzeremo Cloud Functions per generare pacchetti e Firebase Hosting per memorizzare nella cache e servire dinamicamente il contenuto del pacchetto. Maggiori informazioni sui bundle sono disponibili nelle guide .

Per prima cosa crea una semplice funzione HTTP pubblica per interrogare le 50 "storie" più recenti e fornire il risultato come un pacchetto.

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

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

Successivamente configura Firebase Hosting per servire e memorizzare nella cache questa Cloud Function modificando firebase.json . Con questa configurazione il CDN di hosting Firebase servirà il contenuto del bundle in base alle impostazioni della cache impostate dalla funzione Cloud. Quando la cache scade, aggiornerà il contenuto attivando nuovamente la funzione.

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

Infine, nella tua applicazione web, recupera il contenuto in bundle dalla CDN e caricalo nell'SDK di 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
  // ...
}

Risparmio stimato

Considera un sito Web di notizie che ottiene 100.000 utenti al giorno e ogni utente carica le stesse 50 notizie principali al caricamento iniziale. Senza alcuna memorizzazione nella cache, ciò comporterebbe 50 x 100.000 = 5.000.000 di letture di documenti al giorno da Cloud Firestore.

Supponiamo ora che il sito adotti la tecnica sopra descritta e memorizzi nella cache i 50 risultati per un massimo di 5 minuti. Pertanto, invece di caricare i risultati della query per ogni utente, i risultati vengono caricati esattamente 12 volte all'ora. Non importa quanti utenti arrivano al sito, il numero di query su Cloud Firestore rimane lo stesso. Invece di 5.000.000 di letture di documenti, questa pagina utilizzerebbe 12 x 24 x 50 = 14.400 letture di documenti al giorno. I piccoli costi aggiuntivi per Firebase Hosting e Cloud Functions sono facilmente compensati dal risparmio sui costi di Cloud Firestore.

Mentre lo sviluppatore trae vantaggio dal risparmio sui costi, il maggior beneficiario è l’utente. Il caricamento di questi 50 documenti dal CDN di hosting Firebase anziché direttamente da Cloud Firestore può facilmente ridurre di 100-200 ms o più il tempo di caricamento del contenuto della pagina. Gli studi hanno ripetutamente dimostrato che pagine veloci significano utenti più felici.