【问题标题】:Finding the collection length in firebase在firebase中查找集合长度
【发布时间】:2022-04-01 17:02:49
【问题描述】:

我的 Firebase 实时数据库中有超过 20k 个对象。我现在需要取出所有这些对象并对它们进行处理。问题是每次我这样做时服务器都会耗尽内存。这是我当前的代码:

sendEmail.get('/:types/:message', cors(), async (req, res, next) => {
    console.log(5);
    const types = JSON.parse(req.params.types);
    console.log('types', types);
    let recipients = [];
    let mails = [];
    if (types.includes('students')) {
        console.log(1);
        const tmpUsers = await admin.database().ref('Users').orderByChild('student').equalTo(true).once('value').then(r => r.val()).catch(e => console.log(e));
        recipients = recipients.concat(tmpUsers);
    }
    if (types.includes('solvers')) {
        console.log(2);
        let tmpUsers = await admin.database().ref('Users').orderByChild('userType').equalTo('person').once('value').then(r => r.val()).catch(e => console.log(e));
        tmpUsers = tmpUsers.concat(arrayFromObject(await admin.database().ref('Users').orderByChild('userType').equalTo('company').once('value').then(r => r.val()).catch(e => console.log(e))));
        recipients = recipients.concat(tmpUsers);
    }
});

所以我有两个选择。使用startAtendAt 流式传输或限制响应。但是为了限制响应,我需要知道我到底有多少对象。要做到这一点,我需要下载整个集合......你现在看到我的问题了。如何在不下载整个集合的情况下了解我有多少文档?

【问题讨论】:

  • 诀窍是使用limitToFirst/limitToLast 结合startAt/endAt。例如,您可以使用limitToFirst(100) 执行第一个查询,然后从返回的列表中获取最后一个键并将其用于:startAt(key) 和另一个limitToFirst(100)。在讨论range queries 的文档中有一个简单的示例,它暗示了这一点。我看看能不能写一个 node.js 的例子。
  • 我很感激一个例子。我想我明白你的意图,但我不确定我是否完全理解
  • 我会尝试制作一个。不过再想一想:如果是 Cloud Function 在处理大型数据集时超时,您可以改为 increase the function's timeout and memory allocation
  • 我已经试过了。 2GB是不够的。我自己都震惊了
  • 实际上我不确定分页是否会有所帮助,因为我已经编写了一个示例。无论如何我都会发布我的答案,但您可能不得不将数据处理拆分为多个函数调用。

标签: javascript node.js firebase firebase-realtime-database


【解决方案1】:

您可以尝试通过组合 limitToFirst/limitToLaststartAt/endAt 来对查询进行分页。

例如,您可以使用limitToFirst(1000) 执行第一个查询,然后从返回的列表中获取最后一个键并将其与startAt(key) 和另一个limitToFirst(1000) 一起使用,重复直到您到达集合的末尾。

在 node.js 中,它可能看起来像这样(未经测试的代码):

let recipients = [];

let tmpUsers = next();
recipients = filter(recipients, tmpUsers);

// startAt is inclusive, so when this reaches the last result there will only be 1
while (tmpUsers.length>1) {
    let lastKey = tmpUsers.slice(-1).pop().key;
    tmpUsers = next(lastKey);
    if (tmpUsers.length>1) { // Avoid duplicating last result
        recipients = filter(recipients, tmpUsers);
    }
}

async function next(startAt) {
    if (!startAt) {
        return await admin.database().ref('Users')
                .orderByKey()
                .limitToFirst(1000)
                .once('value').then(r => r.val()).catch(e => console.log(e));
    } else {
        return await admin.database().ref('Users')
                .orderByKey()
                .startAt(startAt)
                .limitToFirst(1000)
                .once('value').then(r => r.val()).catch(e => console.log(e));
    }
}

function filter(array1, array2) {
    // TODO: Filter the results here as we can't combine orderByChild/orderByKey
    return array1.concat(array2);
}

这样做的问题是您将无法使用数据库端过滤,因此您需要手动过滤结果,这可能会使事情变得更糟,具体取决于您需要保留多少项目recipients 变量一次。

另一种选择是分批处理它们(例如 1000 个),从 recipients 数组中弹出它们以释放资源,然后移至下一批。这完全取决于您需要对对象执行哪些操作,并且您需要权衡是否真的需要一次性处理(并保存在内存中)整个结果集。

【讨论】:

  • 它将继续运行,直到数据库只返回 1 个或更少的结果 (while (tmpUsers.length>1)),这意味着它已到达列表的末尾。这是因为 startAt 包含在内,因此返回 1 结果意味着它已到达末尾。至少,如果我的代码有效的话,就是这样!
  • 哎呀。那讲得通。我去看看
  • 很好的答案@Grimthorr!
【解决方案2】:

您无需知道集合的大小即可批量处理它们。

您可以通过按键对它们进行排序,限制为 1000 左右,然后在下一批开始第一批的最后一个键。

如果您仍然想知道如何获取集合的大小,唯一的好方法是在单独的节点中维护集合的大小,并在集合更新时保持更新。

【讨论】:

    猜你喜欢
    • 2020-11-21
    • 1970-01-01
    • 2021-04-23
    • 2023-03-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多