Skip to content

world-postgres: enforce per-(run, correlation) uniqueness for entity-creating events#1878

Merged
TooTallNate merged 5 commits into
mainfrom
world-postgres-event-uniqueness
May 4, 2026
Merged

world-postgres: enforce per-(run, correlation) uniqueness for entity-creating events#1878
TooTallNate merged 5 commits into
mainfrom
world-postgres-event-uniqueness

Conversation

@TooTallNate
Copy link
Copy Markdown
Member

Summary

Adds a unique partial index on workflow_events(run_id, correlation_id, type) filtered to step_created / hook_created / wait_created, and translates the resulting unique-violation (pg code 23505, surfaced via DrizzleQueryError.cause) into EntityConflictError.

Background

Concurrent invocations producing identical correlationIds previously both succeeded at the events-table level, leaving duplicate rows in the log. The steps table already deduped via onConflictDoNothing, but the corresponding event row still inserted, so the storage was internally inconsistent (one step, two step_created events). The runtime's existing dedup catch path (if (EntityConflictError.is(err)) continue in runtime/snapshot-entrypoint.ts) was the intended consumer of this signal but never received it from world-postgres.

This is the postgres counterpart of PR #1877 (world-local).

Fix

  • New unique partial index workflow_events_entity_creation_unique on (run_id, correlation_id, type) filtered to entity-creating events (migration 0010_add_events_entity_creation_unique_index.sql).
  • events.create() wraps the INSERT in try/catch, detects pg 23505 (read off DrizzleQueryError.cause.code), and re-throws as EntityConflictError for the relevant event types.
  • 3 regression tests covering concurrent + sequential step_created and wait_created duplicate scenarios.

Verification

pnpm -F @workflow/world-postgres typecheck   # clean
pnpm -F @workflow/world-postgres build       # clean
pnpm -F @workflow/world-postgres test        # 106 passed (was 103 before the 3 regression tests)

Extracted from PR #1300 (snapshot-runtime). The snapshot runtime produces deterministic correlationIds across concurrent VM invocations of the same resumption by design — that path made the dedup gap reliably reproducible — but the fix is also valuable on its own for replay-runtime concurrent scenarios.

Copilot AI review requested due to automatic review settings April 30, 2026 08:24
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Apr 30, 2026

🦋 Changeset detected

Latest commit: 774f4fe

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

This PR includes changesets to release 2 packages
Name Type
@workflow/world-postgres Patch
tarballs 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 Apr 30, 2026

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

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

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 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.030s (-29.7% 🟢) 1.005s (~) 0.975s 10 1.00x
💻 Local Express 0.032s (-27.8% 🟢) 1.005s (~) 0.973s 10 1.06x
💻 Local Next.js (Turbopack) 0.040s 1.005s 0.965s 10 1.32x
🐘 Postgres Nitro 0.045s (-52.6% 🟢) 1.011s (-3.0%) 0.966s 10 1.49x
🐘 Postgres Express 0.048s (-17.6% 🟢) 1.011s (~) 0.963s 10 1.58x
🌐 Redis Next.js (Turbopack) 0.052s 1.005s 0.953s 10 1.73x
🌐 MongoDB Next.js (Turbopack) 0.091s 1.006s 0.916s 10 3.00x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 0.284s (+20.9% 🔺) 2.483s (+16.3% 🔺) 2.199s 10 1.00x
▲ Vercel Next.js (Turbopack) 0.652s (+159.4% 🔺) 2.942s (+26.1% 🔺) 2.290s 10 2.29x
▲ Vercel Nitro 0.806s (+96.8% 🔺) 2.934s (+16.9% 🔺) 2.128s 10 2.83x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

