Skip to content

feat(core): per-run event-log LRU cache to avoid full reloads on warm resume#2210

Draft
TooTallNate wants to merge 1 commit into
mainfrom
nr/event-log-lru-cache
Draft

feat(core): per-run event-log LRU cache to avoid full reloads on warm resume#2210
TooTallNate wants to merge 1 commit into
mainfrom
nr/event-log-lru-cache

Conversation

@TooTallNate
Copy link
Copy Markdown
Member

@TooTallNate TooTallNate commented Jun 1, 2026

Summary

Adds a process-wide LRU cache keyed by runId so warm function instances delta-fetch only the new events on resume instead of reloading the full event log from event 0 on every invocation.

Long, step-heavy runs that resume frequently (once per step) previously re-read a growing log each time — O(N²) total event reads over the run's life. With this change, the warm-replay path becomes O(delta), converting to O(N) overall.

What changed

  • New: packages/core/src/runtime/event-cache.ts — module-level LRU bounded by approximate bytes (~64 MiB) and entry count (500). Both tunable via WORKFLOW_EVENT_CACHE_MAX_BYTES / WORKFLOW_EVENT_CACHE_MAX_ENTRIES. Hand-rolled Map-based LRU (no new dependency).
  • New: packages/core/src/runtime/event-cache.test.ts — 17 unit tests (round-trip, byte-budget eviction, totalSize accounting, clear, feature flag, size estimation).
  • packages/core/src/runtime.ts — integrated the cache into the resume path:
    • First iteration with no preloaded events: check cache → delta-fetch from cached cursor → merge (copy-on-read, dedup by eventId); fall back to a full load on miss.
    • After each replay iteration: write back {events: [...events], cursor} so the next resume's baseline includes wait_completed appends.
    • Evict on terminal events (run_completed / run_failed / run_cancelled).
    • Added a workflow.events.cache_hit span attribute on every replay.
  • packages/core/src/runtime/helpers.ts — exported appendUniqueEvents so the cache merge path reuses the existing dedup helper.
  • packages/core/src/runtime/wait-completion-replay.test.tsclearEventCache() in afterEach so the process-wide cache doesn't leak between scenarios that share a runId.

Correctness

  • Always delta-fetch on cache hit; never replay purely from cache (kept correct against concurrent writers on other instances).
  • Merge dedups by eventId and preserves ascending order (delta uses sortOrder: 'asc').
  • Cached cursor is the last non-null cursor (mirrors helpers.ts semantics).
  • Cached events include everything replayed, including wait_completed appends (write-back happens after the merge block).
  • Copy-on-read / copy-on-write: the cache's array is never aliased with the runtime's mutable events.
  • Bad-cursor 400 → existing shouldRetryWithoutEventCursor path performs a full reload; the cache simply repopulates with the new baseline. No new invalidation protocol needed.
  • Cache-miss path is byte-for-byte equivalent to today's behavior.

Rollout / risk

  • Pure client-side change; ships with the next @workflow/core release.
  • Kill switch: WORKFLOW_DISABLE_EVENT_CACHE=1 forces the cold full-load path so the cache can be disabled without a rollback.
  • On any doubt the code falls back to a full reload (already the cold-path behavior).

Verification

  • pnpm typecheck clean
  • pnpm test — all 1100 tests pass (including 17 new event-cache tests)
  • pnpm build clean
  • Biome format clean on all touched files

… resume

On warm function instances, the workflow runtime now keeps a process-wide
LRU cache (keyed by runId) of loaded event logs. On resume we delta-fetch
from the cached cursor instead of reloading from event 0 on every
invocation, converting the warm-replay path from O(N^2) to O(N) event
reads over a long, step-heavy run's life.

The cache is bounded by ~64 MiB of approximate bytes and 500 entries
(both tunable via WORKFLOW_EVENT_CACHE_MAX_BYTES /
WORKFLOW_EVENT_CACHE_MAX_ENTRIES). We always delta against the server
(never replay purely from cache) so we stay correct against writers on
other instances, and the existing shouldRetryWithoutEventCursor path
self-heals if a cached cursor is ever stale.

Set WORKFLOW_DISABLE_EVENT_CACHE=1 to disable as a kill switch.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jun 1, 2026

🦋 Changeset detected

Latest commit: 71e52b2

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
@workflow/core Minor
workflow Minor
@workflow/builders Patch
@workflow/cli Patch
@workflow/next Patch
@workflow/nitro Patch
@workflow/vitest Patch
@workflow/web-shared Patch
@workflow/web Patch
@workflow/world-testing Patch
@workflow/ai Major
@workflow/astro Patch
@workflow/nest Patch
@workflow/rollup Patch
@workflow/sveltekit Patch
@workflow/vite Patch
@workflow/nuxt Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Jun 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
example-nextjs-workflow-turbopack Error Error Jun 2, 2026 11:55pm
example-nextjs-workflow-webpack Ready Ready Preview, Comment Jun 2, 2026 11:55pm
example-workflow Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workbench-astro-workflow Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workbench-express-workflow Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workbench-fastify-workflow Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workbench-hono-workflow Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workbench-nitro-workflow Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workbench-nuxt-workflow Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workbench-sveltekit-workflow Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workbench-tanstack-start-workflow Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workbench-vite-workflow Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workflow-docs Ready Ready Preview, Comment, Open in v0 Jun 2, 2026 11:55pm
workflow-swc-playground Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workflow-tarballs Ready Ready Preview, Comment Jun 2, 2026 11:55pm
workflow-web Ready Ready Preview, Comment Jun 2, 2026 11:55pm

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

📊 Benchmark Results

📈 Comparing against baseline from main branch. Green 🟢 = faster, Red 🔺 = slower.

