Parse.Object.saveAll 与单独储蓄相比有任何成本优势吗?

Is there any cost advantage of Parse.Object.saveAll vs. saving individually?

Parse JS SDK 提供了一种Parse.Object.saveAll()方法,可以通过一个命令保存多个对象。

ParseServerRESTController.js来看,似乎每个对象都是单独保存的:

if (path === '/batch') {
    let initialPromise = Promise.resolve();
    if (data.transaction === true) {
        initialPromise = config.database.createTransactionalSession();
    }
    return initialPromise.then(() => {
        const promises = data.requests.map(request => {
            return handleRequest(
                request.method,
                request.path,
                request.body,
                options,
                config
            ).then(
                response => {
                    return {
                        success: response
                    };
                },
                error => {
                    return {
                        error: {
                            code: error.code,
                            error: error.message
                        },
                    };
                }
            );
        });
        return Promise.all(promises).then(result => {
            if (data.transaction === true) {
                if (
                    result.find(resultItem => typeof resultItem.error === 'object')
                ) {
                    return config.database.abortTransactionalSession().then(() => {
                        return Promise.reject(result);
                    });
                } else {
                    return config.database.commitTransactionalSession().then(() => {
                        return result;
                    });
                }
            } else {
                return result;
            }
        });
    });
}

似乎 saveAll 只是一个方便的包装器,用于单独保存每个对象,因此它似乎仍然对 n 个对象发出 n 数据库请求。

saveAll 相对于单独保存每个对象 在 Cloud Code 中没有成本优势(性能、网络流量等)是正确的吗?

我可以告诉你,答案是 Parse.Object.saveAllParse.Object.destroyAlldefault in batches of 20 个对象批量请求。但为什么要相信我的话呢?让我们来测试一下!

打开详细日志记录,然后 运行 以下内容:

const run = async function run() {
  const objects = [...Array(10).keys()].map(i => new Parse.Object('Test').set({i}));
  await Parse.Object.saveAll(objects);

  const promises = objects.map(o => o.increment('i').save());
  return Promise.all(promises);
};

run()
  .then(console.log)
  .catch(console.error);

这是解析服务器日志的输出(我已经 运行 对其进行了分类,但它应该足以说明发生了什么):

verbose: REQUEST for [POST] /parse/batch: { // <--- note the path
  "requests": [ // <--- an array of requests!!!
    {
      "method": "POST",
      "body": {
        "i": 0
      },
      "path": "/parse/classes/Test"
    },

    ... skip the next 7, you get the idea

    {
      "method": "POST",
      "body": {
        "i": 9
      },
      "path": "/parse/classes/Test"
    }
  ]
} 
.... // <-- remove some irrelevent output for brevity.
verbose: RESPONSE from [POST] /parse/batch: { 
  "response": [
    {
      "success": {
        "objectId": "szVkuqURVq",
        "createdAt": "2020-03-05T21:25:44.487Z"
      }
    },
    ...
    {
      "success": {
        "objectId": "D18WB4Nsra",
        "createdAt": "2020-03-05T21:25:44.491Z"
      }
    }
  ]
} 
...

// now we iterate through and there's a request per object.
verbose: REQUEST for [PUT] /parse/classes/Test/szVkuqURVq: {
  "i": {
    "__op": "Increment",
    "amount": 1
  }
} 
...
verbose: REQUEST for [PUT] /parse/classes/Test/HtIqDIsrX3: {
  "i": {
    "__op": "Increment",
    "amount": 1
  }
} 
// and the responses...
verbose: RESPONSE from [PUT] /parse/classes/Test/szVkuqURVq: {
  "response": {
    "i": 1,
    "updatedAt": "2020-03-05T21:25:44.714Z"
  }
}
...

在核心管理器代码中,您确实正确地识别出我们正在为每个对象向数据存储发出请求(即 MongoDB),这是必要的,因为对象可能具有关系或指针待处理,可能需要额外调用数据存储。

但是!解析服务器和数据存储之间的调用通常使用二进制格式通过非常快的网络进行,而客户端和解析服务器之间的调用是 JSON 并且通过通常更慢的连接进行更长的距离。

您可以在核心管理器代码中看到另一个潜在优势,即批处理是在事务中完成的。