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

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

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

الحل: الحِزم

تسمح لك حِزم Cloud Firestore بتجميع حِزم بيانات من طلبات بحث شائعة نتائج في الخلفية باستخدام SDK لمشرف Firebase، وعرض هذه وحدات تخزين ثنائية كبيرة محسوبة مسبقًا مُخزَّنة مؤقتًا على شبكة توصيل محتوى (CDN). يمنح ذلك المستخدمين فرصة تجربة تحميل أسرع لأول مرة، كما تقلل تكاليف طلبات البحث في Cloud Firestore.

في هذا الدليل، سنستخدم دوال Cloud لإنشاء الحِزم استضافة 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);
});
      
جافا

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 محتوى الحزمة وفقًا لإعدادات ذاكرة التخزين المؤقت التي ضبطها وظيفة السحابة الإلكترونية. عند انتهاء صلاحية ذاكرة التخزين المؤقت، سيتم تحديث المحتوى من خلال تفعيل الدالة مرة أخرى.

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.

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