【问题标题】:Querying same document in parallel in the same API in mongoDB在mongoDB的同一个API中并行查询同一个文档
【发布时间】:2021-04-17 12:55:15
【问题描述】:

我有一个用 typescript 编写的 API,我尝试使用 promise.allsettled 对同一文档运行并行查询,但它的性能更差,我猜它们是按顺序运行的。有没有办法在 mongoDB 的同一连接中对同一文档执行并行查询。这是代码:

console.time("normal");
let normal = await ContentRepo.geBySkillIdWithSourceFiltered(
    [chosenSkillsArr[0].sid!],
    readContentIds,
    body.isVideoIncluded,
    true,
    true
);
console.timeEnd("normal");

console.time("parallel");
const parallel = await Promise.allSettled(
    chosenSkillsArr.map(async (skill: IScrapeSkillDocument) => {
        const result = await ContentRepo.geBySkillIdWithSourceFiltered(
            [skill.sid!],
            readContentIds,
            body.isVideoIncluded,
            true,
            true
        );
    })
);
console.timeEnd("parallel");

我调用的函数在这里:

async geBySkillIdWithSourceFiltered(
    skillIds: string[],
    contentIds: string[],
    isVideoIncluded?: boolean,
    isCuratorIdFilter?: boolean,
    activeSourceFilter?: boolean
): Promise<IContentWithSource[]> {
    try {
        console.time(`single-${skillIds}`);
        var contents = await ContentM.find({
            $and: [
                { "skills.skillId": { $in: skillIds } },
                { recordStatus: true },
                isCuratorIdFilter ? { curatorId: 0 } : {},
                isVideoIncluded ? {} : { type: contentTypeNumber.read },
                { _id: { $nin: contentIds } },
            ],
        }).exec();
        var items: IContentWithSource[] = [];
        var sourceIds = new Set<string>();
        contents.forEach((content) => {
            if (!this.isEmpty(content.sourceId)) {
                sourceIds.add(content.sourceId!);
            }
        });
        var sources: any = {};
        var sourcesArr = await new SourceRepo().getByIds(
            Array.from(sourceIds)
        );
        sourcesArr.forEach((source) => {
            sources[source._id] = source;
        });

        if (activeSourceFilter) {
            contents
                .map((i) => i.toJSON() as IContentWithSource)
                .map((k) => {
                    if (sources[k.sourceId!].isActive) {
                        k.source = sources[k.sourceId!];
                        items.push(k);
                    }
                });
        } else {
            contents
                .map((i) => i.toJSON() as IContentWithSource)
                .map((k) => {
                    k.source = sources[k.sourceId!];
                    items.push(k);
                });
        }
        console.timeEnd(`single-${skillIds}`);

        return items;
    } catch (err) {
        throw err;
    }
}

结果是:

single-KS120B874P2P6BK1MQ0T: 1872.735ms
normal: 1873.934ms
single-KS120B874P2P6BK1MQ0T: 3369.925ms
single-KS440QS66YCBN23Y8K25: 3721.214ms
single-KS1226Y6DNDT05G7FJ4J: 3799.050ms
parallel: 3800.586ms

【问题讨论】:

    标签: javascript mongodb typescript promise parallel-processing


    【解决方案1】:

    您似乎在并行版本中运行了更多代码

    // The normal version
    let normal = await ContentRepo.geBySkillIdWithSourceFiltered(
        [chosenSkillsArr[0].sid!],
        readContentIds,
        body.isVideoIncluded,
        true,
        true
    );
    
    
    // The code inside the parallel version:
    chosenSkillsArr.map(async (skill: IScrapeSkillDocument) => {
            const result = await ContentRepo.geBySkillIdWithSourceFiltered(
                [skill.sid!],
                readContentIds,
                body.isVideoIncluded,
                true,
                true
            );
        })
    
    [chosenSkillsArr[0].sid!], vs  chosenSkillsArr.map()
    
    

    对于并行版本,您将函数调用 (ContentRepo.geBySkillIdWithSourceFiltered) 放入循环中。这就是它变慢的原因。

    关于并行运行 Promise 的问题:

    Promise.allPromise.allSettled 一样等待多个承诺。它不关心它们解析的顺序,或者计算是否并行运行。它们都不能保证并发性,也不能相反。他们的任务只是确保处理传递给它的所有承诺。

    所以你不能手动保证promise执行的并行性

    这是一个真正的 interesting article 解释并行性和 Promise.All 以及浏览器 Nodejs API 与安装在您计算机上的 Nodejs API 在并行性方面的不同之处。

    以下是文章结论的摘录:

    JavaScript 运行时是单线程的。我们无法访问 JavaScript 中的线程。即使您拥有多核 CPU,您仍然无法使用 JavaScript 并行运行任务。但是,浏览器/NodeJS 使用 C/C++ (!) 他们可以访问线程。所以,他们可以实现并行。

    旁注:

    有一个细微的区别:

    1. Promise.all:仅在传递给它的所有 Promise 都解决时才解决,否则它将以第一个被拒绝的 Promise 错误而拒绝。

    2. Promise.allSettled:将始终使用包含已解决和已拒绝承诺信息的数组来解决。

    【讨论】:

    • 感谢您的出色解释。我尝试了 promise.allSettled 而不使用地图,而是手动提出请求。但这并没有改变任何东西。我想这可能是与 mongoDb 相关的问题。您对在 mongoDB 中并行运行查询有任何想法吗?
    • 我觉得不是MongoDB的问题,而是Nodejs的自治性和单线程性。假设有 10 个人跑向柜台买电影票。所有这些都从完全相同的地点和时间开始。有人会先到,柜台的工作人员会为先到的人服务。将计数器视为 CPU,这 10 个人是 10 个不同的承诺。无论谁先开始,或者离柜台有多近,柜台一次只能为一个人服务。 Nodejs 就是这种情况。
    • 非常感谢您的帮助,那么我应该寻找其他方法来提高查询性能。
    • 很高兴回答了您的问题。至少有一部分。请注意,如果可能,请避免将数据库调用置于循环中。
    猜你喜欢
    • 1970-01-01
    • 2021-09-23
    • 2020-12-12
    • 1970-01-01
    • 2020-07-15
    • 2017-10-17
    • 1970-01-01
    • 2020-06-11
    • 1970-01-01
    相关资源
    最近更新 更多