workflow with no steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 0.031s (-27.6% 🟢) 1.004s (~) 0.973s 10 1.00x
💻 Local Express 0.039s (-11.5% 🟢) 1.006s (~) 0.966s 10 1.26x
💻 Local Next.js (Turbopack) 0.059s 1.005s 0.947s 10 1.88x
🐘 Postgres Nitro 0.059s (-37.8% 🟢) 1.012s (-3.0%) 0.953s 10 1.90x
🐘 Postgres Express 0.063s (+8.3% 🔺) 1.011s (~) 0.949s 10 2.01x
🐘 Postgres Next.js (Turbopack) 0.070s 1.012s 0.943s 10 2.23x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 0.264s (-35.5% 🟢) 2.557s (+1.9%) 2.293s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.072s (-5.2% 🟢) 2.006s (~) 0.934s 10 1.00x
💻 Local Express 1.092s (-3.0%) 2.006s (~) 0.914s 10 1.02x
🐘 Postgres Nitro 1.111s (-2.5%) 2.011s (~) 0.899s 10 1.04x
🐘 Postgres Express 1.116s (-2.7%) 2.011s (~) 0.895s 10 1.04x
💻 Local Next.js (Turbopack) 1.136s 2.006s 0.869s 10 1.06x
🐘 Postgres Next.js (Turbopack) 1.139s 2.009s 0.870s 10 1.06x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.670s (-57.1% 🟢) 3.918s (-33.7% 🟢) 2.247s 10 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.422s (-4.8%) 11.020s (~) 0.598s 3 1.00x
💻 Local Express 10.535s (-3.5%) 11.023s (~) 0.488s 3 1.01x
🐘 Postgres Express 10.568s (-3.6%) 11.024s (~) 0.456s 3 1.01x
🐘 Postgres Nitro 10.572s (-2.8%) 11.018s (~) 0.446s 3 1.01x
💻 Local Next.js (Turbopack) 10.781s 11.021s 0.240s 3 1.03x
🐘 Postgres Next.js (Turbopack) 10.901s 11.353s 0.452s 3 1.05x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 14.298s (-39.8% 🟢) 16.273s (-35.2% 🟢) 1.975s 2 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 13.443s (-10.8% 🟢) 14.026s (-12.5% 🟢) 0.583s 5 1.00x
💻 Local Express 13.704s (-8.5% 🟢) 14.026s (-6.7% 🟢) 0.322s 5 1.02x
🐘 Postgres Express 13.723s (-5.9% 🟢) 14.019s (-6.7% 🟢) 0.296s 5 1.02x
🐘 Postgres Nitro 13.798s (-5.5% 🟢) 14.019s (-6.7% 🟢) 0.221s 5 1.03x
💻 Local Next.js (Turbopack) 14.319s 15.028s 0.710s 4 1.07x
🐘 Postgres Next.js (Turbopack) 14.395s 15.015s 0.620s 4 1.07x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 22.379s (-65.3% 🟢) 24.651s (-63.0% 🟢) 2.272s 3 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 11.915s (-29.0% 🟢) 12.020s (-29.4% 🟢) 0.105s 8 1.00x
💻 Local Express 12.386s (-25.4% 🟢) 13.026s (-23.5% 🟢) 0.639s 7 1.04x
🐘 Postgres Nitro 12.434s (-11.0% 🟢) 13.018s (-9.0% 🟢) 0.585s 7 1.04x
🐘 Postgres Express 12.508s (-10.7% 🟢) 13.020s (-10.8% 🟢) 0.512s 7 1.05x
💻 Local Next.js (Turbopack) 13.505s 14.025s 0.521s 7 1.13x
🐘 Postgres Next.js (Turbopack) 13.782s 14.020s 0.237s 7 1.16x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 33.781s (-92.0% 🟢) 35.868s (-91.6% 🟢) 2.087s 3 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.151s (-29.4% 🟢) 2.005s (-3.3%) 0.854s 15 1.00x
🐘 Postgres Express 1.178s (-6.5% 🟢) 2.008s (~) 0.830s 15 1.02x
🐘 Postgres Nitro 1.183s (-7.2% 🟢) 2.008s (~) 0.825s 15 1.03x
💻 Local Express 1.209s (-18.8% 🟢) 2.006s (~) 0.796s 15 1.05x
🐘 Postgres Next.js (Turbopack) 1.287s 2.007s 0.721s 15 1.12x
💻 Local Next.js (Turbopack) 1.312s 2.006s 0.694s 15 1.14x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 155.293s (+5410.5% 🔺) 158.252s (+3561.2% 🔺) 2.959s 2 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.258s (-46.5% 🟢) 2.007s (-33.3% 🟢) 0.749s 15 1.00x
🐘 Postgres Express 1.262s (-46.6% 🟢) 2.007s (-33.3% 🟢) 0.745s 15 1.00x
🐘 Postgres Next.js (Turbopack) 1.392s 2.007s 0.615s 15 1.11x
💻 Local Nitro 1.572s (-50.0% 🟢) 2.005s (-48.4% 🟢) 0.433s 15 1.25x
💻 Local Express 1.707s (-42.2% 🟢) 2.006s (-41.9% 🟢) 0.298s 15 1.36x
💻 Local Next.js (Turbopack) 1.848s 2.150s 0.302s 14 1.47x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 64.464s (+1490.9% 🔺) 66.360s (+1020.8% 🔺) 1.896s 5 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.399s (-59.9% 🟢) 2.008s (-49.9% 🟢) 0.609s 15 1.00x
🐘 Postgres Nitro 1.407s (-59.6% 🟢) 2.008s (-49.9% 🟢) 0.601s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.728s 2.224s 0.496s 14 1.24x
💻 Local Nitro 4.124s (-50.6% 🟢) 4.868s (-46.0% 🟢) 0.745s 7 2.95x
💻 Local Express 4.763s (-42.9% 🟢) 5.348s (-40.7% 🟢) 0.585s 6 3.41x
💻 Local Next.js (Turbopack) 5.497s 6.013s 0.517s 6 3.93x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.018s (+42.4% 🔺) 7.436s (+34.4% 🔺) 2.418s 5 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.177s (-6.4% 🟢) 2.007s (~) 0.830s 15 1.00x
🐘 Postgres Nitro 1.187s (-5.6% 🟢) 2.008s (~) 0.821s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.238s 2.009s 0.771s 15 1.05x
💻 Local Next.js (Turbopack) 1.363s 2.006s 0.643s 15 1.16x
💻 Local Nitro 1.372s (-26.5% 🟢) 2.005s (-14.3% 🟢) 0.633s 15 1.17x
💻 Local Express 1.514s (-20.1% 🟢) 2.006s (-15.1% 🟢) 0.492s 15 1.29x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.561s (+4.1%) 4.111s (-1.4%) 1.550s 8 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.268s (-45.8% 🟢) 2.010s (-33.3% 🟢) 0.742s 15 1.00x
🐘 Postgres Nitro 1.280s (-45.3% 🟢) 2.008s (-33.3% 🟢) 0.728s 15 1.01x
🐘 Postgres Next.js (Turbopack) 1.372s 2.009s 0.637s 15 1.08x
💻 Local Nitro 1.764s (-42.4% 🟢) 2.083s (-46.4% 🟢) 0.318s 15 1.39x
💻 Local Express 2.085s (-33.4% 🟢) 2.508s (-33.3% 🟢) 0.423s 12 1.64x
💻 Local Next.js (Turbopack) 2.088s 2.674s 0.586s 12 1.65x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.467s (+7.2% 🔺) 5.383s (+6.0% 🔺) 1.916s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.383s (-60.5% 🟢) 2.008s (-49.9% 🟢) 0.626s 15 1.00x
🐘 Postgres Nitro 1.416s (-59.3% 🟢) 2.007s (-49.9% 🟢) 0.592s 15 1.02x
🐘 Postgres Next.js (Turbopack) 1.689s 2.076s 0.387s 15 1.22x
💻 Local Nitro 4.347s (-52.5% 🟢) 5.011s (-50.0% 🟢) 0.664s 7 3.14x
💻 Local Express 5.565s (-36.8% 🟢) 6.014s (-35.1% 🟢) 0.449s 5 4.03x
💻 Local Next.js (Turbopack) 5.874s 6.215s 0.341s 5 4.25x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.043s (-1.0%) 6.962s (+2.1%) 1.919s 5 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.563s (-31.3% 🟢) 1.007s (~) 0.443s 60 1.00x
💻 Local Nitro 0.569s (-41.9% 🟢) 1.038s (-5.1% 🟢) 0.469s 58 1.01x
🐘 Postgres Express 0.579s (-31.0% 🟢) 1.024s (~) 0.445s 59 1.03x
💻 Local Express 0.609s (-38.1% 🟢) 1.022s (-5.1% 🟢) 0.412s 59 1.08x
🐘 Postgres Next.js (Turbopack) 0.843s 1.041s 0.199s 58 1.50x
💻 Local Next.js (Turbopack) 0.849s 1.004s 0.155s 60 1.51x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.724s (-74.0% 🟢) 7.567s (-68.5% 🟢) 1.843s 8 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.317s (-33.4% 🟢) 2.007s (-11.1% 🟢) 0.691s 45 1.00x
💻 Local Nitro 1.384s (-54.4% 🟢) 2.051s (-45.4% 🟢) 0.667s 44 1.05x
🐘 Postgres Nitro 1.392s (-27.8% 🟢) 2.030s (-3.3%) 0.638s 45 1.06x
💻 Local Express 1.459s (-51.6% 🟢) 2.006s (-44.0% 🟢) 0.547s 45 1.11x
🐘 Postgres Next.js (Turbopack) 1.915s 2.076s 0.161s 44 1.45x
💻 Local Next.js (Turbopack) 2.043s 2.764s 0.722s 33 1.55x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 13.550s (-65.7% 🟢) 15.977s (-61.3% 🟢) 2.426s 6 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 2.698s (-34.2% 🟢) 3.112s (-32.4% 🟢) 0.414s 39 1.00x
🐘 Postgres Express 2.709s (-32.1% 🟢) 3.112s (-28.8% 🟢) 0.403s 39 1.00x
💻 Local Nitro 2.834s (-69.5% 🟢) 3.165s (-68.4% 🟢) 0.331s 38 1.05x
💻 Local Express 3.151s (-65.8% 🟢) 3.913s (-60.9% 🟢) 0.761s 31 1.17x
🐘 Postgres Next.js (Turbopack) 3.868s 4.182s 0.314s 29 1.43x
💻 Local Next.js (Turbopack) 4.290s 5.010s 0.720s 24 1.59x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 27.640s (-71.5% 🟢) 29.972s (-69.5% 🟢) 2.333s 5 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.225s (-20.7% 🟢) 1.006s (~) 0.782s 60 1.00x
🐘 Postgres Express 0.226s (-20.1% 🟢) 1.006s (~) 0.781s 60 1.00x
🐘 Postgres Next.js (Turbopack) 0.254s 1.006s 0.752s 60 1.13x
💻 Local Nitro 0.410s (-32.2% 🟢) 1.004s (-1.7%) 0.594s 60 1.83x
💻 Local Express 0.423s (-24.5% 🟢) 1.004s (~) 0.581s 60 1.88x
💻 Local Next.js (Turbopack) 0.550s 1.004s 0.454s 60 2.45x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.379s (+43.2% 🔺) 4.158s (+24.1% 🔺) 1.779s 15 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 0.347s (-31.9% 🟢) 1.007s (~) 0.660s 90 1.00x
🐘 Postgres Nitro 0.347s (-30.0% 🟢) 1.007s (~) 0.659s 90 1.00x
🐘 Postgres Next.js (Turbopack) 0.480s 1.006s 0.526s 90 1.38x
💻 Local Nitro 1.815s (-28.5% 🟢) 2.307s (-23.3% 🟢) 0.492s 40 5.23x
💻 Local Express 2.175s (-13.4% 🟢) 2.765s (-8.1% 🟢) 0.590s 33 6.26x
💻 Local Next.js (Turbopack) 2.289s 2.977s 0.688s 31 6.59x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 4.980s (+54.4% 🔺) 6.787s (+40.8% 🔺) 1.807s 14 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.656s (-17.0% 🟢) 1.006s (~) 0.350s 120 1.00x
🐘 Postgres Express 0.661s (-19.3% 🟢) 1.006s (-1.1%) 0.345s 120 1.01x
🐘 Postgres Next.js (Turbopack) 0.948s 1.489s 0.541s 81 1.45x
💻 Local Nitro 8.135s (-27.3% 🟢) 8.736s (-25.1% 🟢) 0.601s 14 12.40x
💻 Local Express 9.429s (-15.7% 🟢) 9.946s (-16.7% 🟢) 0.517s 13 14.37x
💻 Local Next.js (Turbopack) 10.116s 10.946s 0.830s 12 15.41x

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 14.132s (+83.0% 🔺) 16.449s (+75.0% 🔺) 2.317s 8 1.00x
▲ Vercel Express ⚠️ missing - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - -

🔍 Observability: Nitro

Stream Benchmarks (includes TTFB metrics)
workflow with stream

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.125s (+426.3% 🔺) 2.009s (+100.0% 🔺) 0.008s (-33.6% 🟢) 2.019s (+98.2% 🔺) 0.895s 10 1.00x
🐘 Postgres Express 1.153s (+462.1% 🔺) 2.000s (+100.3% 🔺) 0.001s (-25.0% 🟢) 2.010s (+98.7% 🔺) 0.857s 10 1.02x
💻 Local Express 1.162s (+483.6% 🔺) 2.005s (+99.6% 🔺) 0.015s (+22.3% 🔺) 2.022s (+98.7% 🔺) 0.860s 10 1.03x
🐘 Postgres Nitro 1.163s (+467.3% 🔺) 2.000s (+100.1% 🔺) 0.001s (-33.3% 🟢) 2.010s (+98.7% 🔺) 0.847s 10 1.03x
💻 Local Next.js (Turbopack) 1.200s 2.004s 0.013s 2.020s 0.820s 10 1.07x
🐘 Postgres Next.js (Turbopack) 1.227s 2.001s 0.001s 2.010s 0.783s 10 1.09x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.317s (-39.5% 🟢) 3.325s (-37.0% 🟢) 2.026s (+173.0% 🔺) 5.816s (-10.3% 🟢) 3.500s 10 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 1.463s (+74.5% 🔺) 2.007s (+98.3% 🔺) 0.009s (-7.2% 🟢) 2.017s (+80.8% 🔺) 0.554s 30 1.00x
🐘 Postgres Nitro 1.601s (+156.5% 🔺) 2.004s (+99.1% 🔺) 0.004s (-4.9%) 2.025s (+98.1% 🔺) 0.425s 30 1.09x
🐘 Postgres Express 1.610s (+155.5% 🔺) 2.037s (+102.3% 🔺) 0.004s (+5.3% 🔺) 2.060s (+101.3% 🔺) 0.450s 30 1.10x
🐘 Postgres Next.js (Turbopack) 1.724s 2.012s 0.004s 2.026s 0.302s 30 1.18x
💻 Local Next.js (Turbopack) 1.728s 2.009s 0.010s 2.022s 0.294s 30 1.18x
💻 Local Express 1.741s (+130.0% 🔺) 2.010s (+95.3% 🔺) 0.010s (+2.1%) 2.200s (+111.6% 🔺) 0.459s 28 1.19x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 6.035s (-79.5% 🟢) 7.485s (-75.7% 🟢) 0.641s (+472.1% 🔺) 8.710s (-72.6% 🟢) 2.675s 7 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.694s (-28.4% 🟢) 1.016s (-18.6% 🟢) 0.000s (+24.1% 🔺) 1.036s (-17.6% 🟢) 0.342s 58 1.00x
🐘 Postgres Express 0.709s (-26.2% 🟢) 1.034s (-19.1% 🟢) 0.000s (-20.7% 🟢) 1.048s (-19.8% 🟢) 0.338s 58 1.02x
🐘 Postgres Next.js (Turbopack) 0.845s 1.091s 0.000s 1.102s 0.257s 55 1.22x
💻 Local Nitro 1.215s (-0.6%) 2.013s (~) 0.000s (+133.3% 🔺) 2.015s (~) 0.800s 30 1.75x
💻 Local Express 1.355s (+10.6% 🔺) 2.014s (~) 0.000s (-40.0% 🟢) 2.016s (~) 0.661s 30 1.95x
💻 Local Next.js (Turbopack) 1.457s 2.013s 0.001s 2.016s 0.559s 30 2.10x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.764s (+23.4% 🔺) 5.024s (+14.4% 🔺) 0.000s (-100.0% 🟢) 5.519s (+14.8% 🔺) 1.755s 11 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

