אפליקציות רבות מציגות את אותו תוכן לכל המשתמשים בטעינת הדף הראשונה. לדוגמה, באתר חדשות יכולות להופיע הכתבות האחרונות, ובאתר מסחר אלקטרוני יכולים להופיע הפריטים הנמכרים ביותר.
אם התוכן הזה מוצג מ-Cloud Firestore, כל משתמש ישלח שאילתה חדשה לאותן תוצאות כשהוא יטען את האפליקציה. התוצאות האלה לא נשמרות במטמון בין המשתמשים, ולכן האפליקציה איטית ויקרה יותר ממה שהיא צריכה להיות.
הפתרון: חבילות
חבילות Cloud Firestore מאפשרות לכם לאסוף חבילות נתונים מתוצאות של שאילתות נפוצות בקצה העורפי באמצעות ה-SDK של Firebase לאדמינים, ולהציג את ה-blobs האלה שחושבו מראש ונשמרו במטמון ב-CDN. כך המשתמשים נהנים מחוויית טעינה ראשונית מהירה יותר, ועלויות השאילתות ב-Cloud Firestore מצטמצמות.
במדריך הזה נשתמש ב-Cloud Functions כדי ליצור חבילות וב-Firebase Hosting כדי לשמור במטמון באופן דינמי את תוכן החבילה ולהציג אותו. מידע נוסף על חבילות זמין במדריכים.
קודם כל, יוצרים פונקציית HTTP ציבורית פשוטה כדי לשלוח שאילתה על 50 ה-"stories" האחרונים ולהציג את התוצאה כחבילה.
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.json כדי להגדיר את אירוח ב-Firebase כך שיציג את הפונקציה הזו של Cloud Functions וישמור אותה במטמון. בהגדרה הזו, רשת ה-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 כפול 100,000, כלומר 5,000,000 קריאות של מסמכים ביום מ-Cloud Firestore.
עכשיו נניח שהאתר משתמש בשיטה שלמעלה ושומר במטמון את 50 התוצאות האלה למשך עד 5 דקות. לכן, במקום לטעון את תוצאות השאילתה לכל משתמש, התוצאות נטענות בדיוק 12 פעמים בשעה. לא משנה כמה משתמשים מגיעים לאתר, מספר השאילתות ל-Cloud Firestore נשאר זהה. במקום 5,000,000 קריאות מסמכים, הדף הזה ישתמש ב-12 x 24 x 50 = 14,400 קריאות מסמכים ביום. העלויות הנוספות הקטנות של אירוח ב-Firebase ו-Cloud Functions מתקזזות בקלות עם החיסכון בעלויות של Cloud Firestore.
המפתח נהנה מהחיסכון בעלויות, אבל המשתמש הוא המרוויח העיקרי. טעינת 50 המסמכים האלה מ-CDN של אירוח ב-Firebase במקום ישירות מ-Cloud Firestore יכולה בקלות לקצר את זמן טעינת התוכן של הדף ב-100-200 אלפיות השנייה או יותר. מחקרים רבים הראו שדפים מהירים משפרים את חוויית המשתמש.