Javascript Firestore云函数递归更新子集合/集合组

Javascript Firestore云函数递归更新子集合/集合组,javascript,firebase,recursion,google-cloud-firestore,google-cloud-functions,Javascript,Firebase,Recursion,Google Cloud Firestore,Google Cloud Functions,我有这个云功能: import pLimit from "p-limit"; const syncNotificationsAvatar = async ( userId: string, change: Change<DocumentSnapshot> ) => { if (!change.before.get("published") || !change.after.exists) { return; } const before: Pr

我有这个云功能:

import pLimit from "p-limit";


const syncNotificationsAvatar = async (
  userId: string,
  change: Change<DocumentSnapshot>
) => {
  if (!change.before.get("published") || !change.after.exists) {
    return;
  }

  const before: Profile = change.before.data() as any;
  const after: Profile = change.after.data() as any;
  const keysToCompare: (keyof Profile)[] = ["avatar"];
  if (
    arraysEqual(
      keysToCompare.map((k) => before[k]),
      keysToCompare.map((k) => after[k])
    )
  ) {
    return;
  }

  const limit = pLimit(1000);

  const input = [
    limit(async () => {
      const notifications = await admin
        .firestore()
        .collectionGroup("notifications")
        .where("userId", "==", userId)
        .limit(1000)
        .get()

      await Promise.all(
        chunk(notifications.docs, 500).map(
          async (docs: admin.firestore.QueryDocumentSnapshot[]) => {
            const batch = admin.firestore().batch();
            for (const doc of docs) {
              batch.update(doc.ref, {
                avatar: after.avatar
              });
            }
            await batch.commit();
          }
        )
      );
    })
  ];

  return await Promise.all(input);
};


从“p-limit”导入pLimit;
const syncNotificationsAvatar=async(
userId:string,
改变:改变
) => {
如果(!change.before.get(“published”)| |!change.after.exists){
返回;
}
const before:Profile=change.before.data()如有;
const after:Profile=change.after.data()如有;
const keystompare:(keyof Profile)[]=[“化身”];
如果(
arraysEqual(
keystompare.map((k)=>在[k]之前),
keystompare.map((k)=>在[k]之后)
)
) {
返回;
}
常数限值=pLimit(1000);
常量输入=[
限制(异步()=>{
const notifications=等待管理员
.firestore()
.collectionGroup(“通知”)
.where(“userId”,“userId=”,userId)
.限额(1000)
.get()
等待承诺(
区块(notifications.docs,500).map(
异步(文档:admin.firestore.QueryDocumentSnapshot[])=>{
const batch=admin.firestore().batch();
for(文件的常量文件){
批处理更新(doc.ref{
阿凡达:在阿凡达之后
});
}
等待批处理。提交();
}
)
);
})
];
返回等待承诺。全部(输入);
};

如何递归更新
通知
集合,但首先将查询限制为
1.000
文档(直到没有更多文档),然后再
批处理。更新
文档?我担心此查询将超时,因为收集量可能会随着时间的推移而增大

发布我制定的解决方案,虽然没有遵循问题的上下文,但它可以很容易地组合起来。希望它能帮助别人

import * as admin from "firebase-admin";

const onResults = async (
  query: admin.firestore.Query,
  action: (batch: number, docs: admin.firestore.QueryDocumentSnapshot[]) => Promise<void>
) => {
  let batch = 0;
  const recursion = async (start?: admin.firestore.DocumentSnapshot) => {
    const { docs, empty } = await (start == null
      ? query.get()
      : query.startAfter(start).get());
    if (empty) {
      return;
    }
    batch++;
    await action(
      batch,
      docs.filter((d) => d.exists)
    ).catch((e) => console.error(e));
    await recursion(docs[docs.length - 1]);
  };
  await recursion();
};

const getMessages = async () => {
  const query = admin
    .firestore()
    .collection("messages")
    .where("createdAt", ">", new Date("2020-05-04T00:00:00Z"))
    .limit(200);

  const messages: FirebaseFirestore.DocumentData[] = [];

  await onResults(query, async (batch, docs) => {
    console.log(`Getting Message: ${batch * 200}`);
    docs.forEach((doc) => {
       messages.push(doc.data());
    });
  });
  return messages;
};

import*作为“firebase管理员”的管理员;
const onResults=async(
查询:admin.firestore.query,
操作:(批次:编号,文档:admin.firestore.QueryDocumentSnapshot[])=>承诺
) => {
设批次=0;
常量递归=异步(开始?:admin.firestore.DocumentSnapshot)=>{
const{docs,empty}=await(start==null
?query.get()
:query.startAfter(start.get());
if(空){
返回;
}
批处理++;
等待行动(
批次,
docs.filter((d)=>d.exists)
).catch((e)=>console.error(e));
等待递归(docs[docs.length-1]);
};
等待递归();
};
const getMessages=async()=>{
const query=admin
.firestore()
.收集(“信息”)
其中(“createdAt”,“>”,新日期(“2020-05-04T00:00:00Z”))
.限额(200);
常量消息:FirebaseFirestore.DocumentData[]=[];
等待结果(查询、异步(批处理、文档)=>{
log(`get Message:${batch*200}`);
docs.forEach((doc)=>{
messages.push(doc.data());
});
});
返回消息;
};

您使用的是
p-limit
,但实际上您使用
limit
函数只向
input
数组传递了一个承诺;您不需要
p-limit
,因为您不关心并发执行多少承诺。您不必将获取的文档限制为1000个(从而执行任何类型的获取递归),因为您可以增加函数的超时和内存使用,请参见。您可以使用来获取文档块,但我不建议这样做。不过,要注意系统调用该函数的频率,因为它将有
运行时间X文档
读/写费用。谢谢Christos,我是前一段时间发布everypay回购的人!哇,这个!是的,我记得你,你从lint less和pretty less那里保存了这个包,做得很好Hey@Christolytras,找到了一个解决方案,抛弃了pLimit,创建了一个递归函数。谢谢你的帮助。非常有趣的查询游标和承诺用法!您是否针对多个(数千个)文档测试了它的性能?干得好+1不,还没有,我会在生产中运行它,并对结果发表评论。嘿@Christolytras,花了约13秒处理了约10.000个文档。为什么我认为更新文档需要很多时间?你对那场演出满意吗?您在哪里有更新文档代码?这不是为了更新用户的头像吗?递归非常好,但我认为您应该尝试将其与500个create/commit批处理(如在您的O/P中)以及递归结合起来,看看会得到什么结果。批量更新500个文档块比更新循环中的文档块花费的时间更少。在这种递归情况下,我不需要更新,只需要阅读。我将尝试将其与更新查询结合起来,并让您知道结果。谢谢你的补充信息。