透過 CDN 提供隨附的 Firestore 內容

許多應用程式會在第一次載入網頁時,為所有使用者提供相同的內容。適用對象 舉例來說,新聞網站可能會顯示最新報導 最暢銷商品

如果這項內容是透過 Cloud Firestore 提供,則每位使用者都會發出新的 查詢相同結果。由於這些 系統不會在使用者之間快取結果、應用程式速度較慢,以及更多 代價高昂

解決方案:套裝組合

Cloud Firestore 組合可讓您組合常見查詢的資料組合 在後端透過 Firebase Admin SDK 擷取結果,並在後端 預先運算的 blob 快取。這讓使用者 並降低 Cloud Firestore 查詢費用。

在本指南中,我們將使用 Cloud Functions 產生套裝組合, Firebase 託管,可動態快取及提供套裝組合內容。更多內容 如需套裝組合的相關資訊,請參閱指南

首先建立一個簡單的公開 HTTP 函式來查詢最新 50 個「精選故事」和 以組合的形式提供結果

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

接著設定 Firebase 託管,以便提供及快取這個 Cloud 函式,方法如下: 修改 firebase.json。完成設定後,Firebase 託管 CDN 將根據 Deployment 的 Cloud 函式。快取到期時,系統會觸發並重新整理內容 再次執行函式

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

最後在網頁應用程式中,從 CDN 中擷取組合內容並載入 至 Firestore SDK

// 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
  // ...
}

預估節省費用

假設新聞網站每天獲得 10 萬名使用者,而且每位使用者載入 初次載入時顯示的 50 篇焦點新聞如未設定任何快取 50 x 100,000 = 每天從 Cloud Firestore 讀取 5,000,000 份文件。

現在,假設網站採用上述技術,並快取這 50 筆結果, 最長 5 分鐘。因此,您不必為每位使用者載入查詢結果, 結果每小時完全載入 12 次不論到達多少使用者 Cloud Firestore 的查詢數不會改變而不是 5,000,000 次文件讀取,這個網頁使用 12 x 24 x 50 = 14,400 份文件 每日讀取數這是因為 Firebase 託管與 藉由 Cloud Firestore 節省的費用,您可以輕鬆抵銷 Cloud Functions。

雖然開發人員在節省成本方面也能獲益,但最大的優勢是 使用者。而不是從 Firebase 託管 CDN 載入這 50 份文件 可以直接從 Cloud Firestore 取得 100 到 200 毫秒以上的資料 內容載入時間。研究多次顯示,快速載入的網頁 使用者也能滿意