Check out what’s new from Firebase at Google I/O 2022. Learn more

הצג תוכן Firestore מאגד מ-CDN

יישומים רבים מגישים את אותו תוכן לכל המשתמשים בעת טעינת העמוד הראשון. לדוגמה, אתר חדשות עשוי להציג את הסיפורים האחרונים, או שאתר מסחר אלקטרוני עשוי להציג את הפריטים הנמכרים ביותר.

אם תוכן זה מוגש מ-Cloud Firestore, כל משתמש יוציא שאילתה חדשה לאותן תוצאות בעת טעינת האפליקציה. מכיוון שתוצאות אלו אינן מאוחסנות במטמון בין משתמשים, האפליקציה איטית ויקרה יותר ממה שהיא צריכה להיות.

פתרון: חבילות

חבילות Cloud Firestore מאפשרות לך להרכיב חבילות נתונים מתוצאות שאילתות נפוצות בקצה העורפי באמצעות Firebase Admin SDK, ולהגיש את הבלובים המחושבים מראש האלה המאוחסנים במטמון ב-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 Hosting לשרת ולשמור את פונקציית הענן הזו על ידי שינוי 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 x 100,000 = 5,000,000 קריאות מסמכים ביום מ-Cloud Firestore.

כעת נניח שהאתר מאמץ את הטכניקה שלמעלה ומאחסן את 50 התוצאות הללו למשך עד 5 דקות. אז במקום לטעון את תוצאות השאילתה עבור כל משתמש, התוצאות נטענות בדיוק 12 פעמים בשעה. לא משנה כמה משתמשים מגיעים לאתר, מספר השאילתות ל-Cloud Firestore נשאר זהה. במקום 5,000,000 קריאות מסמכים, דף זה ישתמש ב-12 x 24 x 50 = 14,400 קריאות מסמכים ביום. העלויות הנוספות הקטנות עבור Firebase Hosting ו-Cloud Functions מקוזזות בקלות על ידי חיסכון בעלויות של Cloud Firestore.

בעוד שהמפתח מרוויח מהחיסכון בעלויות, המרוויח הגדול ביותר הוא המשתמש. טעינת 50 המסמכים הללו מה-CDN של Firebase Hosting ולא ישירות מ-Cloud Firestore יכולה בקלות לגלח 100-200ms או יותר מזמן טעינת התוכן של הדף. מחקרים הראו שוב ושוב שדפים מהירים פירושם משתמשים מאושרים יותר.