workflow with 1 step

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Express 1.069s (-5.0% 🟢) 2.006s (~) 0.938s 10 1.00x
💻 Local Nitro 1.069s (-5.5% 🟢) 2.006s (~) 0.937s 10 1.00x
🐘 Postgres Nitro 1.085s (-4.8%) 2.010s (~) 0.924s 10 1.02x
🐘 Postgres Express 1.086s (-5.3% 🟢) 2.011s (~) 0.925s 10 1.02x
💻 Local Next.js (Turbopack) 1.091s 2.005s 0.915s 10 1.02x
🌐 Redis Next.js (Turbopack) 1.110s 2.007s 0.897s 10 1.04x
🌐 MongoDB Next.js (Turbopack) 1.149s 2.007s 0.859s 10 1.07x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 1.710s (-56.1% 🟢) 3.269s (-44.7% 🟢) 1.559s 10 1.00x
▲ Vercel Express 1.804s (-3.8%) 3.852s (+1.2%) 2.047s 10 1.06x
▲ Vercel Next.js (Turbopack) 2.650s (+30.2% 🔺) 4.575s (+19.4% 🔺) 1.925s 10 1.55x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 10 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 10.392s (-5.1% 🟢) 11.021s (~) 0.630s 3 1.00x
🐘 Postgres Nitro 10.407s (-4.3%) 11.021s (~) 0.614s 3 1.00x
💻 Local Express 10.407s (-4.7%) 11.021s (~) 0.614s 3 1.00x
🐘 Postgres Express 10.421s (-4.9%) 11.017s (~) 0.596s 3 1.00x
💻 Local Next.js (Turbopack) 10.525s 11.021s 0.496s 3 1.01x
🌐 MongoDB Next.js (Turbopack) 10.626s 11.019s 0.393s 3 1.02x
🌐 Redis Next.js (Turbopack) 10.725s 11.023s 0.299s 3 1.03x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 13.979s (-19.3% 🟢) 15.374s (-20.7% 🟢) 1.395s 2 1.00x
▲ Vercel Nitro 14.040s (-40.8% 🟢) 15.736s (-37.3% 🟢) 1.696s 2 1.00x
▲ Vercel Express 16.525s (-2.7%) 18.170s (-9.2% 🟢) 1.645s 2 1.18x

🔍 Observability: Next.js (Turbopack) | Nitro | Express

workflow with 25 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 13.434s (-10.8% 🟢) 14.027s (-12.5% 🟢) 0.592s 5 1.00x
🐘 Postgres Express 13.445s (-7.8% 🟢) 14.017s (-6.7% 🟢) 0.572s 5 1.00x
💻 Local Express 13.459s (-10.1% 🟢) 14.025s (-6.7% 🟢) 0.566s 5 1.00x
🐘 Postgres Nitro 13.488s (-7.6% 🟢) 14.018s (-6.7% 🟢) 0.529s 5 1.00x
💻 Local Next.js (Turbopack) 13.684s 14.024s 0.341s 5 1.02x
🌐 MongoDB Next.js (Turbopack) 13.978s 14.217s 0.239s 5 1.04x
🌐 Redis Next.js (Turbopack) 14.040s 14.428s 0.387s 5 1.05x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 22.836s (-54.6% 🟢) 24.756s (-52.9% 🟢) 1.920s 3 1.00x
▲ Vercel Nitro 24.859s (-61.4% 🟢) 26.397s (-60.4% 🟢) 1.538s 3 1.09x
▲ Vercel Next.js (Turbopack) 25.551s (-51.4% 🟢) 27.284s (-50.0% 🟢) 1.733s 3 1.12x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
💻 Local 🥇 Nitro 11.797s (-29.7% 🟢) 12.023s (-29.4% 🟢) 0.226s 8 1.00x
🐘 Postgres Express 11.909s (-15.0% 🟢) 12.016s (-17.7% 🟢) 0.107s 8 1.01x
💻 Local Express 11.944s (-28.1% 🟢) 12.396s (-27.2% 🟢) 0.451s 8 1.01x
🐘 Postgres Nitro 11.998s (-14.1% 🟢) 12.390s (-13.4% 🟢) 0.392s 8 1.02x
💻 Local Next.js (Turbopack) 12.387s 13.023s 0.636s 7 1.05x
🌐 MongoDB Next.js (Turbopack) 12.674s 13.018s 0.344s 7 1.07x
🌐 Redis Next.js (Turbopack) 13.042s 13.454s 0.413s 7 1.11x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 36.343s (-70.0% 🟢) 38.276s (-69.0% 🟢) 1.933s 3 1.00x
▲ Vercel Next.js (Turbopack) 39.846s (-89.9% 🟢) 41.372s (-89.5% 🟢) 1.525s 3 1.10x
▲ Vercel Nitro 182.474s (-56.9% 🟢) 184.312s (-56.6% 🟢) 1.839s 2 5.02x

🔍 Observability: Express | Next.js (Turbopack) | Nitro