fan-out fan-in 10 streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 1.344s (-24.2% 🟢) 2.068s (-5.1% 🟢) 0.000s (NaN%) 2.090s (-4.9%) 0.747s 29 1.00x
🐘 Postgres Nitro 1.514s (-15.5% 🟢) 2.102s (-1.8%) 0.000s (-100.0% 🟢) 2.113s (-2.8%) 0.600s 29 1.13x
🐘 Postgres Next.js (Turbopack) 1.875s 2.261s 0.000s 2.276s 0.401s 27 1.39x
💻 Local Nitro 2.215s (-34.6% 🟢) 2.877s (-28.6% 🟢) 0.000s (-73.2% 🟢) 2.881s (-28.6% 🟢) 0.666s 21 1.65x
💻 Local Express 2.776s (-20.0% 🟢) 3.237s (-19.7% 🟢) 0.000s (-60.5% 🟢) 3.243s (-19.7% 🟢) 0.467s 19 2.07x
💻 Local Next.js (Turbopack) 2.859s 3.406s 0.001s 3.419s 0.560s 18 2.13x

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.277s (+28.9% 🔺) 7.027s (+30.7% 🔺) 0.000s (-100.0% 🟢) 7.572s (+30.7% 🔺) 2.295s 8 1.00x
▲ Vercel Express ⚠️ missing - - - - -
▲ Vercel Next.js (Turbopack) ⚠️ missing - - - - -

🔍 Observability: Nitro

Summary

Fastest Framework by World

Winner determined by most benchmark wins

World 🥇 Fastest Framework Wins
💻 Local Nitro 20/21
🐘 Postgres Express 11/21
▲ Vercel Nitro 21/21
Fastest World by Framework

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 16/21
Next.js (Turbopack) 🐘 Postgres 15/21
Nitro 🐘 Postgres 12/21
Column Definitions
  • Workflow Time: Runtime reported by workflow (completedAt - createdAt) - primary metric
  • TTFB: Time to First Byte - time from workflow start until first stream byte received (stream benchmarks only)
  • Slurp: Time from first byte to complete stream consumption (stream benchmarks only)
  • Wall Time: Total testbench time (trigger workflow + poll for result)
  • Overhead: Testbench overhead (Wall Time - Workflow Time)
  • Samples: Number of benchmark iterations run
  • vs Fastest: How much slower compared to the fastest configuration for this benchmark

Worlds:

  • 💻 Local: In-memory filesystem world (local development)
  • 🐘 Postgres: PostgreSQL database world (local development)
  • ▲ Vercel: Vercel production/preview deployment
  • 🌐 Turso: Community world (local development)
  • 🌐 MongoDB: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Jazz: Community world (local development)
  • 🌐 Redis: Community world (local development)
  • 🌐 Redis + BullMQ: Community world (local development)
  • 🌐 Cloudflare: Community world (local development)
  • 🌐 MySQL: Community world (local development)
  • 🌐 Azure: Community world (local development)
  • 🌐 NATS JetStream: Community world (local development)
  • 🌐 Upstash: Community world (local development)

