2022 年 10 月 18 日に開催される Firebase Summit に、直接会場で、またはオンラインでご参加ください。Firebase を使用してアプリ開発を加速させ、自信を持ってアプリをリリースし、簡単にスケールする方法をご紹介します。 今すぐ登録

CDNからバンドルされたFirestoreコンテンツを提供する

コレクションでコンテンツを整理 必要に応じて、コンテンツの保存と分類を行います。

多くのアプリケーションは、最初のページ読み込み時にすべてのユーザーに同じコンテンツを提供します。たとえば、ニュース サイトで最新の記事を表示したり、e コマース サイトでベストセラーのアイテムを表示したりできます。

このコンテンツが Cloud Firestore から提供される場合、各ユーザーは、アプリケーションをロードするときに同じ結果に対して新しいクエリを発行します。これらの結果はユーザー間でキャッシュされないため、アプリケーションは必要以上に遅くなり、コストがかかります。

解決策: バンドル

Cloud Firestore バンドルを使用すると、Firebase Admin SDK を使用してバックエンドで一般的なクエリ結果からデータ バンドルを組み立て、CDN にキャッシュされたこれらの事前に計算された blob を提供できます。これにより、ユーザーは最初の読み込みがはるかに高速になり、Cloud Firestore クエリのコストが削減されます。

このガイドでは、Cloud Functions を使用してバンドルを生成し、Firebase Hosting を使用してバンドル コンテンツを動的にキャッシュして提供します。バンドルの詳細については、ガイドを参照してください。

最初に、最新の 50 件の「ストーリー」をクエリし、結果をバンドルとして提供する単純なパブリック HTTP 関数を作成します。

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);
});
      
ジャワ

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.jsonを変更して、この Cloud Function を提供およびキャッシュするように Firebase Hosting を構成します。この構成では、Cloud Function によって設定されたキャッシュ設定に従って、Firebase Hosting CDN がバンドル コンテンツを提供します。キャッシュの有効期限が切れると、関数が再度トリガーされてコンテンツが更新されます。

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

推定節約額

1 日あたり 100,000 人のユーザーを獲得し、各ユーザーが最初の読み込みで同じ 50 のトップ ストーリーを読み込むニュース Web サイトを考えてみましょう。キャッシュがなければ、Cloud Firestore から 1 日あたり 50 x 100,000 = 5,000,000 回のドキュメント読み取りが発生します。

ここで、サイトが上記の手法を採用し、これらの 50 件の結果を最大 5 分間キャッシュするとします。そのため、すべてのユーザーのクエリ結果を読み込む代わりに、結果は 1 時間に正確に 12 回読み込まれます。何人のユーザーがサイトにアクセスしても、Cloud Firestore へのクエリの数は変わりません。 5,000,000 回のドキュメント読み取りの代わりに、このページは 1 日あたり 12 x 24 x 50 = 14,400 回のドキュメント読み取りを使用します。 Firebase Hosting と Cloud Functions のわずかな追加コストは、Cloud Firestore のコスト削減によって簡単に相殺されます。

開発者はコスト削減の恩恵を受けますが、最大の受益者はユーザーです。 Cloud Firestore から直接ではなく、Firebase Hosting CDN からこれらの 50 個のドキュメントを読み込むと、ページのコンテンツ読み込み時間を 100 ~ 200 ミリ秒以上簡単に短縮できます。ページの読み込みが速いほど、ユーザーの満足度が高まることが研究で繰り返し示されています。