خدمة محتوى Firestore المجمعة من CDN

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

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

الحل: الحزم

تسمح لك حزم Cloud Firestore بتجميع حزم البيانات من نتائج الاستعلام الشائعة على الواجهة الخلفية باستخدام Firebase Admin SDK، وخدمة هذه البيانات الكبيرة المحسوبة مسبقًا والمخزنة مؤقتًا على شبكة CDN. وهذا يمنح المستخدمين تجربة تحميل أولي أسرع بكثير ويقلل من تكاليف استعلام Cloud Firestore.

سنستخدم في هذا الدليل وظائف السحابة لإنشاء الحزم واستضافة 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
  // ...
}

المدخرات المقدرة

لنأخذ على سبيل المثال موقعًا إخباريًا يستقبل 100000 مستخدم يوميًا ويقوم كل مستخدم بتحميل نفس أهم 50 خبرًا عند التحميل الأولي. بدون أي تخزين مؤقت، قد يؤدي ذلك إلى قراءة 50 × 100000 = 5000000 مستند يوميًا من Cloud Firestore.

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

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