Promise.all with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.153s (-9.5% 🟢) 2.007s (~) 0.854s 15 1.00x
🐘 Postgres Express 1.162s (-7.8% 🟢) 2.007s (~) 0.845s 15 1.01x
💻 Local Nitro 1.177s (-27.8% 🟢) 2.006s (-3.3%) 0.828s 15 1.02x
💻 Local Express 1.179s (-20.8% 🟢) 2.006s (~) 0.827s 15 1.02x
💻 Local Next.js (Turbopack) 1.221s 2.005s 0.784s 15 1.06x
🌐 Redis Next.js (Turbopack) 1.249s 2.006s 0.757s 15 1.08x
🌐 MongoDB Next.js (Turbopack) 2.024s 2.734s 0.710s 11 1.75x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.491s (-11.6% 🟢) 3.842s (-11.1% 🟢) 1.351s 8 1.00x
▲ Vercel Express 2.633s (-7.9% 🟢) 4.170s (-9.8% 🟢) 1.537s 8 1.06x
▲ Vercel Next.js (Turbopack) 3.832s (+12.8% 🔺) 5.363s (+8.7% 🔺) 1.531s 6 1.54x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.all with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.210s (-48.5% 🟢) 2.007s (-33.3% 🟢) 0.797s 15 1.00x
🐘 Postgres Express 1.231s (-47.9% 🟢) 2.007s (-33.3% 🟢) 0.776s 15 1.02x
💻 Local Next.js (Turbopack) 1.662s 2.005s 0.343s 15 1.37x
💻 Local Nitro 1.685s (-46.4% 🟢) 2.006s (-48.4% 🟢) 0.321s 15 1.39x
💻 Local Express 1.742s (-41.0% 🟢) 2.005s (-42.0% 🟢) 0.263s 15 1.44x
🌐 Redis Next.js (Turbopack) 2.371s 3.009s 0.637s 10 1.96x
🌐 MongoDB Next.js (Turbopack) 3.560s 4.007s 0.447s 8 2.94x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 3.266s (-9.8% 🟢) 5.058s (-1.0%) 1.791s 6 1.00x
▲ Vercel Nitro 3.381s (-16.6% 🟢) 5.219s (-11.8% 🟢) 1.838s 6 1.04x
▲ Vercel Next.js (Turbopack) 5.250s (-26.1% 🟢) 6.595s (-25.9% 🟢) 1.345s 5 1.61x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Promise.all with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.356s (-61.0% 🟢) 2.008s (-49.9% 🟢) 0.652s 15 1.00x
🐘 Postgres Express 1.366s (-60.8% 🟢) 2.007s (-49.9% 🟢) 0.641s 15 1.01x
🌐 Redis Next.js (Turbopack) 3.648s 4.010s 0.362s 8 2.69x
💻 Local Next.js (Turbopack) 4.277s 5.011s 0.734s 6 3.15x
💻 Local Nitro 4.394s (-47.4% 🟢) 5.011s (-44.5% 🟢) 0.617s 6 3.24x
💻 Local Express 5.281s (-36.7% 🟢) 5.680s (-37.1% 🟢) 0.399s 6 3.90x
🌐 MongoDB Next.js (Turbopack) 6.210s 7.013s 0.803s 5 4.58x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.449s (+54.6% 🔺) 7.072s (+27.8% 🔺) 1.624s 5 1.00x
▲ Vercel Next.js (Turbopack) 5.810s (-34.8% 🟢) 7.506s (-31.5% 🟢) 1.695s 5 1.07x
▲ Vercel Express 5.922s (+39.7% 🔺) 7.523s (+22.8% 🔺) 1.601s 4 1.09x

🔍 Observability: Nitro | Next.js (Turbopack) | Express

