【问题标题】:How to improve preformance of firestore cache query如何提高 Firestore 缓存查询的性能
【发布时间】:2019-10-22 09:02:58
【问题描述】:

我正在开发一个 PWA,它显示一个交易列表(交易是一个包含大约 10 个字段的对象)。我正在使用 firestore 进行存储和实时更新,我也有 enabled persistance

我希望我的应用程序将所有数据都保存在内存中,并且我希望自己只显示必要的信息(例如,对事务列表使用虚拟滚动)。由于这个原因,我听了整个收藏(也就是交易)。

在应用程序启动时,我想确保数据已加载,因此我使用one time cache query 获取事务。我希望查询几乎是即时的,但是在笔记本电脑上大约需要 1 秒才能获取初始数据(而且我还有另一个从缓存中获取的集合,这会在 2 秒后解决交易请求后)。对于移动设备,大约需要 ~9 秒 (loading on mobile, loading on laptop)

我希望我的应用感觉是即时的,但我需要几秒钟才能获得数据。请注意,我没有进行任何高级查询(我只是想将数据加载到内存中)。

我是不是做错了什么?我已阅读 Firestore 文档,但我认为缓存中的数据量不会导致性能如此糟糕。

更新:即使我将初始查询限制为仅加载 20 个文档。检索它们仍然需要大约 2 秒。
更新 2: 代码如下所示:

export const initializeFirestore = (): Thunk => (dispatch) => {
  const initialQueries: Array<Promise<unknown>> = []
  getQueries().forEach((query) => {
    const q = query.createFirestoneQuery()
    initialQueries.push(
      q
        .get({
          source: 'cache',
        })
        .then((snapshot) =>
          dispatch(firestoneChangeAction(query, snapshot, true)),
        ),
    )
    q.onSnapshot((change) => {
      dispatch(firestoneChangeAction(query, change))
    })
  })

  console.log('Now I am just waiting for initial data...')
  return Promise.all(initialQueries)
}

【问题讨论】:

  • 这里有一篇关于why Firestore queries may be running slow的有趣读物。
  • 您在打开应用程序时加载了多少文档?也请回复@AlexMamo
  • @PabloAlmécijaRodríguez 是的,这部分与我最相关:“第三,考虑减少离线缓存的大小。移动设备上的缓存大小默认设置为 100MB,但在某些在这种情况下,这可能是您的设备无法处理的太多数据,尤其是当您最终将大部分数据集中在一个庞大的集合中时。”。但如果 1500 件物品在这种情况下是一个“巨大的收藏”,我会感到非常难过。
  • @AlexMamo 我正在阅读所有这些(约 1500 个),但即使我将查询限制为 20 个文档,加载仍需要约 3 秒。
  • @AlexMamo,是的,但我发现了为什么代码这么慢。如果我删除快照订阅,代码会更快(如我所料)。也就是说,现在我首先触发初始查询并等待它们完成,然后才在查询上调用 onSnapshot ......但是,我不明白为什么这很重要。

标签: firebase google-cloud-firestore offline-caching


【解决方案1】:

