خدمة محتوى 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 × 100،000 = 5،000،000 قراءة مستند يوميًا من Cloud Firestore.

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

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