📋 View full workflow run


Some benchmark jobs failed:

  • Local: success
  • Postgres: success
  • Vercel: failure

Check the workflow run for details.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

🧪 E2E Test Results

Some tests failed

Summary

Passed Failed Skipped Total
❌ ▲ Vercel Production 1120 13 217 1350
✅ 💻 Local Development 1671 0 219 1890
✅ 📦 Local Production 1671 0 219 1890
✅ 🐘 Local Postgres 1671 0 219 1890
✅ 🪟 Windows 135 0 0 135
❌ 📋 Other 767 2 176 945
Total 7035 15 1050 8100

❌ Failed Tests

▲ Vercel Production (13 failed)

astro (1 failed):

  • AbortController abortFetchInFlightWorkflow: aborting cancels an in-flight fetch

example (8 failed):

  • fibonacciWorkflow - recursive workflow composition via start() | wrun_01KT2T3XYR8YGMJ630S8ZGAM9V | 🔍 observability
  • health check (queue-based) - workflow endpoint responds to health check messages
  • health check (CLI) - workflow health command reports healthy endpoints
  • pathsAliasWorkflow - TypeScript path aliases resolve correctly | wrun_01KT2T4AP7R6KA0BR2EKW7SYPR | 🔍 observability
  • Calculator.calculate - static workflow method using static step methods from another class | wrun_01KT2T4FKW5F56R77SCKF921VX | 🔍 observability
  • AllInOneService.processNumber - static workflow method using sibling static step methods | wrun_01KT2T4MGCPRYSM83QBAMW55C6 | 🔍 observability
  • ChainableService.processWithThis - static step methods using this to reference the class | wrun_01KT2T4S9TPR6V13NF5T94FR7F | 🔍 observability
  • thisSerializationWorkflow - step function invoked with .call() and .apply() | wrun_01KT2T4Y2RE7T69T1N301M7Q18 | 🔍 observability

