אפליקציות רבות מציגות תוכן זהה לכל המשתמשים בטעינת הדף הראשון. עבור למשל, אתר חדשות מציג את הסיפורים העדכניים ביותר, או אתר מסחר אלקטרוני של הפריטים הכי נמכרים.
אם התוכן הזה מוצג מ-Cloud Firestore, כל משתמש ינפיק שאילתה לאותן תוצאות כאשר הן טוענים את האפליקציה. כי אלה התוצאות לא נשמרות במטמון בין משתמשים, האפליקציה איטית יותר ועוד יקר ממה שהוא צריך להיות.
הפתרון: חבילות
חבילות Cloud Firestore מאפשרות לך להרכיב חבילות נתונים משאילתות נפוצות תוצאות בקצה העורפי באמצעות Firebase Admin SDK, ויציגו את הנתונים האלה blobs מחושבים מראש שנשמרו במטמון ב-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 כדי להציג ולשמור את הפונקציה של Cloud Functions על ידי
שינוי של firebase.json
. בהגדרה הזו, ה-CDN של Firebase Hosting
יציג את תוכן החבילה בהתאם להגדרות המטמון שנקבעו על ידי
הפונקציה של Cloud Functions. כשתוקף המטמון יפוג, התוכן ירענן על ידי
את הפונקציה שוב.
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 במקום מ-Cloud Firestore ישירות יכולים להתגלח בקלות 100-200 אלפיות השנייה או יותר זמן הטעינה של תוכן הדף. מחקרים הראו שוב ושוב שדפים מהירים משתמשים מרוצים יותר.