Skip to content

perf: optimize HTTP routing hot paths - #37

Merged
Upd4ting merged 3 commits into
mainfrom
perf/http-routing-hot-paths
Aug 21, 2026
Merged

perf: optimize HTTP routing hot paths#37
Upd4ting merged 3 commits into
mainfrom
perf/http-routing-hot-paths

Conversation

@Upd4ting

@Upd4ting Upd4ting commented Aug 21, 2026

Copy link
Copy Markdown
Member

Summary

  • precompute literal prefix/suffix guards for dynamic route segments so large sibling tables skip impossible regular expressions while retaining the existing ordered trie walk
  • keep parameter providers and modifiers synchronous until the first actual thenable, then resume the remaining chain with the same controller receiver
  • retain per-request middleware priority sorting because the semantics-preserving bucket candidate regressed the full listener workload

Review findings

Dynamic sibling lookup: included

The router still checks static routes first, dynamic routes in registration order, and catch-all routes last. The new guards are necessary conditions derived from each existing compiled pattern; regex matching, captures, recursive backtracking, and handler selection remain unchanged. Existing lifecycle tests plus focused prefix/suffix extraction coverage exercise registration, unregistration, hot reload, ambiguity/backtracking, and catch-all behavior.

Node 22.19.0 microbenchmark, three-segment routes shaped as /bench/:value-N/done; values are µs/lookup:

siblings position before after change
20 early 1.195 0.266 -77.7%
20 middle 2.926 0.554 -81.1%
20 late 2.128 0.685 -67.8%
20 miss 1.662 0.258 -84.5%
100 early 0.541 0.343 -36.6%
100 middle 7.463 0.994 -86.7%
100 late 14.051 2.302 -83.6%
100 miss 12.133 0.870 -92.8%
500 early 0.304 0.323 +6.3%
500 middle 29.552 3.278 -88.9%
500 late 67.399 6.395 -90.5%
500 miss 59.592 3.864 -93.5%

The stressed early-match cost is +19 ns because every candidate now checks two precomputed strings. Realistic small tables and every non-early fan-out case improve materially; no radix structure or lookup cache is introduced.

A keep-alive HTTP benchmark with 50 connections and 5,000 failed requests against 500 sibling routes produced median throughput of 3,570 requests/s before and 12,661 requests/s after (+254.6%, three runs).

Middleware priority sorting: excluded

The eight-listener reproduction matched prefix handlers across four trie depths and mixed all five priorities. Baseline lookup plus execution was 1.774 µs/request. Removing sorting reached 0.382 µs, but changes priority semantics and is only an invalid upper bound.

A stable five-priority bucket gather preserved cross-depth and equal-priority order, method/any ordering, and lifecycle behavior, but measured 2.036 µs/request (+14.8%) due to bucket allocation/flattening. Registration-time sorting alone cannot preserve global order when prefix, postfix, or monitor handlers match at different trie depths. The candidate was therefore removed; production sorting and all existing prefix/postfix/monitor semantics remain unchanged.

Synchronous parameter modifiers: included

The compiled resolver now loops synchronously and only creates a promise after observing a provider or modifier thenable. It reads custom thenables once, invokes them with their original receiver, preserves rejection flow, and resumes at the next unprocessed modifier.

Node 22.19.0 results:

chain before after change
sync provider + 2 sync modifiers 1.749 µs 0.074 µs -95.8%
async provider + 2 async modifiers 0.622 µs 0.612 µs -1.6%
sync provider + async/sync modifiers 0.476 µs 0.223 µs -53.2%

A full HTTP listener/controller benchmark with one sync provider and two sync modifiers improved from 10,911 to 14,570 requests/s (+33.5%, 10,000 requests, 50 keep-alive connections).

Verification

  • pnpm lint — passed (27 files)
  • pnpm build — passed
  • focused compiled tests — 32 passing, covering routing precedence/backtracking/lifecycle, sync/async/thenable controller chains, priorities, prefix/postfix/monitor execution, and WebSocket order
  • complete direct Mocha run — 75 passing; the combined run's pre-existing listen.test after hook timed out, while listen.test alone passed all 12 tests in 42 ms
  • pnpm test cannot launch in this checkout because the locked dependencies do not provide the configured ajs executable (sh: ajs: not found)
  • Node 22.19.0 --trace-opt --trace-deopt HTTP run — findHandlers, executeRequest, and processRequest all optimized with TurboFan; no deoptimization named any of those functions

Benchmark harnesses were temporary and are not tracked.

Greptile Summary

This PR optimizes HTTP routing and controller parameter resolution while preserving existing matching and execution semantics.

  • Adds precomputed literal prefix and suffix guards before dynamic-route regular-expression evaluation.
  • Keeps provider and modifier chains synchronous until an actual thenable is encountered.
  • Adds focused coverage for dynamic-route filtering, synchronous modifiers, mixed asynchronous chains, and stateful thenables.

Confidence Score: 5/5

The PR appears safe to merge, with no actionable correctness or security issues identified.

The new routing guards are necessary conditions of the existing regular expressions, and the resolver optimization retains modifier ordering, controller binding, thenable handling, and request error propagation.

Important Files Changed

Filename Overview
src/implementations/api/index.ts Reworks parameter modifier resolution to avoid promises for synchronous chains while preserving thenable assimilation, receiver binding, ordering, and error propagation.
src/server.ts Adds necessary-condition prefix and suffix checks that reduce regular-expression work without changing route matching or sibling precedence.
src/test/controller-resolution.test.ts Adds coverage for synchronous modifier execution, mixed thenable chains, single-read then getters, and modifier ordering.
src/test/routing-parameters.test.ts Adds focused coverage for prefixed and suffixed dynamic segments and rejection of impossible candidates.

Reviews (1): Last reviewed commit: "perf(api): preserve synchronous paramete..." | Re-trigger Greptile

Security dependency floor

This PR now requires @antelopejs/interface-api >=0.0.12 <1.0.0 and resolves the root runtime dependency to 0.0.12. Version 0.0.12 contains the bounded request-body handling and HTTP 413 response fix shipped in interface-api PR #15, preventing API consumers from resolving the vulnerable 0.0.11 request-body implementation. The lockfile was regenerated with the repository-declared pnpm 10.6.5; no other dependencies or source files changed in this update.

@Upd4ting
Upd4ting merged commit 31ff770 into main Aug 21, 2026
2 checks passed
@Upd4ting
Upd4ting deleted the perf/http-routing-hot-paths branch August 21, 2026 14:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant