首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >修复简单的"get“查询在云函数中需要10秒

修复简单的"get“查询在云函数中需要10秒
EN

Stack Overflow用户
提问于 2021-08-05 07:15:39
回答 1查看 218关注 0票数 0

我有一个云函数,它通过doc运行一个简单的防火墙查询,函数日志显示在运行查询时延迟了7秒。下面是函数代码:

代码语言:javascript
复制
 exports.cancelUnpaidOrder = functions.https.onCall(async (orderId, context) => {
  console.log("in cancelUnpaidOrder");
  const uid = context.auth.uid;

  console.log("awaiting get order doc");
  const orderSnap = await admin
    .firestore()
    .collection("order")
    .doc(orderId)
    .get();
  console.log("getting order doc data");
  const order = orderSnap.data();
  console.log("asserting");
  assert.ok(order.userId == uid && !order.paid);
  console.log("awaiting update order doc");
  await admin.firestore().collection("order").doc(orderId).update({
    canceled: true,
    cancelMsg: "canceled by user before pay",
    open: false,
  });
  console.log("finished cancelUnpaidOrder");
});

下面是日志,注意9:52:299:52:36之间很长的查询时间

代码语言:javascript
复制
9:52:37.003 Function execution took 9292 ms, finished with status code: 200
9:52:37.001  finished cancelUnpaidOrder
9:52:36.487  awaiting update order doc
9:52:36.487  asserting
9:52:36.486  getting order doc data
9:52:29.558  awaiting get order doc
9:52:29.558  in cancelUnpaidOrder
9:52:29.558  Callable request verification passed
9:52:27.712  Function execution started

功能区: us-central1 1。固定恢复区: nam5 (美国中部)

谢谢。

编辑:修复文档大小为6.57K

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-08-09 08:37:35

请注意,就像云函数实例Admin一样,它经历了一个冷启动。虽然您的代码可能只是一个简单的查询,但在执行查询之前,first API请求还会在服务帐户客户端和身份验证服务器之间触发身份验证令牌的交换。对于任何给定的函数实例,这种性能影响应该只发生一次,除非该特定实例在重新身份验证时能够存活足够长的时间(通常是一个小时)。如果多个实例被激发以吸收需求,那么每个实例只会有一次性能上的冲击。

最小数据传输

可以通过对返回的数据使用字段掩码来帮助函数的性能。对于Admin,这是使用select()完成的。为您的文档使用字段掩码将其大小从6.57KB降至仅51 b。

代码语言:javascript
复制
exports.cancelUnpaidOrder = functions.https.onCall(async (orderId, context) => {
  console.log("in cancelUnpaidOrder");
  const uid = context.auth.uid;

  const orderRef = admin.firestore() // <- new: store reference in variable
    .collection("order")
    .doc(orderId);

  console.log("awaiting get order doc");
  console.time("getDoc");
  const orderSnap = await orderRef
    .select('userId', 'paid') // <- new: only fetch userId and paid, ignore other fields
    .get();
  console.timeEnd("getDoc"); // <-- new: calculate timings locally

  console.log("getting order doc data");
  const order = orderSnap.data();
  console.log("asserting");
  assert.ok(order.userId === uid && !order.paid); // <- new: use === for user ID checks!
  console.log("awaiting update order doc");
  console.time("setDoc");
  await orderRef.update({
    canceled: true,
    cancelMsg: "canceled by user before pay",
    open: false,
  });
  console.timeEnd("setDoc"); // <-- new: calculate timings locally
  console.log("finished cancelUnpaidOrder");
});

注: canceled可能应该更正为cancelled

处理错误

与其使用assert.ok,不如使用一个助手函数,它执行相同的任务,但是抛出一个HttpsError,这样您的前端就可以接收到一个有意义的错误,而不是一个错误代码为“内部”的错误。以类似的方式,也可能希望将Firestore调用转换为抛出HttpsError

这些辅助函数如下所示:

代码语言:javascript
复制
// Typescript: function assertOk(condition: unknown, message?: string): asserts condition {
function assertOk(condition, message = undefined) {
  if (!condition) {
    throw new functions.https.HttpsError(
      "failed-precondition",
      message || "failed-precondition"
    );
  }
}

function throwAsHttpsError(error, message = undefined) {
  let err, errCode = (!!error && typeof error === "object" && error.code) || "internal";
  try {
    // attempt to use original error's code
    err = new functions.https.HttpsError(
      errCode,
      message || "INTERNAL"
    );
  } catch {
    // unexpected error code, use fallback code of "internal"
    err = new functions.https.HttpsError(
      "internal",
      message || errCode || "INTERNAL"
    );
  }
  throw err;
}

适用这些原则的结果如下:

代码语言:javascript
复制
exports.cancelUnpaidOrder = functions.https.onCall(async (orderId, context) => {
  console.log("in cancelUnpaidOrder");
  const uid = context.auth.uid;

  const orderRef = admin.firestore()
    .collection("order")
    .doc(orderId);

  console.log("awaiting get order doc");
  console.time("getDoc");
  const orderSnap = await orderRef
    .select('userId', 'paid')
    .get()
    .catch(e => throwAsHttpsError(e, "failed to read order"));
  console.timeEnd("getDoc");

  console.log("getting order doc data");
  const order = orderSnap.data();

  console.log("asserting");
  assertOk(order.userId === uid, "User mismatch");
  assertOk(!order.paid, "Order already paid");

  console.log("awaiting update order doc");
  console.time("setDoc");
  await orderRef
    .update({
      canceled: true,
      cancelMsg: "canceled by user before pay",
      open: false,
    })
    .catch(e => throwAsHttpsError(e, "failed to cancel order"));
  console.timeEnd("setDoc");

  console.log("finished cancelUnpaidOrder");
});

测试时间

在测试时,您应该确保测试两次调用该函数。第一次是测试一个新的云函数实例的冷启动性能。第二次是尝试在第一个调用空闲后重用第一个调用所使用的同一个实例。无论是打电话还是不打电话,都可能经历一个冷酷的开始.

代码语言:javascript
复制
const cancelUnpaidOrder = firebase.functions().httpsCallable('cancelUnpaidOrder');

function testCall(orderId) {
  const tag = "Call for Order #" + orderId;
  console.time(tag);
  return cancelUnpaidOrder(orderId1)
    .then(() => {
      console.timeEnd(tag);
      console.log(tag + ": success");
    }, (err) => {
      console.timeEnd(tag);
      console.error(tag + ": error -> ", err);
    });
}

const orderId1 = /* first test ID to be cancelled */;
const orderId2 = /* second test ID to be cancelled */;

// attempt to invoke a cold-start
testCall(orderId1);

// attempt to catch the cooled down instance, timings may need adjustment
setTimeout(() => testCall(orderId2), 15000);
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68662152

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档