CDN에서 번들된 Firestore 콘텐츠 제공

대부분의 애플리케이션에서는 페이지가 처음 로드될 때 모든 사용자에게 동일한 콘텐츠를 제공합니다. 예를 들어 뉴스 사이트에 최신 뉴스를 보여주거나 전자상거래 사이트에서 가장 잘 팔리는 상품을 보여주는 경우입니다.

이 콘텐츠가 Cloud Firestore에서 제공되는 경우 각 사용자가 새 동일한 결과를 쿼리할 수 있습니다. 이러한 결과는 사용자 간에 캐시되지 않으므로 애플리케이션은 필요한 것보다 느리고 비용이 많이 발생합니다.

솔루션: 번들

Cloud Firestore 번들을 사용하면 일반 쿼리에서 데이터 번들을 조합할 수 있습니다. 백엔드에서 결과를 수집하고, 이러한 결과를 사전 계산된 blob을 CDN에 캐시합니다. 이를 통해 사용자는 첫 로드 속도가 빨라지고 Cloud Firestore 쿼리 비용이 절감됩니다.

이 가이드에서는 Cloud Functions를 사용하여 번들을 생성하고 Firebase Hosting: 번들 콘텐츠를 동적으로 캐시 및 제공합니다. 더보기 번들에 대한 정보는 가이드에서 확인할 수 있습니다.

먼저 간단한 공개 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);
});
      
자바

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 함수를 제공하고 캐시하도록 Firebase 호스팅을 구성합니다. 이 구성을 사용하면 Firebase Hosting CDN이 Google은 게시자가 설정한 캐시 설정에 따라 번들 콘텐츠를 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
  // ...
}

예상 절감액

하루에 100,000명의 사용자가 유입되고 각 사용자가 최초 로드 시 동일한 50개의 주요 뉴스를 로드하는 뉴스 웹사이트를 가정해 보겠습니다. 캐싱이 없으면 Cloud Firestore에서 일일 문서 읽기 50 x 100,000 = 5,000,000회

이제 사이트에서 위의 기법을 채택하고 50개의 결과를 최대 5분 동안 캐시한다고 가정해 보겠습니다. 따라서 모든 사용자에게 쿼리 결과를 로드하는 대신 결과가 정확히 시간당 12회 로드됩니다. 사이트에 얼마나 많은 사용자가 유입되었는지에 관계없이 Cloud Firestore의 쿼리 수는 동일하게 유지됩니다. 이 페이지에서는 5,000,000회의 문서 읽기 대신 하루에 12 x 24 x 50 = 14,400회의 문서 읽기를 사용합니다. Firebase 호스팅 및 Cloud FunctionsCloud Firestore 비용 절감으로 쉽게 상쇄됩니다.

개발자는 비용 절감 혜택을 얻을 수 있지만 가장 큰 수혜자는 사용자입니다. 다음 50개 문서를 Firebase 호스팅 CDN에서 로드하는 것이 아니라 Cloud Firestore에서 직접 전송하는 경우 페이지 콘텐츠 로드 시간과 지연 시간을 확인할 수 있습니다 연구에 의하면 페이지 속도가 빨라지면 사용자의 만족도도 높아지는 것으로 여러 차례 확인되었습니다.