عرض محتوى Firestore مجمّع من شبكة توصيل محتوى

تعرض العديد من التطبيقات المحتوى نفسه لجميع المستخدمين عند تحميل الصفحة لأول مرة. على سبيل المثال، قد يعرض موقع إخباري آخر الأخبار، أو قد يعرض موقع للتجارة الإلكترونية المنتجات الأكثر مبيعًا.

إذا تم عرض هذا المحتوى من Cloud Firestore، سيُجري كل مستخدم طلب بحث جديدًا عن النتائج نفسها عند تحميل التطبيق. وبما أنّ هذه النتائج لا يتم تخزينها مؤقتًا بين المستخدمين، يكون التطبيق أبطأ وأكثر تكلفة مما يجب أن يكون عليه.

الحل: الحِزم

Cloud Firestore تسمح لك حِزم بتجميع حِزم البيانات من نتائج طلبات البحث الشائعة على الخلفية باستخدام مدير SDK في Firebase، وعرض هذه الكائنات الثنائية الكبيرة المحسوبة مسبقًا والمخزّنة مؤقتًا على شبكة توصيل المحتوى (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);
});
      
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 Function هذه وتخزينها مؤقتًا عن طريق تعديل firebase.json. باستخدام هذا الإعداد، ستعرض شبكة CDN الخاصة بخدمة Firebase Hosting CDN محتوى الحزمة وفقًا لإعدادات التخزين المؤقت التي ضبطتها دالة Cloud Function. عند انتهاء صلاحية ذاكرة التخزين المؤقت، سيتم تحديث المحتوى عن طريق تشغيل الدالة مرة أخرى.

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 خبرًا عند التحميل الأولي. بدون أي تخزين مؤقت، سيؤدي ذلك إلى قراءة 50 × 100,000 = 5,000,000 مستند في اليوم من Cloud Firestore.

لنفترض الآن أنّ الموقع الإلكتروني يعتمد الأسلوب أعلاه ويخزّن هذه النتائج الـ 50 مؤقتًا لمدة تصل إلى 5 دقائق. بدلاً من تحميل نتائج طلب البحث لكل مستخدم، يتم تحميل النتائج 12 مرة في الساعة بالضبط. بغض النظر عن عدد المستخدمين الذين يصلون إلى الموقع الإلكتروني، يظل عدد طلبات البحث في Cloud Firestore كما هو. بدلاً من قراءة 5,000,000 مستند، ستستخدم هذه الصفحة 12 × 24 × 50 = 14,400 عملية قراءة مستند في اليوم. يمكن تعويض التكاليف الإضافية الصغيرة لخدمتَي استضافة Firebase و Cloud Functions بسهولة من خلال التوفير في تكاليف Cloud Firestore.

في حين يستفيد المطوّر من التوفير في التكاليف، فإنّ المستفيد الأكبر هو المستخدم. يمكن أن يؤدي تحميل هذه المستندات الـ 50 من شبكة توصيل المحتوى (CDN) الخاصة بخدمة استضافة Firebase بدلاً من من Cloud Firestore مباشرةً إلى تقليل مدّة تحميل المحتوى للصفحة بمقدار 100 إلى 200 ملي ثانية أو أكثر بسهولة. أظهرت الدراسات مرارًا وتكرارًا أنّ الصفحات السريعة تعني مستخدمين أكثر رضا.