Molte applicazioni mostrano gli stessi contenuti a tutti gli utenti al primo caricamento della pagina. Ad esempio, un sito di notizie potrebbe mostrare le ultime notizie o un sito di e-commerce potrebbe mostrare gli articoli più venduti.
Se questi contenuti vengono pubblicati da Cloud Firestore, ogni utente emetterà 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 del necessario.
Soluzione: pacchetti
I bundle Cloud Firestore ti consentono di assemblare bundle di dati dai risultati di query comuni sul backend utilizzando l'SDK Firebase Admin e di pubblicare questi blob precalcolati memorizzati nella cache di una CDN. In questo modo, i tuoi utenti avranno un'esperienza di caricamento iniziale molto più rapida e i costi delle query Cloud Firestore saranno ridotti.
In questa guida utilizzeremo Cloud Functions per generare bundle e Firebase Hosting per memorizzare nella cache e pubblicare dinamicamente i contenuti dei bundle. Per saperne di più sui bundle, consulta le guide.
Per prima cosa, crea una semplice funzione HTTP pubblica per eseguire query sulle 50 "storie" più recenti e mostrare il risultato come 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())); } }
Successivamente, configura Firebase Hosting per pubblicare e memorizzare nella cache questa funzione Cloud modificando firebase.json
. Con questa configurazione, la CDN Firebase Hosting
pubblicherà i contenuti del bundle in base alle impostazioni della cache impostate dalla
Cloud Function. Quando la cache scade, il contenuto viene aggiornato attivando
di nuovo la funzione.
firebase.json
{
"hosting": {
// ...
"rewrites": [{
"source": "/createBundle",
"function": "createBundle"
}]
},
// ...
}
Infine, nella tua applicazione web, recupera i contenuti in bundle 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
Prendi in considerazione 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 alcuna memorizzazione nella cache, ciò comporterebbe 50 x 100.000 = 5.000.000 di 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. Pertanto, anziché 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 a Cloud Firestore rimane invariato. Anziché 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.
Sebbene lo sviluppatore tragga vantaggio dal risparmio sui costi, il principale beneficiario è l'utente. Il caricamento di questi 50 documenti dalla CDN di Firebase Hosting anziché da Cloud Firestore direttamente può ridurre facilmente di 100-200 ms o più il tempo di caricamento dei contenuti della pagina. Diversi studi hanno dimostrato che le pagine veloci rendono gli utenti più felici.