ارائه محتوای بسته‌بندی‌شده Firestore از CDN

بسیاری از برنامه‌ها در اولین بارگیری صفحه، محتوای یکسانی را به همه کاربران ارائه می‌دهند. برای مثال، سایت خبری ممکن است جدیدترین داستان‌ها را نشان دهد، یا سایت تجارت الکترونیک ممکن است پرفروش‌ترین موارد را نشان دهد.

اگر این محتوا از Cloud Firestore ارائه شود، هر کاربر هنگام بار کردن برنامه، پُرسمان جدیدی برای همان نتایج صادر خواهد کرد. Because these results are not cached between users, the application is slower and more expensive than it needs to be.

راه‌حل: بسته‌ها

دسته‌های Cloud Firestore به شما امکان می‌دهند دسته‌های داده را از نتایج پُرسمان رایج در پس‌زمینه بااستفاده از Firebase Admin SDK جمع‌آوری کنید و این حباب‌های ازپیش محاسبه‌شده را که در CDN ذخیره شده‌اند ارائه دهید. این کار تجربه بارگیری اولیه بسیار سریع‌تری را برای کاربران فراهم می‌کند و هزینه‌های پُرسمان Cloud Firestore را کاهش می‌دهد.

در این راهنما، از Cloud Functions برای تولید دسته‌ها و از Firebase Hosting برای ذخیره کردن در حافظه نهان و ارائه محتوای دسته به‌صورت پویا استفاده خواهیم کرد. اطلاعات بیشتر درباره بسته‌ها در راهنماها دردسترس است.

ابتدا یک تابع 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» را پیکربندی کنید تا این «تابع ابری» را با اصلاح firebase.json ارائه و ذخیره کند. با این پیکربندی، Firebase Hosting CDN محتوای دسته را طبق تنظیمات حافظه نهان تنظیم‌شده توسط Cloud Function ارائه خواهد کرد. وقتی حافظه پنهان منقضی می‌شود، محتوا را با راه‌اندازی مجدد تابع بازآوری می‌کند.

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

درنهایت در برنامه وب، محتوای بسته‌بندی‌شده را از CDN واکشی کنید و آن را در کیت توسعه نرم‌افزار 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
  // ...
}

صرفه‌جویی‌های برآوردشده

وب‌سایت خبری‌ای را درنظر بگیرید که روزانه ۱۰۰٬۰۰۰ کاربر دارد و هر کاربر در بارگیری اولیه، همان ۵۰ داستان برتر را بارگیری می‌کند. بدون هیچ‌گونه ذخیره‌سازی در حافظه نهان، این امر منجر به ۵۰ × ۱۰۰٬۰۰۰ = ۵٬۰۰۰٬۰۰۰ خواندن سند در روز از Cloud Firestore می‌شود.

اکنون فرض کنید سایت از تکنیک بالا استفاده می‌کند و آن ۵۰ نتیجه را تا ۵ دقیقه در حافظه نهان ذخیره می‌کند. بنابراین به‌جای بار کردن نتایج پُرسمان برای هر کاربر، نتایج دقیقاً ۱۲ بار در ساعت بار می‌شود. مهم نیست چند کاربر به سایت وارد می‌شوند، تعداد پُرسمان‌ها به Cloud Firestore ثابت می‌ماند. به‌جای ۵٬۰۰۰٬۰۰۰ خواندن سند، این صفحه از ۱۲ × ۲۴ × ۵۰ = ۱۴٬۴۰۰ خواندن سند در روز استفاده می‌کند. هزینه‌های اضافی اندک برای «میزبانی Firebase» و Cloud Functions به‌راحتی با صرفه‌جویی در هزینه Cloud Firestore جبران می‌شود.

درحالی‌که توسعه‌دهنده از صرفه‌جویی در هزینه بهره‌مند می‌شود، بزرگ‌ترین ذینفع کاربر است. بار کردن این ۵۰ سند از CDN «میزبانی Firebase» به‌جای بار کردن مستقیم از Cloud Firestore می‌تواند به‌راحتی ۱۰۰ تا ۲۰۰ میلی‌ثانیه یا بیشتر از زمان بار کردن محتوای صفحه بکاهد. مطالعات مکرراً نشان داده‌اند که صفحه‌های سریع‌تر به‌معنای کاربران خوشحال‌تر است.