Exiba conteúdo agrupado do Firestore de uma CDN

Muitos aplicativos fornecem o mesmo conteúdo a todos os usuários no carregamento da primeira página. Por exemplo, um site de notícias pode mostrar as últimas notícias ou um site de comércio eletrônico pode mostrar os itens mais vendidos.

Se esse conteúdo for veiculado pelo Cloud Firestore, cada usuário emitirá uma nova consulta para os mesmos resultados ao carregar o aplicativo. Como esses resultados não são armazenados em cache entre os usuários, o aplicativo é mais lento e mais caro do que o necessário.

Solução: Pacotes

Os pacotes do Cloud Firestore permitem que você monte pacotes de dados a partir de resultados de consultas comuns no back-end usando o SDK Admin do Firebase e forneça esses blobs pré-computados armazenados em cache em um CDN. Isso proporciona aos usuários uma experiência de primeiro carregamento muito mais rápida e reduz os custos de consulta do Cloud Firestore.

Neste guia, usaremos Cloud Functions para gerar pacotes e Firebase Hosting para armazenar em cache e servir dinamicamente o conteúdo do pacote. Mais informações sobre pacotes estão disponíveis nos guias .

Primeiro, crie uma função HTTP pública simples para consultar as 50 "histórias" mais recentes e apresentar o resultado como um pacote.

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

Em seguida, configure o Firebase Hosting para servir e armazenar em cache esta função do Cloud, modificando firebase.json . Com esta configuração, o Firebase Hosting CDN veiculará o conteúdo do pacote de acordo com as configurações de cache definidas pela Cloud Function. Quando o cache expirar, ele atualizará o conteúdo acionando a função novamente.

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

Por fim, em seu aplicativo Web, busque o conteúdo agrupado do CDN e carregue-o no SDK do 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
  // ...
}

Economia estimada

Considere um site de notícias que recebe 100.000 usuários por dia e cada usuário carrega as mesmas 50 notícias principais no carregamento inicial. Sem qualquer armazenamento em cache, isso resultaria em 50 x 100.000 = 5.000.000 leituras de documentos por dia no Cloud Firestore.

Agora suponha que o site adote a técnica acima e armazene em cache esses 50 resultados por até 5 minutos. Portanto, em vez de carregar os resultados da consulta para cada usuário, os resultados são carregados exatamente 12 vezes por hora. Não importa quantos usuários cheguem ao site, o número de consultas ao Cloud Firestore permanece o mesmo. Em vez de 5.000.000 leituras de documentos, esta página usaria 12 x 24 x 50 = 14.400 leituras de documentos por dia. Os pequenos custos adicionais para Firebase Hosting e Cloud Functions são facilmente compensados ​​pela economia de custos do Cloud Firestore.

Embora o desenvolvedor se beneficie da economia de custos, o maior beneficiário é o usuário. Carregar esses 50 documentos do Firebase Hosting CDN, em vez de diretamente do Cloud Firestore, pode facilmente reduzir de 100 a 200 ms ou mais do tempo de carregamento do conteúdo da página. Estudos têm mostrado repetidamente que páginas rápidas significam usuários mais felizes.