您可能会对 Firebase 工程师在 2019 年 Firebase 峰会的“Faster web apps with Firebase”会议上提出的智能方法感兴趣(您可以在此处观看视频:https://www.youtube.com/watch?v=DHbVyRLkX4c)。

简而言之,他们的想法是使用 Firestore REST API 对数据库进行第一次查询(无需下载任何 SDK),同时动态导入 Web SDK 以便将其用于后续查询。

github 仓库在这里:https://github.com/hsubox76/fireconf-demo


我粘贴下面的关键 js 文件 (https://github.com/hsubox76/fireconf-demo/blob/master/src/dynamic.js) 的内容以供进一步参考。

import { firebaseConfigDynamic as firebaseConfig } from "./shared/firebase-config";
import { renderPage, logPerformance } from "./shared/helpers";

let firstLoad = false;

// Firestore REST URL for "current" collection.
const COLLECTION_URL =
  `https://firestore.googleapis.com/v1/projects/exchange-rates-adcf6/` +
  `databases/(default)/documents/current`;


// STEPS
// 1) Fetch REST data
// 2) Render data
// 3) Dynamically import Firebase components
// 4) Subscribe to Firestore



// HTTP GET from Firestore REST endpoint.
fetch(COLLECTION_URL)
  .then(res => res.json())
  .then(json => {
    // Format JSON data into a tabular format.
    const stocks = formatJSONStocks(json);

    // Measure time between navigation start and now (first data loaded)
    performance && performance.measure("initialDataLoadTime");

    // Render using initial REST data.
    renderPage({
      title: "Dynamic Loading (no Firebase loaded)",
      tableData: stocks
    });

    // Import Firebase library.
    dynamicFirebaseImport().then(firebase => {
      firebase.initializeApp(firebaseConfig);
      firebase.performance(); // Use Firebase Performance - 1 line
      subscribeToFirestore(firebase);
    });
  });

/**
 * FUNCTIONS
 */

// Dynamically imports firebase/app, firebase/firestore, and firebase/performance.
function dynamicFirebaseImport() {
  const appImport = import(
    /* webpackChunkName: "firebase-app-dynamic" */
    "firebase/app"
  );
  const firestoreImport = import(
    /* webpackChunkName: "firebase-firestore-dynamic" */
    "firebase/firestore"
  );
  const performanceImport = import(
    /* webpackChunkName: "firebase-performance-dynamic" */
    "firebase/performance"
  );
  return Promise.all([appImport, firestoreImport, performanceImport]).then(
    ([dynamicFirebase]) => {
      return dynamicFirebase;
    }
  );
}

// Subscribe to "current" collection with `onSnapshot()`.
function subscribeToFirestore(firebase) {
  firebase
    .firestore()
    .collection(`current`)
    .onSnapshot(snap => {
      if (!firstLoad) {
        // Measure time between navigation start and now (first data loaded)
        performance && performance.measure("realtimeDataLoadTime");
        // Log to console for internal development
        logPerformance();
        firstLoad = true;
      }
      const stocks = formatSDKStocks(snap);
      renderPage({
        title: "Dynamic Loading (Firebase now loaded)",
        tableData: stocks
      });
    });
}

// Format stock data in JSON format (returned from REST endpoint)
function formatJSONStocks(json) {
  const stocks = [];
  json.documents.forEach(doc => {
    const pathParts = doc.name.split("/");
    const symbol = pathParts[pathParts.length - 1];
    stocks.push({
      symbol,
      value: doc.fields.closeValue.doubleValue || 0,
      delta: doc.fields.delta.doubleValue || 0,
      timestamp: parseInt(doc.fields.timestamp.integerValue)
    });
  });
  return stocks;
}

// Format stock data in Firestore format (returned from `onSnapshot()`)
function formatSDKStocks(snap) {
  const stocks = [];
  snap.forEach(docSnap => {
    if (!docSnap.data()) return;
    const symbol = docSnap.id;
    const value = docSnap.data().closeValue;
    stocks.push({
      symbol,
      value,
      delta: docSnap.data().delta,
      timestamp: docSnap.data().timestamp
    });
  });
  return stocks;
}

【讨论】:

  • 您提出了一个很好的观点,即在我的查询之前,firestore 可能正在做一些其他的异步工作,我认为这可以解释性能不佳(但我会验证这一点)。异步导入 firebase 也是一个好主意,但我不想在初始加载时调用 REST,因为我希望我的应用程序离线工作(具有合理的性能)。
【解决方案2】:

你没有做错任何事。查询将花费尽可能多的时间来完成。这就是许多网站使用加载指示器的原因。

对于您应用中的第一个查询,它将包括完全初始化 SDK 所需的时间,这可能涉及异步工作,而不仅仅是查询本身。还要记住,从本地磁盘读取和排序数据不一定“快”,而且对于大量文档,本地磁盘缓存读取甚至可能比获取相同文档的时间更昂贵网络。

由于我们没有任何迹象表明您有多少文档、您尝试传输的总数据量以及您为此使用的代码,我们所能做的就是猜测。但是,除了可能限制结果集的大小之外,您实际上无法加快初始查询的速度。

如果您认为您遇到的是错误,请提交错误报告on GitHub

【讨论】:

    猜你喜欢
    • 2011-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-17
    • 1970-01-01
    • 2013-03-21
    • 1970-01-01
    相关资源
    最近更新 更多