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

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

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

الحل: الحِزم

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

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

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