We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
最近其他团队交接过来一个 Node.js 服务, 在完成该服务的技术架构迁移后发现了一个奇怪的现象, 表现是代码发布上线后第一次请求该服务 100% 会超时 (延时超过 2000ms), 第二次及以后的请求恢复正常 (延时稳定在 600ms 左右)。
首先查看 Cat 延时监控来确认是上游依赖的接口超时还是 Node.js 代码运行卡住导致的超时。插曲是第一次请求超时的记录却丢失了, 花了一些时间先排查解决了基础上报库的 bug。
非真实监控图, 示例图来自 cat/wiki/transaction
记录丢失问题修复后发现上游依赖的接口的第一次耗时也就 600ms 左右, 那超时的原因只能是 Node.js 的某些代码存在严重的性能问题, 卡住了 JS 的主线程 1400ms +。
接着对大量可疑的代码进行了埋点, 最终定位到如下仅一行代码 ❌ this.stack = new Error().stack; 运行耗时约 1600ms
this.stack = new Error().stack;
export function TestError(message = 'TestError') { this.name = 'TestError'; this.message = message; this.errorCode = 100; this.stack = new Error().stack; }
当我给出这个排查结果时大家都是不敢相信, 确实读取一个 Error 对象的 stack 属性能有这么大的能量 ?
紧接着我本地运行该 Node.js 服务来复现, 发现自己的 M1 电脑第一次运行这行代码也花费了约 1200ms, 第二次及以后基本就是 1ms 或者 0.x ms 可以忽略不计了。
定位 JS 代码性能比较好用的就是火焰图了, 我们可以使用下面的命令通过调试模式启动该服务
node --inspect you-app.js
随着调用堆栈的展示, Error.stack 的真相才慢慢浮出水面
从调用堆栈我们发现了当运行 Error.stack 这行代码时,
最后我们再说一下运行 Error.stack 代码后为什么会先运行 Node.js 源码中的 prepareStackTrace 函数 ?
// https://github.com/nodejs/node/blob/v12.13.1/lib/internal/bootstrap/node.js#L314C3-L314C51 setPrepareStackTraceCallback(prepareStackTrace);
// https://github.com/nodejs/node/blob/v12.13.1/src/node_errors.cc#L819 void SetPrepareStackTraceCallback(const FunctionCallbackInfo<Value>& args) { Environment* env = Environment::GetCurrent(args); CHECK(args[0]->IsFunction()); env->set_prepare_stack_trace_callback(args[0].As<Function>()); }
// https://github.com/nodejs/node/blob/v12.13.1/src/api/environment.cc#L199 isolate->SetPrepareStackTraceCallback(PrepareStackTraceCallback);
由于 v8 的 SetPrepareStackTraceCallback API 提供的底层能力让用户可以设置读取 Error.stack 属性时的回调, 相当于 JS Object 设置 getter 函数, 使得一些库比如 source-map-support 复写了 Error.prepareStackTrace 的值将可能导致预期外的性能问题。
更多关于 v8 Stack trace API 的内容见: Customizing stack traces
The text was updated successfully, but these errors were encountered:
No branches or pull requests
问题背景
最近其他团队交接过来一个 Node.js 服务, 在完成该服务的技术架构迁移后发现了一个奇怪的现象, 表现是代码发布上线后第一次请求该服务 100% 会超时 (延时超过 2000ms), 第二次及以后的请求恢复正常 (延时稳定在 600ms 左右)。
问题排查
Cat Transaction
首先查看 Cat 延时监控来确认是上游依赖的接口超时还是 Node.js 代码运行卡住导致的超时。插曲是第一次请求超时的记录却丢失了, 花了一些时间先排查解决了基础上报库的 bug。
记录丢失问题修复后发现上游依赖的接口的第一次耗时也就 600ms 左右, 那超时的原因只能是 Node.js 的某些代码存在严重的性能问题, 卡住了 JS 的主线程 1400ms +。
Error.stack
接着对大量可疑的代码进行了埋点, 最终定位到如下仅一行代码 ❌
this.stack = new Error().stack;
运行耗时约 1600ms当我给出这个排查结果时大家都是不敢相信, 确实读取一个 Error 对象的 stack 属性能有这么大的能量 ?
紧接着我本地运行该 Node.js 服务来复现, 发现自己的 M1 电脑第一次运行这行代码也花费了约 1200ms, 第二次及以后基本就是 1ms 或者 0.x ms 可以忽略不计了。
Performance
定位 JS 代码性能比较好用的就是火焰图了, 我们可以使用下面的命令通过调试模式启动该服务
随着调用堆栈的展示, Error.stack 的真相才慢慢浮出水面
Call Tree
从调用堆栈我们发现了当运行 Error.stack 这行代码时,
问题探索
最后我们再说一下运行 Error.stack 代码后为什么会先运行 Node.js 源码中的 prepareStackTrace 函数 ?
问题结论
由于 v8 的 SetPrepareStackTraceCallback API 提供的底层能力让用户可以设置读取 Error.stack 属性时的回调, 相当于 JS Object 设置 getter 函数, 使得一些库比如 source-map-support 复写了 Error.prepareStackTrace 的值将可能导致预期外的性能问题。
更多关于 v8 Stack trace API 的内容见: Customizing stack traces
The text was updated successfully, but these errors were encountered: