Многие приложения показывают всем пользователям один и тот же контент при загрузке первой страницы. Например, новостной сайт может показывать последние новости, а сайт электронной коммерции — самые продаваемые товары.
Если этот контент предоставляется из Cloud Firestore , каждый пользователь будет отправлять новый запрос на те же результаты при загрузке приложения. Поскольку эти результаты не кэшируются между пользователями, приложение работает медленнее и требует больше ресурсов, чем следовало бы.
Решение: Пакеты
Пакеты данных Cloud Firestore позволяют собирать пакеты данных из результатов общих запросов на бэкенде с помощью Firebase Admin SDK и предоставлять эти предварительно вычисленные BLOB-объекты, кэшированные в 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); });
Ява
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
. При такой настройке CDN Firebase Hosting будет обслуживать содержимое пакета в соответствии с настройками кэширования, заданными облачной функцией. По истечении срока действия кэша контент будет обновляться, запуская функцию повторно.
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 и Cloud Functions легко компенсируются экономией на Cloud Firestore .
Хотя разработчик выигрывает от экономии средств, наибольшую выгоду получает пользователь. Загрузка этих 50 документов из CDN Firebase Hosting, а не напрямую из Cloud Firestore может легко сократить время загрузки контента страницы на 100–200 мс и более. Исследования неоднократно показывали, что чем быстрее загружаются страницы, тем выше уровень удовлетворенности пользователей.