Birçok uygulama, ilk sayfa yüklendiğinde tüm kullanıcılara aynı içeriği sunar. Örneğin, bir haber sitesi en son haberleri veya bir e-ticaret sitesinde en çok satan öğeleri gösterebilir.
Bu içerik Cloud Firestore'ten yayınlanırsa her kullanıcı, uygulamayı yüklediğinde aynı sonuçlar için yeni bir sorgu gönderir. Bu sonuçlar kullanıcılar arasında önbelleğe alınmadığından uygulama olması gerekenden daha yavaş ve daha pahalıdır.
Çözüm: Paketler
Cloud Firestore paketleri, Firebase Admin SDK'sını kullanarak arka uçtaki ortak sorgu sonuçlarından veri paketleri oluşturmanıza ve bu önceden hesaplanmış blob'ları bir CDN'de önbelleğe alarak yayınlamanıza olanak tanır. Bu sayede kullanıcılarınıza çok daha hızlı bir ilk yükleme deneyimi sunabilir ve Cloud Firestore sorgu maliyetlerinizi düşürebilirsiniz.
Bu kılavuzda, grupları oluşturmak için Cloud Functions, paket içeriğini dinamik olarak önbelleğe alıp yayınlamak için Firebase Hosting özelliğini kullanacağız. Paketler hakkında daha fazla bilgiyi kılavuzlarda bulabilirsiniz.
Öncelikle son 50 "hikayeyi" sorgulamak ve sonucu bir paket olarak sunmak için basit bir herkese açık HTTP işlevi oluşturun.
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())); } }
Ardından, firebase.json
değerini değiştirerek Firebase Hosting'i bu Cloud Function'ı yayınlayıp önbelleğe alacak şekilde yapılandırın. Bu yapılandırmada Firebase Hosting CDN, paket içeriğini Cloud Functions işlevi tarafından belirlenen önbellek ayarlarına göre yayınlar. Önbelleğin süresi dolduğunda işlevi tekrar tetikleyerek içeriği yeniler.
firebase.json
{
"hosting": {
// ...
"rewrites": [{
"source": "/createBundle",
"function": "createBundle"
}]
},
// ...
}
Son olarak, web uygulamanızda paketlenmiş içeriği CDN'den alın ve Firestore SDK'sına yükleyin.
// 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
// ...
}
Tahmini Tasarruf Miktarı
Günde 100.000 kullanıcı alan ve her kullanıcının ilk yüklemede aynı 50 popüler haberi yüklediği bir haber web sitesini düşünün. Önbelleğe alma olmadan bu işlem, Cloud Firestore'ten günde 50 x 100.000 = 5.000.000 belge okumayla sonuçlanır.
Şimdi sitenin yukarıdaki tekniği benimsediğini ve bu 50 sonucu 5 dakikaya kadar önbelleğe aldığını varsayalım. Bu nedenle, sorgu sonuçları her kullanıcı için yüklenmek yerine saatte tam 12 kez yüklenir. Siteye kaç kullanıcı gelirse gelsin, Cloud Firestore'a gönderilen sorgu sayısı aynı kalır. Bu sayfa, 5.000.000 doküman okuması yerine günde 12 x 24 x 50 = 14.400 doküman okuması kullanır. Firebase Hosting ve Cloud Functions'in küçük ek maliyetleri, Cloud Firestore maliyet tasarrufu sayesinde kolayca dengelenebilir.
Geliştirici maliyet tasarrufundan faydalansa da en büyük fayda, kullanıcıdır. Bu 50 dokümanı doğrudan Cloud Firestore yerine Firebase Hosting CDN'den yüklemek, sayfanın içerik yükleme süresinden kolayca 100-200 ms veya daha fazla zaman kazanılmasını sağlayabilir. Çalışmalar, hızlı sayfaların kullanıcıların memnuniyetini artırdığını defalarca göstermiştir.