Promise.race with 10 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.157s (-8.0% 🟢) 2.009s (~) 0.852s 15 1.00x
🐘 Postgres Express 1.166s (-7.2% 🟢) 2.009s (~) 0.843s 15 1.01x
🌐 Redis Next.js (Turbopack) 1.248s 2.006s 0.758s 15 1.08x
💻 Local Next.js (Turbopack) 1.354s 2.005s 0.651s 15 1.17x
💻 Local Express 1.401s (-26.0% 🟢) 2.005s (-15.2% 🟢) 0.604s 15 1.21x
💻 Local Nitro 1.410s (-24.4% 🟢) 2.006s (-14.3% 🟢) 0.596s 15 1.22x
🌐 MongoDB Next.js (Turbopack) 2.020s 2.735s 0.714s 11 1.75x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.467s (~) 3.952s (-5.2% 🟢) 1.486s 8 1.00x
▲ Vercel Express 2.998s (+16.1% 🔺) 4.477s (+2.9%) 1.479s 7 1.22x
▲ Vercel Next.js (Turbopack) 4.452s (+51.9% 🔺) 5.647s (+21.6% 🔺) 1.195s 6 1.80x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.race with 25 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.231s (-47.4% 🟢) 2.009s (-33.3% 🟢) 0.778s 15 1.00x
🐘 Postgres Express 1.244s (-46.9% 🟢) 2.009s (-33.3% 🟢) 0.765s 15 1.01x
💻 Local Nitro 1.925s (-37.2% 🟢) 2.315s (-40.4% 🟢) 0.391s 13 1.56x
💻 Local Next.js (Turbopack) 2.016s 2.391s 0.375s 13 1.64x
💻 Local Express 2.060s (-34.2% 🟢) 2.468s (-34.4% 🟢) 0.408s 13 1.67x
🌐 Redis Next.js (Turbopack) 2.356s 3.008s 0.653s 10 1.91x
🌐 MongoDB Next.js (Turbopack) 3.564s 4.007s 0.444s 8 2.89x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.226s (~) 4.516s (-11.0% 🟢) 1.291s 7 1.00x
▲ Vercel Express 3.493s (+9.4% 🔺) 5.424s (+13.2% 🔺) 1.932s 6 1.08x
▲ Vercel Next.js (Turbopack) 4.603s (+46.5% 🔺) 5.938s (+31.3% 🔺) 1.335s 6 1.43x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

