我有一个云函数,它通过doc运行一个简单的防火墙查询,函数日志显示在运行查询时延迟了7秒。下面是函数代码:
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:29和9:52:36之间很长的查询时间
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
发布于 2021-08-09 08:37:35
请注意,就像云函数实例Admin一样,它经历了一个冷启动。虽然您的代码可能只是一个简单的查询,但在执行查询之前,first API请求还会在服务帐户客户端和身份验证服务器之间触发身份验证令牌的交换。对于任何给定的函数实例,这种性能影响应该只发生一次,除非该特定实例在重新身份验证时能够存活足够长的时间(通常是一个小时)。如果多个实例被激发以吸收需求,那么每个实例只会有一次性能上的冲击。
最小数据传输
可以通过对返回的数据使用字段掩码来帮助函数的性能。对于Admin,这是使用select()完成的。为您的文档使用字段掩码将其大小从6.57KB降至仅51 b。
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。
这些辅助函数如下所示:
// 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;
}适用这些原则的结果如下:
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");
});测试时间
在测试时,您应该确保测试两次调用该函数。第一次是测试一个新的云函数实例的冷启动性能。第二次是尝试在第一个调用空闲后重用第一个调用所使用的同一个实例。无论是打电话还是不打电话,都可能经历一个冷酷的开始.
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);https://stackoverflow.com/questions/68662152
复制相似问题