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