express (1 failed):

  • AbortController abortVoidSleepTimeoutWorkflow: documented void sleep().then(abort) pattern works

nextjs-webpack (1 failed):

  • fibonacciWorkflow - recursive workflow composition via start() | wrun_01KT2T3XYR8YGMJ630S8ZGAM9V | 🔍 observability

nitro (1 failed):

  • AbortController abortThrowIfAbortedWorkflow: throwIfAborted causes FatalError, no retries

vite (1 failed):

  • AbortController abortVoidSleepTimeoutWorkflow: documented void sleep().then(abort) pattern works
📋 Other (2 failed)

e2e-vercel-prod-tanstack-start (2 failed):

  • sleepWithSequentialStepsWorkflow - sequential steps work with concurrent sleep (control) | wrun_01KT2T82Y7K9CM6DAP3BAGMXZQ
  • AbortController abortTimeoutWorkflow: timeout cancels long-running step

Details by Category

❌ ▲ Vercel Production
App Passed Failed Skipped
❌ astro 108 1 26
❌ example 101 8 26
❌ express 108 1 26
✅ fastify 109 0 26
✅ hono 109 0 26
❌ nextjs-webpack 132 1 2
❌ nitro 108 1 26
✅ nuxt 109 0 26
✅ sveltekit 128 0 7
❌ vite 108 1 26
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 110 0 25
✅ express-stable 110 0 25
✅ fastify-stable 110 0 25
✅ hono-stable 110 0 25
✅ nextjs-turbopack-canary 116 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 135 0 0
✅ nextjs-webpack-canary 116 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 135 0 0
✅ nitro-stable 110 0 25
✅ nuxt-stable 110 0 25
✅ sveltekit-stable 129 0 6
✅ vite-stable 110 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 110 0 25
✅ express-stable 110 0 25
✅ fastify-stable 110 0 25
✅ hono-stable 110 0 25
✅ nextjs-turbopack-canary 116 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 135 0 0
✅ nextjs-webpack-canary 116 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 135 0 0
✅ nitro-stable 110 0 25
✅ nuxt-stable 110 0 25
✅ sveltekit-stable 129 0 6
✅ vite-stable 110 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 110 0 25
✅ express-stable 110 0 25
✅ fastify-stable 110 0 25
✅ hono-stable 110 0 25
✅ nextjs-turbopack-canary 116 0 19
✅ nextjs-turbopack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-turbopack-stable-lazy-discovery-enabled 135 0 0
✅ nextjs-webpack-canary 116 0 19
✅ nextjs-webpack-stable-lazy-discovery-disabled 135 0 0
✅ nextjs-webpack-stable-lazy-discovery-enabled 135 0 0
✅ nitro-stable 110 0 25
✅ nuxt-stable 110 0 25
✅ sveltekit-stable 129 0 6
✅ vite-stable 110 0 25
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 135 0 0
❌ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 110 0 25
✅ e2e-local-dev-tanstack-start- 110 0 25
✅ e2e-local-postgres-nest-stable 110 0 25
✅ e2e-local-postgres-tanstack-start- 110 0 25
✅ e2e-local-prod-nest-stable 110 0 25
✅ e2e-local-prod-tanstack-start- 110 0 25
❌ e2e-vercel-prod-tanstack-start 107 2 26

📋 View full workflow run


Some E2E test jobs failed:

  • Vercel Prod: failure
  • Local Dev: success
  • Local Prod: success
  • Local Postgres: success
  • Windows: success

Check the workflow run for details.

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