Promise.race with 50 concurrent steps

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.347s (-61.3% 🟢) 2.009s (-49.9% 🟢) 0.662s 15 1.00x
🐘 Postgres Express 1.371s (-60.8% 🟢) 2.008s (-49.9% 🟢) 0.636s 15 1.02x
🌐 Redis Next.js (Turbopack) 3.623s 4.010s 0.387s 8 2.69x
💻 Local Next.js (Turbopack) 4.507s 5.178s 0.671s 6 3.35x
💻 Local Nitro 4.570s (-50.0% 🟢) 5.180s (-48.3% 🟢) 0.610s 6 3.39x
💻 Local Express 5.631s (-36.0% 🟢) 6.180s (-33.4% 🟢) 0.549s 6 4.18x
🌐 MongoDB Next.js (Turbopack) 6.210s 7.010s 0.800s 5 4.61x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 4.692s (-26.9% 🟢) 6.252s (-23.6% 🟢) 1.559s 5 1.00x
▲ Vercel Nitro 4.868s (-4.4%) 6.359s (-6.7% 🟢) 1.491s 5 1.04x
▲ Vercel Next.js (Turbopack) 5.903s (-12.6% 🟢) 7.578s (-11.3% 🟢) 1.675s 4 1.26x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.459s (-44.0% 🟢) 1.024s (+1.7%) 0.565s 59 1.00x
🐘 Postgres Express 0.462s (-45.0% 🟢) 1.006s (-1.7%) 0.544s 60 1.01x
💻 Local Nitro 0.474s (-51.7% 🟢) 1.004s (-8.2% 🟢) 0.530s 60 1.03x
💻 Local Next.js (Turbopack) 0.555s 1.004s 0.449s 60 1.21x
💻 Local Express 0.565s (-42.6% 🟢) 1.088s (+1.1%) 0.523s 60 1.23x
🌐 MongoDB Next.js (Turbopack) 0.597s 1.005s 0.408s 60 1.30x
🌐 Redis Next.js (Turbopack) 0.621s 1.004s 0.383s 60 1.35x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 5.257s (-76.2% 🟢) 6.675s (-72.2% 🟢) 1.418s 9 1.00x
▲ Vercel Express 5.358s (-71.8% 🟢) 7.133s (-66.6% 🟢) 1.775s 9 1.02x
▲ Vercel Next.js (Turbopack) 7.643s (-47.3% 🟢) 9.025s (-43.9% 🟢) 1.381s 7 1.45x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 25 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.062s (-44.9% 🟢) 1.661s (-20.9% 🟢) 0.600s 55 1.00x
🐘 Postgres Express 1.108s (-43.9% 🟢) 1.844s (-18.3% 🟢) 0.736s 49 1.04x
💻 Local Nitro 1.175s (-61.3% 🟢) 2.005s (-46.6% 🟢) 0.831s 45 1.11x
💻 Local Express 1.207s (-60.0% 🟢) 2.006s (-44.1% 🟢) 0.799s 45 1.14x
💻 Local Next.js (Turbopack) 1.384s 2.005s 0.621s 45 1.30x
🌐 MongoDB Next.js (Turbopack) 1.490s 2.006s 0.516s 45 1.40x
🌐 Redis Next.js (Turbopack) 1.579s 2.028s 0.449s 45 1.49x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 13.297s (-61.5% 🟢) 15.190s (-58.7% 🟢) 1.892s 6 1.00x
▲ Vercel Nitro 14.217s (-64.0% 🟢) 15.754s (-61.9% 🟢) 1.537s 6 1.07x
▲ Vercel Next.js (Turbopack) 16.536s (-66.8% 🟢) 18.023s (-65.1% 🟢) 1.487s 5 1.24x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 50 sequential data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Express 2.088s (-47.7% 🟢) 2.661s (-39.1% 🟢) 0.573s 46 1.00x
🐘 Postgres Nitro 2.111s (-48.6% 🟢) 2.603s (-43.5% 🟢) 0.492s 47 1.01x
💻 Local Nitro 2.661s (-71.4% 🟢) 3.007s (-70.0% 🟢) 0.346s 40 1.27x
💻 Local Express 2.676s (-70.9% 🟢) 3.032s (-69.7% 🟢) 0.356s 40 1.28x
💻 Local Next.js (Turbopack) 3.004s 3.521s 0.517s 35 1.44x
🌐 Redis Next.js (Turbopack) 3.056s 3.705s 0.649s 33 1.46x
🌐 MongoDB Next.js (Turbopack) 3.446s 4.010s 0.564s 30 1.65x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 39.949s (-69.3% 🟢) 42.244s (-68.0% 🟢) 2.295s 3 1.00x
▲ Vercel Nitro 44.227s (-54.4% 🟢) 46.424s (-52.8% 🟢) 2.197s 3 1.11x
▲ Vercel Next.js (Turbopack) 48.690s (-54.6% 🟢) 50.218s (-53.9% 🟢) 1.528s 3 1.22x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 10 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.189s (-33.3% 🟢) 1.006s (~) 0.817s 60 1.00x
🐘 Postgres Express 0.198s (-29.7% 🟢) 1.006s (~) 0.807s 60 1.05x
🌐 Redis Next.js (Turbopack) 0.261s 1.004s 0.744s 60 1.38x
💻 Local Express 0.431s (-23.2% 🟢) 1.004s (~) 0.573s 60 2.28x
💻 Local Nitro 0.436s (-27.8% 🟢) 1.004s (-1.7%) 0.568s 60 2.31x
💻 Local Next.js (Turbopack) 0.496s 1.004s 0.508s 60 2.63x
🌐 MongoDB Next.js (Turbopack) 1.028s 1.799s 0.771s 34 5.44x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 2.184s (+11.7% 🔺) 3.808s (+4.7%) 1.624s 16 1.00x
▲ Vercel Nitro 2.220s (+33.7% 🔺) 3.735s (+11.5% 🔺) 1.515s 17 1.02x
▲ Vercel Next.js (Turbopack) 3.754s (+85.6% 🔺) 5.069s (+33.6% 🔺) 1.316s 12 1.72x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

workflow with 25 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.303s (-39.0% 🟢) 1.006s (~) 0.703s 90 1.00x
🐘 Postgres Express 0.314s (-38.4% 🟢) 1.006s (~) 0.692s 90 1.04x
🌐 Redis Next.js (Turbopack) 0.418s 1.004s 0.586s 90 1.38x
💻 Local Express 2.125s (-15.5% 🟢) 2.851s (-5.2% 🟢) 0.727s 32 7.01x
💻 Local Nitro 2.256s (-11.1% 🟢) 2.884s (-4.2%) 0.628s 32 7.45x
💻 Local Next.js (Turbopack) 2.318s 2.912s 0.594s 31 7.65x
🌐 MongoDB Next.js (Turbopack) 2.577s 3.006s 0.429s 30 8.51x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 8.597s (+166.5% 🔺) 10.154s (+110.6% 🔺) 1.557s 9 1.00x
▲ Vercel Express 8.636s (+183.5% 🔺) 10.390s (+116.1% 🔺) 1.754s 9 1.00x
▲ Vercel Next.js (Turbopack) 9.375s (+165.2% 🔺) 10.914s (+110.2% 🔺) 1.540s 9 1.09x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

workflow with 50 concurrent data payload steps (10KB)

