Pubblica contenuti Firestore in bundle da una CDN

Molte applicazioni pubblicano gli stessi contenuti per tutti gli utenti al primo caricamento della pagina. Per Ad esempio, un sito di notizie può mostrare le ultime notizie o un sito di e-commerce può mostrare gli articoli più venduti.

Se questi contenuti vengono pubblicati da Cloud Firestore, ogni utente emetterà una nuova per ottenere gli stessi risultati quando caricano l'applicazione. Poiché questi risultati non vengono memorizzati nella cache tra gli utenti, l'applicazione è più lenta e più costosa del necessario.

Soluzione: cofanetti

I bundle Cloud Firestore consentono di combinare pacchetti di dati dalla query comune sul backend utilizzando l'SDK Firebase Admin e pubblicare questi BLOB precalcolati memorizzati nella cache su una CDN. In questo modo, gli utenti avranno un'esperienza di primo caricamento molto più rapida e i costi delle query Cloud Firestore diminuiranno.

In questa guida utilizzeremo Cloud Functions per generare bundle e Firebase Hosting per memorizzare nella cache e pubblicare dinamicamente i contenuti dei bundle. Altro informazioni sui pacchetti sono disponibili nelle guide.

Innanzitutto crea una semplice funzione HTTP pubblica per interrogare le ultime 50 storie e per pubblicare il risultato in 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);
});
      
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()));
  }
}
      

Poi configura Firebase Hosting per pubblicare e memorizzare nella cache questa funzione Cloud Functions Modifica di firebase.json in corso... Con questa configurazione, la CDN Firebase Hosting pubblica i contenuti del bundle in base alle impostazioni della cache impostate dalla funzione Cloud. Quando la cache scade, i contenuti vengono aggiornati attivando nuovamente la funzione.

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

Infine, nell'applicazione web, recupera i contenuti pacchettizzati dalla CDN e caricali nell'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
  // ...
}

Risparmi stimati

Prendiamo ad esempio un sito web di notizie che riceve 100.000 utenti al giorno e ogni utente carica le stesse 50 notizie principali al caricamento iniziale. Senza memorizzazione nella cache, si otterrebbero 50 x 100.000 = 5.000.000 letture di documenti al giorno da Cloud Firestore.

Ora supponiamo che il sito adotti la tecnica sopra descritta e memorizzi nella cache questi 50 risultati per un massimo di 5 minuti. Quindi, invece di caricare i risultati della query per ogni utente, i risultati vengono caricati esattamente 12 volte all'ora. Indipendentemente dal numero di utenti che arrivano sul sito, il numero di query su Cloud Firestore rimane invariato. 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 dai risparmi sui costi di Cloud Firestore.

Sebbene lo sviluppatore tragga vantaggio dai risparmi sui costi, il maggiore beneficiario è l'utente. Caricare questi 50 documenti dalla CDN di Firebase Hosting anziché direttamente da Cloud Firestore può facilmente eliminare 100-200 ms o più dal il tempo di caricamento dei contenuti della pagina. Studi hanno dimostrato ripetutamente che le pagine veloci significa che gli utenti sono più soddisfatti.