nestjs

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。文档页面可能继续更新,排查时请对照项目使用的版本。

  1. NestJS v11.1.6 RouterExplorer
  2. NestJS v11.1.6 RouterExecutionContext
  3. NestJS request lifecycle
  4. NestJS v11.1.6 InterceptorsConsumer
  5. NestJS injection scopes
  6. NestJS v11.1.6 InstanceWrapper
  7. NestJS ModuleRef and scoped resolution
  8. NestJS v11.0.0 ValidationPipe
  9. class-transformer v0.5.1 TransformOperationExecutor
  10. class-validator v0.14.1 ValidationExecutor
  11. NestJS v11.0.0 ClassSerializerInterceptor
  12. Express v4.21.2 response implementation
  13. Node.js: Don’t block the Event Loop or the Worker Pool
  14. Node.js v22.14.0 command-line options and diagnostics
  15. Node.js worker_threads
  16. Node.js v22.14.0 AsyncLocalStorage
  17. Node.js tracing garbage collection
  18. Node.js v22.14.0 process CPU, memory and I/O
  19. Cornell Virtual Workshop: Amdahl’s Law
  20. Linda Green: Queueing Theory and Modeling
  21. John D. C. Little: Little’s Law as Viewed on Its 50th Anniversary
  22. TypeORM performance optimization with QueryBuilder
  23. TypeORM select queries, entities, raw results and pagination
  24. node-postgres Pool API
  25. node-postgres Pool Sizing
  26. Redis cache-aside and request coalescing
  27. DataLoader batching and request-local caching
  28. MDN Promise.race
  29. Google SRE: Addressing Cascading Failures
  30. NestJS Fastify performance adapter
  31. Fastify validation and serialization
  32. NestJS v11.0.0 FastifyAdapter route registration
  33. RxJS finalize operator
  34. Node.js v22.14.0 HTTP response events
  35. Node.js v22.14.0 performance hooks
  36. Kubernetes resource requests, limits and CPU throttling
  37. wrk2 and coordinated omission
Mttao

Mttao GitHub ↗

探索技术与生活的智慧

相关文章

/ 评论