💻 Local Development

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.610s (-22.8% 🟢) 1.006s (~) 0.396s 120 1.00x
🐘 Postgres Express 0.652s (-20.4% 🟢) 1.006s (-1.1%) 0.355s 120 1.07x
🌐 Redis Next.js (Turbopack) 0.769s 1.004s 0.235s 120 1.26x
🌐 MongoDB Next.js (Turbopack) 5.272s 6.011s 0.739s 20 8.65x
💻 Local Express 9.811s (-12.3% 🟢) 10.360s (-13.2% 🟢) 0.549s 12 16.09x
💻 Local Nitro 9.981s (-10.8% 🟢) 10.445s (-10.5% 🟢) 0.464s 12 16.37x
💻 Local Next.js (Turbopack) 10.397s 10.936s 0.538s 11 17.05x
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - -

▲ Production (Vercel)

World Framework Workflow Time Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Next.js (Turbopack) 17.435s (+68.8% 🔺) 19.021s (+54.8% 🔺) 1.587s 7 1.00x
▲ Vercel Nitro 22.706s (+194.0% 🔺) 24.508s (+160.7% 🔺) 1.802s 5 1.30x
▲ Vercel Express 23.765s (+220.3% 🔺) 25.541s (+176.3% 🔺) 1.776s 5 1.36x

🔍 Observability: Next.js (Turbopack) | Nitro | Express

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.129s (+428.2% 🔺) 2.005s (+99.6% 🔺) 0.010s (-16.8% 🟢) 2.018s (+98.0% 🔺) 0.889s 10 1.00x
💻 Local Express 1.129s (+467.1% 🔺) 2.005s (+99.6% 🔺) 0.012s (+0.8%) 2.019s (+98.3% 🔺) 0.890s 10 1.00x
💻 Local Next.js (Turbopack) 1.136s 2.002s 0.010s 2.016s 0.880s 10 1.01x
🐘 Postgres Nitro 1.136s (+454.1% 🔺) 1.999s (+99.9% 🔺) 0.001s (-26.7% 🟢) 2.009s (+98.7% 🔺) 0.873s 10 1.01x
🐘 Postgres Express 1.147s (+459.5% 🔺) 2.000s (+100.3% 🔺) 0.001s (-18.8% 🟢) 2.010s (+98.7% 🔺) 0.862s 10 1.02x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 2.188s (-42.9% 🟢) 3.221s (-39.0% 🟢) 2.121s (+185.8% 🔺) 5.752s (-11.3% 🟢) 3.564s 10 1.00x
▲ Vercel Express 2.254s (-10.0% 🟢) 3.337s (-18.4% 🟢) 1.734s (+80.5% 🔺) 5.506s (-1.5%) 3.252s 10 1.03x
▲ Vercel Next.js (Turbopack) 4.807s (-29.9% 🟢) 4.700s (-45.7% 🟢) 1.259s (+99.2% 🔺) 7.523s (-23.1% 🟢) 2.716s 10 2.20x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

stream pipeline with 5 transform steps (1MB)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 1.500s (+140.3% 🔺) 2.000s (+98.6% 🔺) 0.004s (-8.9% 🟢) 2.026s (+98.1% 🔺) 0.526s 30 1.00x
🐘 Postgres Express 1.532s (+143.1% 🔺) 2.000s (+98.7% 🔺) 0.004s (-0.8%) 2.024s (+97.9% 🔺) 0.493s 30 1.02x
💻 Local Next.js (Turbopack) 1.573s 2.010s 0.009s 2.022s 0.449s 30 1.05x
💻 Local Nitro 1.692s (+101.7% 🔺) 2.011s (+98.7% 🔺) 0.009s (-6.2% 🟢) 2.200s (+97.2% 🔺) 0.509s 28 1.13x
💻 Local Express 1.715s (+126.5% 🔺) 2.011s (+95.4% 🔺) 0.009s (+0.9%) 2.201s (+111.7% 🔺) 0.486s 28 1.14x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 6.067s (-6.7% 🟢) 7.280s (-9.1% 🟢) 0.274s (-32.9% 🟢) 8.021s (-9.2% 🟢) 1.954s 8 1.00x
▲ Vercel Nitro 6.238s (-78.8% 🟢) 7.553s (-75.5% 🟢) 0.239s (+113.6% 🔺) 8.177s (-74.3% 🟢) 1.939s 8 1.03x
▲ Vercel Next.js (Turbopack) 11.752s (-30.5% 🟢) 12.474s (-31.6% 🟢) 0.234s (+10.9% 🔺) 13.626s (-28.0% 🟢) 1.875s 5 1.94x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

