Únete de manera presencial y en línea a Firebase Summit el 18 de octubre de 2022. Descubre cómo Firebase puede ayudarte a acelerar el desarrollo de apps, a lanzar la tuya con confianza y a escalar con facilidad. Regístrate ahora

Servir contenido de Firestore empaquetado desde una CDN

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

Muchas aplicaciones ofrecen el mismo contenido a todos los usuarios en la carga de la primera página. Por ejemplo, un sitio de noticias puede mostrar las últimas historias o un sitio de comercio electrónico puede mostrar los artículos más vendidos.

Si este contenido se sirve desde Cloud Firestore, cada usuario emitirá una nueva consulta para obtener los mismos resultados cuando cargue la aplicación. Debido a que estos resultados no se almacenan en caché entre los usuarios, la aplicación es más lenta y costosa de lo que debería ser.

Solución: Paquetes

Los paquetes de Cloud Firestore le permiten ensamblar paquetes de datos a partir de resultados de consultas comunes en el backend mediante el SDK de administración de Firebase y servir estos blobs precalculados almacenados en caché en una CDN. Esto brinda a sus usuarios una primera experiencia de carga mucho más rápida y reduce los costos de consulta de Cloud Firestore.

En esta guía, usaremos Cloud Functions para generar paquetes y Firebase Hosting para almacenar en caché y brindar contenido de paquetes de forma dinámica. Más información sobre paquetes está disponible en las guías .

Primero cree una función HTTP pública simple para consultar las 50 "historias" más recientes y presente el resultado como un paquete.

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

A continuación, configure Firebase Hosting para servir y almacenar en caché esta Cloud Function modificando firebase.json . Con esta configuración, Firebase Hosting CDN servirá el contenido del paquete de acuerdo con la configuración de caché establecida por Cloud Function. Cuando el caché caduque, actualizará el contenido activando la función nuevamente.

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

Finalmente, en su aplicación web, busque el contenido incluido en la CDN y cárguelo en el SDK de 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
  // ...
}

Ahorros Estimados

Considere un sitio web de noticias que recibe 100.000 usuarios por día y cada usuario carga las mismas 50 historias principales en la carga inicial. Sin almacenamiento en caché, esto daría como resultado 50 x 100 000 = 5 000 000 lecturas de documentos por día desde Cloud Firestore.

Ahora suponga que el sitio adopta la técnica anterior y almacena en caché esos 50 resultados por hasta 5 minutos. Entonces, en lugar de cargar los resultados de la consulta para cada usuario, los resultados se cargan exactamente 12 veces por hora. No importa cuántos usuarios lleguen al sitio, la cantidad de consultas a Cloud Firestore permanece igual. En lugar de 5 000 000 de lecturas de documentos, esta página usaría 12 x 24 x 50 = 14 400 lecturas de documentos por día. Los pequeños costos adicionales de Firebase Hosting y Cloud Functions se compensan fácilmente con los ahorros de costos de Cloud Firestore.

Si bien el desarrollador se beneficia del ahorro de costos, el mayor beneficiario es el usuario. Cargar estos 50 documentos desde Firebase Hosting CDN en lugar de hacerlo directamente desde Cloud Firestore puede reducir fácilmente entre 100 y 200 ms o más del tiempo de carga de contenido de la página. Los estudios han demostrado repetidamente que las páginas rápidas significan usuarios más felices.