NestJS 接口慢怎么排查?耗时分析与瓶颈定位
以订单列表接口为例,结合请求追踪、CPU profile、事件循环延迟和连接池指标, 排查 NestJS 接口慢的原因,定位 DTO 转换、序列化、数据库查询与排队瓶颈,并用压测验证优化效果。
约六千七百字·读约二十分钟 · English
一个订单接口里的时间都花在哪了
看一个 NestJS 订单列表接口:Controller 调 Service,Service 再调 PricingService 和 Repository,外面还挂着 Guard、Pipe、Interceptor。接口一慢,很容易怀疑是这些层次拖累了性能。
看到 this.ordersService.list(),我们还不知道 list() 里面做了什么:可能只是读取内存中的数据,也可能要转换对象、查询数据库或调用远程服务。只数 Service 分了几层,判断不了瓶颈在哪。
排查时,我会先看请求在哪里计算、在哪里等,以及同一份数据处理了几遍。下面就用这个订单接口,把这些开销逐个说清楚。例子中的查询数量和耗时都是假设值,方便解释计算过程,不是线上压测结果。
多一层 Service,会多做一次依赖注入吗
应用启动时,Nest 会扫描模块和 provider,读取控制器与路由元数据,建立依赖关系,并创建默认作用域的实例。对于依赖树静态的 HTTP 路由,框架也会在路由注册阶段创建相应的处理包装,缓存部分参数与响应处理元数据。
请求到来后,执行的是已经注册好的 handler。
假设订单接口采用这样的分层:
OrdersController
→ OrdersService
→ PricingService
→ OrdersRepository
如果这些 provider 都是默认单例,而且没有 request-scoped 依赖使其依赖树变成非静态,this.ordersService.list() 就是在已注入对象上调用普通 JavaScript 方法。
Nest 不会因为调用进入 PricingService,就再做一轮构造函数解析;也不会因为进入 Repository,就重新搜索所有 Module。
这里的单例是指同一 provider 注册项共享实例。同一个类如果在多处重复注册,仍可能有多个实例。
装饰器留下的元数据,很多在初始化时就处理好了。如果怀疑反射开销,要检查请求执行时究竟在哪里读取了元数据。自定义 Guard 和 Interceptor 里的读取算在其中,启动时的模块扫描不算。
所以,启动慢就查模块初始化、构造函数和连接建立。服务预热后仍然慢,再查每次请求执行的代码。这两组耗时最好分开记录。
Guard、Pipe 和 Interceptor 花的时间
一次普通的成功请求,大致会经过这些步骤:
Middleware
→ Guards
→ Interceptors 入站
→ Pipes
→ Controller / Service
→ Interceptors 出站
→ HTTP adapter 序列化与发送
Guard 可以拒绝请求,Interceptor 可以跳过后续 handler,直接返回结果;因此请求不一定走完整条链。Exception Filter 处理未捕获异常,不在这条成功路径里。
调度、传参和处理异步结果都有开销,具体要看路由上挂了哪些组件。NestJS v11.1.6 的实现里,Interceptor 列表为空时会直接执行后续 handler,跳过拦截器链的 RxJS 包装。
同样是 Guard,读角色元数据、比较权限,和先查 Redis、再查权限数据库、最后调用身份服务,耗时会差很多。后一种情况里,“Guard 耗时高”还不够具体,需要继续拆出网络往返、连接获取和鉴权重试各用了多久。
Pipe 和 Interceptor 也要这样看。校验器可能在查数据库,日志拦截器可能在序列化大对象。只记整个组件的耗时,很容易把这些工作统统算到框架头上。
request scope 会多创建哪些对象
把 provider 设成 Scope.REQUEST 后,Nest 就需要为请求解析实例了。还是看这条调用链:
OrdersController → OrdersService → OrdersRepository
如果 OrdersService 被设为 request-scoped,依赖它的 Controller 也需要按请求解析。原本静态的 Repository 则不会因此自动变成 request-scoped。
Nest 所说的作用域冒泡,是从 request-scoped 依赖向依赖它的消费者传播,不是沿箭头把所有下游对象都变成每请求实例。
请求进入非静态路由后,Nest 会取得或创建 ContextId,解析当前 context 所需的依赖。相同 context 内的 request-scoped provider 可以复用,不是每调用一次方法都重新 new。
排查时要沿着依赖关系看一遍:一个 Scope.REQUEST,最后让多少对象变成了每请求创建?
只保存 request ID 的小对象,构造成本通常有限。如果 service 的构造函数还会创建 SDK wrapper、分配大缓存,或者带出一串依赖,每个请求都要重复付出这些成本。并发一高,短命对象多起来,GC 也会更忙。
也要检查全局注册的 APP_GUARD、APP_PIPE 和 APP_INTERCEPTOR。其中的 request-scoped 依赖可能影响大量控制器。
如果代码用到了 ModuleRef.resolve(),再检查它是否复用了 context:
// 对 scoped provider,不传 contextId 会创建独立的解析 context。
const isolated = await moduleRef.resolve(ScopedWorker);
// 前提:Nest 已为当前请求建立并关联 DI context。
const contextId = ContextIdFactory.getByRequest(req);
const requestWorker = await moduleRef.resolve(ScopedWorker, contextId);
循环里反复调用第一种写法,可能不断创建新的 scoped 子树。第二种写法的前提是请求已经关联了 DI context;尚未关联时,需要显式创建并复用 context。如果 provider 还要注入 REQUEST,也需要注册 request。getByRequest() 本身不会完成这一步。
TRANSIENT 按消费者分配实例,不会自动把单例消费者变成 request-scoped。被单例持有的 transient 实例,也可能一直存在到这个单例释放。
如果使用 request scope 只是为了传递 request ID 或 tenant ID,可以评估显式参数或 AsyncLocalStorage。但后者也有上下文传播和对象保留成本。它适合保存少量必要信息,不适合顺手塞进完整 request、实体集合和大响应。
DTO 转换和校验,可能反复遍历同一份数据
订单查询可能包含时间范围、状态列表、排序条件和嵌套筛选。对于批量操作,body 里还可能有大量订单明细。
ValidationPipe 处理这样的输入时,可能先把普通对象转成 DTO 实例,再递归校验。即使配置了 transform: false,这次转换也可能照常发生。
在 NestJS v11.0.0 中,参数的 metatype 进入 DTO 验证分支后,ValidationPipe 仍会先执行 plainToInstance(),再交给 class-validator。transform 主要影响验证成功后是否把 DTO 实例作为结果返回。某些 validator 选项下,还会再执行转回 plain object 的步骤。
如果 CPU profile 的热点里有 class-transformer,只关掉 transform 不一定管用。
校验要花多久,和字段数量、数组长度、嵌套深度、每字段约束数、未知字段数量都有关系。@ValidateNested()、each: true 和 whitelist 都可能增加需要遍历的范围。
例如,两个 DTO 都嵌套了三层,一个每层只有几个标量,另一个包含上千条明细,处理量差得很远。排查时记录数组长度和字段数量,比只看嵌套层数有用。
还要找重复处理:全局已经挂了校验 Pipe,路由或参数上又挂一次;同一请求体被多个 DTO 参数分别消费;进了 Service,再转一次对象。顺着数据走一遍,往往比只看某个 Pipe 的配置更容易发现问题。
失败路径也要测。超大数组、大量未知字段和复杂错误树,可能让非法请求同样消耗大量 CPU。stopAtFirstError 不表示整份 payload 在首个错误后立即停止,disableErrorMessages 也不是输入大小限制的替代品。
可以先限制输入大小、数组长度,合并重复的校验和转换。改完后检查未知字段处理和错误格式是否一致,这些也是接口行为的一部分。
返回结果还要映射和序列化
订单查询完成后,接口通常还会做实体映射、字段过滤和 JSON 输出。
启用 ClassSerializerInterceptor 时,返回值可能先经过 classToPlain();配置具体类型时,plain object 还可能先转实例,再转回 plain object。随后 HTTP adapter 才处理最终响应。普通 Express 对象响应最终还会经过 JSON 序列化。
筛选字段、转换类型,是在整理对象;把对象编码成 JSON 字符串,则是后面的另一遍处理。两者都可能遍历整份结果。
如果查询结果先被 Repository 映射一遍,Service 再 map 一遍,Controller 又复制一遍,最后经过类序列化器和 JSON.stringify(),同一批数据就可能被反复遍历。
检查这些映射时,要保留领域转换和脱敏,去掉没有实际用途的重复复制。查询阶段也可以少取一些字段,免得先传到应用里,再被映射代码丢掉。
大响应的代价还会影响其他请求。Node 的大 JSON parse/stringify 会占用事件循环,不是只有当前订单请求变慢。
只在 Controller 里计时,会漏掉返回后的这部分工作,包括后续序列化、GC 和写出等待。
await 后面的计算仍然占用主线程
这段代码里的数据库查询是异步的:
async function listOrders() {
const orders = await repository.findOrders();
return expensiveMappingAndSorting(orders);
}
但数据库返回以后,expensiveMappingAndSorting() 仍在当前 JavaScript 线程执行。async 只是改变函数返回与等待的方式,不会让这段计算自动进入另一个 CPU 核心。
同样,大数组排序、深拷贝、复杂正则、同步加密和同步压缩,都可能占住主线程。一个请求占用事件循环时,同进程其他请求的回调、校验和响应处理也要等待。
Promise.all() 能重叠独立 I/O 的等待,但不能让同一线程上的同步 JavaScript 并行。如果每个任务都先执行一段重计算再返回 Promise,这些计算依然要占用同一个线程。
大量 Promise 后续回调、递归 process.nextTick() 或过密的微任务,也会推迟 I/O 回调执行。不过,一次 await 并不一定对应一整轮 I/O 轮询,不能按 await 的数量估算延迟。
部分异步文件操作、crypto、dns.lookup() 和 zlib 使用 libuv 线程池。它们可能在线程池排队,而不是堵在 JavaScript 计算上。worker_threads 则适合评估 CPU 密集 JavaScript 的并行执行,通常应使用可复用、有界的 worker 池。
增大 libuv 池不会让普通 JavaScript 自动多核;新建 worker 也不是加速普通数据库 I/O 的默认方案。
短命对象多了,GC 也会影响延迟
DTO、数组映射、日志对象和 trace span 都会分配内存。短命对象多,垃圾回收工作就多;其中暂停主线程的阶段会推迟请求执行。看 GC 日志时要留意这些暂停,而不是把整个 GC 过程都算作停顿。
如果 after-GC heap 能回落,问题可能主要是分配压力。如果回收后的基线持续升高,就要查看是否有缓存、闭包、未完成 Promise 或 exporter 队列在保留对象。
RSS 增长本身不足以证明 JavaScript 堆泄漏。heapUsed、external 和 arrayBuffers 表示不同范围,arrayBuffers 又包含在 external 中,不能简单相加。
少用一点 CPU,为什么能多接不少请求
假设一次请求先做 0.8 ms 本地处理,同时发起一个 30 ms 数据库请求和一个无依赖的 18 ms HTTP 请求,最后做 1.7 ms 映射与序列化。这个简化模型假定两项 I/O 等待可以重叠,主线程 CPU 只计入前后两段本地工作。
这里用假设数字算一遍,不代表 NestJS 的实测性能。
没有排队时,关键路径为:
0.8 + max(30, 18) + 1.7 = 32.5 ms
主线程 CPU 服务需求则为:
0.8 + 1.7 = 2.5 ms/request
对于一个可以独占 CPU 核心的 JavaScript 主线程,忽略 GC、调度、容器节流和其他瓶颈后,理想 CPU 服务率上界为:
1000 / 2.5 = 400 请求/秒
这是主线程 CPU 的上限估算,不是整个接口的实测吞吐;如果数据库或 HTTP 下游先达到容量上限,实际吞吐还会更低。接近 CPU 上限时,也不能指望请求仍保持前面无排队的 32.5 ms 延迟。
它不是 1000 / 32.5。后者把不同请求之间可以重叠的 I/O 等待,当成了主线程被串行占用。
假如把前面的 0.8 ms 完全消除,单请求无排队延迟只从 32.5 ms 降到 31.7 ms;主线程每次请求只需 1.7 ms CPU,理想 CPU 上界因此变成 1000 / 1.7 ≈ 588 请求/秒。
这不到 1 ms 的延迟变化,用户可能感觉不到。但如果服务已经接近主线程的处理上限,省下的 CPU 时间就能用来处理其他请求。
如果只是把该 0.8 ms 片段提速 10 倍,它会变成 0.08 ms,整体关键路径是 0.08 + 30 + 1.7 = 31.78 ms,并不会让整条接口快 10 倍。这与 Amdahl 定律表达的限制一致:局部加速的整体收益,取决于该局部原本占了多少关键路径。
读 CPU profile 时也要记住这一点:它显示的是 CPU 时间占比。接口如果大部分时间在等数据库,某个函数占了 50% 的 CPU 时间,也不代表它占了 50% 的响应时间。
接近满载时,时间花在排队上
低负载下,多做一点同步工作,往往只是让当前请求多花一点时间。接近容量时,这段额外工作还会让后面的请求排队。
排队增多后,在途请求、Promise、DTO 和观测对象会一起积压。内存和 GC 压力可能进一步增加,超时与重试又可能放大下游负载。
因此,不能把低并发时测出的额外耗时,简单加到高并发 p99 上。排队系统在接近饱和时,等待并不按相同比例增长。
还可以用 Little 定律核对这些指标:平均在途请求数 = 平均吞吐 × 平均停留时间。同一稳定边界内,平均吞吐 300 请求/秒、平均停留 40 ms = 0.040 秒,对应平均在途请求数为 300 × 0.040 = 12。
这里必须使用平均值。不能把 p99 代进去,也不能拿网关的请求数去配 Controller 内部的耗时。
50 条订单,为什么查了 101 次数据库
假设订单列表返回 50 条数据。实现先查订单,再为每条订单分别查询一次客户和一次明细,没有批量合并或缓存,那么查询数是:
1 + 2 × 50 = 101 次
循环内使用串行 await,会把大量往返放到关键路径上;直接全部 Promise.all(),则可能迅速占满连接池。
这两种写法都要执行 101 次查询。要减少查询次数,可以批量读取或 JOIN;要减少返回数据和应用里的计算,可以只选需要的列,或把聚合放到数据库里做。TypeORM 的性能文档也讨论了 N+1 和不必要的实体构造。
合并查询以后,还得看看实际返回了多少行。如果 50 条订单每条都有 3 条明细和 2 笔支付,同时 JOIN 两个一对多关系,连接结果可以产生 50 × 3 × 2 = 300 行。ORM 最终可能整理回 50 个订单实体,但重复列的传输、读取和整形已经发生。
详情页确实需要完整关系时,JOIN 加实体映射可能很合适。列表页只需要编号、状态、金额和客户昵称时,先取稳定的一页订单,再批量补充必要信息,可能更容易控制数据量。
只读列表不需要完整实体时,可以评估 getRawMany()。它省掉一部分实体构造,但字段别名、类型、缺失值和 DTO 映射需要自己处理。从 getMany() 改过来时,尤其要检查原先的脱敏规则是否还在。
分页也要结合产品需求选。offset 方便跳到指定页,深页却可能要跳过大量数据;keyset 按稳定排序键继续往后读,适合连续翻页,不方便直接跳到第 N 页。带复杂 JOIN 时,最好看一下 ORM 实际生成的 SQL,它不一定只是加上 OFFSET/LIMIT。
SQL 不慢,接口也可能在等连接
数据库调用至少应拆成连接获取与查询执行。以 node-postgres 为例,池满时请求会等待可用 client,waitingCount 可以反映等待者数量。
扩容时还要算总连接数。假设有 6 个 Pod,每个各用一个上限为 10 的池,合起来最多就是 60 个连接,其他服务另算。
扩大池可能降低本地等待,也可能把队列搬到数据库、代理或锁等待处。应一起查看查询数、事务长度、获取连接时间和数据库容量,而不是只把 max 调大。
加缓存和重试之前,还有几处要检查
命中远程缓存后,请求仍要经历 key 构造、网络往返、反序列化,以及最后的 HTTP 序列化。没命中时,还要回源和写回。
订单按 tenant、用户或权限过滤时,缓存 key 就要区分这些条件。只用 URL,可能让两个用户拿到同一份结果;只加权限版本也未必够,同一权限版本下的用户仍可能只能看各自的订单。
缓存直接可返回的响应时,需要保留对应身份的字段可见性;缓存内部原始数据时,则必须在命中后继续授权和脱敏,不能直接透传。
TTL 从缓存写入时开始计时。如果旧请求在缓存失效后才把结果写回来,它还能再存一个 TTL。复制延迟和失效失败也会带来旧数据,所以 TTL 不能直接当作业务数据陈旧时间的上限。
热 key 过期时,可以通过请求合并或 single-flight 减少重复回源,同时限制等待数量和时间,防止等待请求不断积压。
GraphQL 的 N+1 可以考虑 request-local DataLoader。它可以合并合适调度窗口内的加载请求,但 batch 返回值必须与输入 key 的顺序和长度对应,缺失值也要明确表达。通常按请求创建 loader,避免把不同用户或 tenant 的结果放进同一个缓存。
Promise.race([work, timeout]) 也需要留意:timeout 先结束,只会让 race 返回,正在执行的数据库查询或 RPC 不会自动取消。
客户端收到超时后,如果原任务仍占用资源,再来一次重试,新旧任务可能同时运行。应检查底层是否支持取消、deadline 是否向下游传递,以及取消后的事务和幂等语义。
重试也不能层层叠加。网关、Nest 服务与数据库客户端分别重试,会在下游变慢时继续放大负载。应明确负责层级、限制预算,并采用带随机扰动的退避。
日志和追踪本身也要做计算
深调用链容易放大观测成本。如果每层都记录完整参数、返回值和堆栈,日志输出之前就已经付出了格式化、脱敏、对象分配和 JSON 化的成本。
stdout/stderr 是否同步,取决于平台和连接目标。要测日志输出开销,尽量使用和生产相同的日志管道。
如果关闭日志后明显变快,再分别测记录构造和输出。前者可以尝试少记字段、降低采样率,后者可以比较批量输出等方式。这样才知道收益来自哪里。
监控指标标签尤其要控制基数。模板路由、状态码类和响应大小桶有利于聚合;把订单 ID、用户 ID 和原始 URL 都变成指标标签,会增加成本。trace 属性可以按诊断需要保留标识,但也应考虑采样、敏感信息和数据量。
什么时候值得换 Fastify
Fastify 可以改变 HTTP 路由、解析和响应路径,但不会自动移除 Nest 的 Guard、Pipe、类验证和类序列化。
它的编译验证与响应序列化能力需要实际路由注册相应 JSON Schema。普通 DTO、class-validator 装饰器和 Swagger 文档,不会自动等同于 Fastify 的运行时 route schema。
数据库、DTO 转换和大响应映射如果占了大部分开销,换 adapter 的收益就可能有限。轻量接口的 HTTP 处理占比较高时,才更值得对比 adapter。
比较时还必须确认认证、输入限制、错误格式和中间件行为一致。少做了验证的一方更快,不能证明它在相同功能下更快。
先确认计时覆盖了哪一段
“接口耗时”可能指客户端读完响应,也可能只是 Controller 执行时间。比较数据之前,先确认计时从哪里开始、在哪里结束。
Nest Interceptor 入站发生在 Guard 之后,因此它不包含此前的 middleware 和 Guard 耗时。被 Guard 拒绝的请求,也不会进入后续 Interceptor。
RxJS finalize() 表示 Observable 完成、报错或取消订阅,不表示 HTTP 客户端已经收完响应。Node 的 finish 事件也只是表示最后的响应片段已交给操作系统。
对于普通 JSON 接口,可以分别观察客户端完整读完、服务端最早 middleware、Interceptor、下游 span 与 finish。流式响应、SSE 和下载则需要协议专用的完成定义。
父子 span 也不能直接相加。父 span 已包含子 span,并发子 span 还可能重叠。计算父级独占时间时,应扣除子区间的并集,而不是无条件逐项相减。
把 CPU、事件循环和连接等待对起来看
monitorEventLoopDelay() 能反映事件循环延迟,输出单位是纳秒;eventLoopUtilization() 反映事件循环 active/idle,不等于进程 CPU 利用率。
进程 CPU 高时,要确认是主线程、原生线程还是其他工作在消耗资源。进程 CPU 不高时,也不能排除 CPU 配额节流、同步等待或下游排队。容器 CPU limit 的节流会影响实际经过时间。
需要定位 CPU、分配与 GC 时,可以在隔离测试副本上分别使用:
mkdir -p diagnostics
# 每轮只启用一种诊断,并与无 profiler 基线对照。
node --cpu-prof --cpu-prof-dir=diagnostics dist/main.js
node --heap-prof --heap-prof-dir=diagnostics dist/main.js
node --trace-gc dist/main.js > diagnostics/gc.log 2>&1
CPU profile 回答的是 CPU 花在哪里,不会把数据库等待变成热点函数。诊断工具本身也有开销。尤其 heap snapshot 可能暂停进程并增加内存压力,不宜在没有余量的生产实例上作为默认动作。
下面这些现象可以帮助缩小范围,再用 profile 或单变量实验验证:
| 观察到的现象 | 优先核对什么 | 候选改动 |
|---|---|---|
| CPU、事件循环延迟与 p99 同时上升 | JSON、DTO 转换、排序、同步日志是否占据热点 | 限制数据规模,减少重复遍历与序列化 |
事件循环平稳,连接获取时间与 waitingCount 上升 | 查询次数、事务长度、各副本的连接预算 | 批量查询,缩短事务,再评估池大小 |
| 大 body 或大响应特别慢 | 数组长度、嵌套对象数、实际响应字段 | 限制输入规模,按需投影,分组压测 |
| 请求量上升时对象分配和 GC 增多 | request scope 影响范围、重复映射、日志和追踪对象 | 缩小每请求实例范围,减少不必要的分配 |
| 超时后下游负载仍在增加 | 原任务是否取消,多层重试是否叠加 | 传递 deadline,明确取消语义与重试预算 |
| 只有轻量接口仍受 HTTP 路径限制 | 在相同认证、校验、响应契约下比较 adapter | 再评估 Fastify 与进程容量 |
压测时,让请求按计划持续进来
固定 Node、Nest、adapter 和 ORM 版本,同时固定数据量、索引、权限结果、请求分布、响应字段、日志配置、Pod 数与资源配额。
冷启动、首批请求和预热后的数据分开记录。每次只改一个变量,并保持鉴权、校验和响应字段一致,否则两轮压测做的就不是同一份工作。
除了固定并发的 closed-loop,也应考虑预设到达率的 open-loop。前者在服务变慢时会等响应再发下一次请求,自动降低发送量,可能掩盖过载。后者需要关注计划发送时刻、发生器排队和客户端资源,避免漏掉 coordinated omission 所隐藏的尾延迟。
逐档提高到达率,同时记录成功吞吐、错误、超时、延迟分布、在途请求和下游队列。超时样本也要计入结果;只看成功请求的平均值,会漏掉最慢的那部分请求。
真正动手时,从哪一处开始
如果现在就要排查这个订单接口,我会先抓一份慢请求 trace,确认时间主要花在数据库往返、连接等待,还是本地处理上。查了 101 次数据库,就先减少查询次数;热点在 DTO 或 JSON,就看数据量和重复遍历;连接池排队,就把事务长度和总连接数一起查清楚。
每改一处,用相同的输入、权限和响应字段重跑一遍,比较成功吞吐、p99 和错误率。单次请求快了多少要看,接近满载时能否少排队也要看。等这些重复工作处理完,仍然受限于 HTTP 层或主线程容量,再试 Fastify、增加进程或 worker 池。
至于 Service 要不要少分一层,可以按代码是否好维护来决定。只有 profile 指向那一层的额外工作时,才有必要为了性能去改它。
相关内容:NestJS 新手入门 · NestJS 与 Express 的项目选型
参考资料
源码对应 NestJS v11.0.0、v11.1.6,Node API 主要参考 v22.14.0。文档页面可能继续更新,排查时请对照项目使用的版本。
- NestJS v11.1.6 RouterExplorer
- NestJS v11.1.6 RouterExecutionContext
- NestJS request lifecycle
- NestJS v11.1.6 InterceptorsConsumer
- NestJS injection scopes
- NestJS v11.1.6 InstanceWrapper
- NestJS ModuleRef and scoped resolution
- NestJS v11.0.0 ValidationPipe
- class-transformer v0.5.1 TransformOperationExecutor
- class-validator v0.14.1 ValidationExecutor
- NestJS v11.0.0 ClassSerializerInterceptor
- Express v4.21.2 response implementation
- Node.js: Don’t block the Event Loop or the Worker Pool
- Node.js v22.14.0 command-line options and diagnostics
- Node.js worker_threads
- Node.js v22.14.0 AsyncLocalStorage
- Node.js tracing garbage collection
- Node.js v22.14.0 process CPU, memory and I/O
- Cornell Virtual Workshop: Amdahl’s Law
- Linda Green: Queueing Theory and Modeling
- John D. C. Little: Little’s Law as Viewed on Its 50th Anniversary
- TypeORM performance optimization with QueryBuilder
- TypeORM select queries, entities, raw results and pagination
- node-postgres Pool API
- node-postgres Pool Sizing
- Redis cache-aside and request coalescing
- DataLoader batching and request-local caching
- MDN Promise.race
- Google SRE: Addressing Cascading Failures
- NestJS Fastify performance adapter
- Fastify validation and serialization
- NestJS v11.0.0 FastifyAdapter route registration
- RxJS finalize operator
- Node.js v22.14.0 HTTP response events
- Node.js v22.14.0 performance hooks
- Kubernetes resource requests, limits and CPU throttling
- wrk2 and coordinated omission
Mttao GitHub ↗
探索技术与生活的智慧