10 parallel streams (1MB each)

💻 Local Development

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
🐘 Postgres 🥇 Nitro 0.670s (-30.8% 🟢) 1.034s (-17.1% 🟢) 0.000s (-57.9% 🟢) 1.057s (-15.9% 🟢) 0.387s 57 1.00x
🐘 Postgres Express 0.684s (-28.8% 🟢) 1.015s (-20.5% 🟢) 0.000s (+19.0% 🔺) 1.044s (-20.1% 🟢) 0.360s 58 1.02x
💻 Local Express 1.332s (+8.7% 🔺) 2.016s (~) 0.000s (+40.0% 🔺) 2.018s (~) 0.686s 30 1.99x
💻 Local Nitro 1.350s (+10.4% 🔺) 2.015s (~) 0.000s (+133.3% 🔺) 2.017s (~) 0.666s 30 2.01x
💻 Local Next.js (Turbopack) 1.390s 2.013s 0.000s 2.016s 0.625s 30 2.07x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Nitro 3.688s (+20.9% 🔺) 4.921s (+12.0% 🔺) 0.000s (-100.0% 🟢) 5.315s (+10.5% 🔺) 1.627s 12 1.00x
▲ Vercel Express 3.914s (+4.7%) 5.284s (+3.5%) 0.001s (+200.0% 🔺) 5.787s (+4.6%) 1.872s 11 1.06x
▲ Vercel Next.js (Turbopack) 5.914s (-41.9% 🟢) 6.070s (-47.3% 🟢) 0.001s (+Infinity% 🔺) 7.208s (-40.2% 🟢) 1.294s 9 1.60x

🔍 Observability: Nitro | Express | Next.js (Turbopack)

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.276s (-28.0% 🟢) 2.031s (-6.7% 🟢) 0.000s (+Infinity% 🔺) 2.047s (-6.9% 🟢) 0.771s 30 1.00x
🐘 Postgres Nitro 1.296s (-27.7% 🟢) 2.029s (-5.2% 🟢) 0.000s (-100.0% 🟢) 2.087s (-4.0%) 0.791s 29 1.02x
💻 Local Next.js (Turbopack) 2.798s 3.471s 0.001s 3.475s 0.678s 18 2.19x
💻 Local Nitro 2.978s (-12.1% 🟢) 3.619s (-10.2% 🟢) 0.001s (+120.6% 🔺) 3.623s (-10.2% 🟢) 0.645s 17 2.33x
💻 Local Express 3.065s (-11.6% 🟢) 3.776s (-6.4% 🟢) 0.001s (-14.1% 🟢) 3.782s (-6.3% 🟢) 0.717s 16 2.40x
🌐 MongoDB Next.js (Turbopack) ⚠️ missing - - - - -
🌐 Redis Next.js (Turbopack) ⚠️ missing - - - - -
🐘 Postgres Next.js (Turbopack) ⚠️ missing - - - - -

▲ Production (Vercel)

World Framework Workflow Time TTFB Slurp Wall Time Overhead Samples vs Fastest
▲ Vercel 🥇 Express 5.421s (+18.2% 🔺) 7.136s (+18.5% 🔺) 0.000s (+Infinity% 🔺) 7.540s (+16.8% 🔺) 2.119s 9 1.00x
▲ Vercel Nitro 5.468s (+33.6% 🔺) 6.784s (+26.2% 🔺) 0.001s (+185.2% 🔺) 7.156s (+23.5% 🔺) 1.688s 9 1.01x
▲ Vercel Next.js (Turbopack) 8.826s (+57.1% 🔺) 8.959s (+28.3% 🔺) 0.000s (+33.3% 🔺) 10.503s (+39.3% 🔺) 1.676s 6 1.63x

🔍 Observability: Express | Nitro | Next.js (Turbopack)

Summary

Fastest Framework by World

Winner determined by most benchmark wins

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

Winner determined by most benchmark wins

Framework 🥇 Fastest World Wins
Express 🐘 Postgres 17/21
Next.js (Turbopack) 💻 Local 15/21
Nitro 🐘 Postgres 15/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)

📋 View full workflow run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

🧪 E2E Test Results

All tests passed

Summary

Passed Failed Skipped Total
✅ ▲ Vercel Production 859 0 219 1078
✅ 💻 Local Development 957 0 219 1176
✅ 📦 Local Production 957 0 219 1176
✅ 🐘 Local Postgres 957 0 219 1176
✅ 🪟 Windows 98 0 0 98
✅ 📋 Other 510 0 176 686
Total 4338 0 1052 5390

Details by Category

✅ ▲ Vercel Production
App Passed Failed Skipped
✅ astro 72 0 26
✅ example 72 0 26
✅ express 72 0 26
✅ fastify 72 0 26
✅ hono 72 0 26
✅ nextjs-turbopack 96 0 2
✅ nextjs-webpack 96 0 2
✅ nitro 72 0 26
✅ nuxt 72 0 26
✅ sveltekit 91 0 7
✅ vite 72 0 26
✅ 💻 Local Development
App Passed Failed Skipped
✅ astro-stable 73 0 25
✅ express-stable 73 0 25
✅ fastify-stable 73 0 25
✅ hono-stable 73 0 25
✅ nextjs-turbopack-canary 79 0 19
✅ nextjs-turbopack-stable 98 0 0
✅ nextjs-webpack-canary 79 0 19
✅ nextjs-webpack-stable 98 0 0
✅ nitro-stable 73 0 25
✅ nuxt-stable 73 0 25
✅ sveltekit-stable 92 0 6
✅ vite-stable 73 0 25
✅ 📦 Local Production
App Passed Failed Skipped
✅ astro-stable 73 0 25
✅ express-stable 73 0 25
✅ fastify-stable 73 0 25
✅ hono-stable 73 0 25
✅ nextjs-turbopack-canary 79 0 19
✅ nextjs-turbopack-stable 98 0 0
✅ nextjs-webpack-canary 79 0 19
✅ nextjs-webpack-stable 98 0 0
✅ nitro-stable 73 0 25
✅ nuxt-stable 73 0 25
✅ sveltekit-stable 92 0 6
✅ vite-stable 73 0 25
✅ 🐘 Local Postgres
App Passed Failed Skipped
✅ astro-stable 73 0 25
✅ express-stable 73 0 25
✅ fastify-stable 73 0 25
✅ hono-stable 73 0 25
✅ nextjs-turbopack-canary 79 0 19
✅ nextjs-turbopack-stable 98 0 0
✅ nextjs-webpack-canary 79 0 19
✅ nextjs-webpack-stable 98 0 0
✅ nitro-stable 73 0 25
✅ nuxt-stable 73 0 25
✅ sveltekit-stable 92 0 6
✅ vite-stable 73 0 25
✅ 🪟 Windows
App Passed Failed Skipped
✅ nextjs-turbopack 98 0 0
✅ 📋 Other
App Passed Failed Skipped
✅ e2e-local-dev-nest-stable 73 0 25
✅ e2e-local-dev-tanstack-start-stable 73 0 25
✅ e2e-local-postgres-nest-stable 73 0 25
✅ e2e-local-postgres-tanstack-start-stable 73 0 25
✅ e2e-local-prod-nest-stable 73 0 25
✅ e2e-local-prod-tanstack-start-stable 73 0 25
✅ e2e-vercel-prod-tanstack-start 72 0 26

📋 View full workflow run

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Postgres-level deduplication guard for entity-creating events so concurrent (or repeated) creates with the same (runId, correlationId, type) surface as EntityConflictError, aligning world-postgres behavior with the runtime’s existing dedup contract.

Changes:

  • Add a partial unique index on workflow_events(run_id, correlation_id, type) for step_created / hook_created / wait_created.
  • Translate Postgres unique-violation (23505) during events.create() into EntityConflictError for entity-creating event types.
  • Add regression tests for duplicate step_created and wait_created scenarios.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/world-postgres/src/storage.ts Catch and translate unique-violation errors into EntityConflictError for entity-creating events.
packages/world-postgres/src/drizzle/schema.ts Add Drizzle schema definition for the partial unique index.
packages/world-postgres/src/drizzle/migrations/0010_add_events_entity_creation_unique_index.sql Create the partial unique index in SQL migration.
packages/world-postgres/src/drizzle/migrations/meta/_journal.json Register the new migration in the Drizzle journal.
packages/world-postgres/test/storage.test.ts Add tests asserting duplicate entity-creation attempts surface as EntityConflictError and don’t duplicate event rows.
.changeset/fix-world-postgres-events-uniqueness.md Publish a patch changeset describing the new uniqueness + error translation behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/world-postgres/src/storage.ts
Comment thread packages/world-postgres/test/storage.test.ts Outdated
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.

3 participants