World / Cloudflare
GitHub

Inside the World

Shards and placement

Spread independent runs, preserve old routing, and keep state near execution where possible. More shards are capacity, not free speed.

Routing without moving old runsIllustrative hash = 109
0123456789101112131415
Old run → epoch A → 109 % 16 = 13, unchanged.
New run → epoch B → 109 % 16 = 13.
The ULID time selects the epoch first. Changing a historical epoch would route old runs to the wrong objects.

Time picks the epoch, hash picks the shard

The default epoch has 128 store shards, 256 queue shards and 16 index shards. The larger staging epoch used for recent tests has 256 stores and 512 queues. These are configured counts, not platform limits.

The routing function takes the run’s ULID time, finds the applicable epoch, then hashes the run ID with FNV-1a to select a store. Newer epochs have distinct object-name prefixes. Dispatched steps use their keys to spread delivery work.

Illustrative SHARD_EPOCHS value
[
  { "from": 0, "store": 128, "queue": 256, "index": 16 },
  { "from": 1800000000000, "store": 256, "queue": 512, "index": 16 }
]

Append only. Deploy consistent routing to every relevant Worker before the new epoch’s activation time. Editing an old epoch does not migrate data; it changes where callers look for it.

A location hint is a hint

LOCATION_HINT requests a placement region for newly created objects. It does not guarantee residency, the same data center, or that an existing object moves when the setting changes.

The store first touches the workflow runner without a hint, favoring placement near the store. This improves the chance that repeated state/execution calls are short. It does not prove physical colocation.

Sequential probes vary substantially across run placements. Recent 2k-step samples ranged from about 130 to 395 seconds. A run can have little VM work yet spend most of its time between sequential state calls.

Why creation throughput is high

Independent starts distribute across store and queue shards. There is no global directory lookup, and new-run listing updates are not on the critical acknowledgment path. Bindings avoid an application-managed HTTP authentication layer between our components.

The load generator ran on Workers with the same region hint as the state, avoiding a laptop-to-region round trip per request. That exposes backend admission capacity better than a distant single client.

Many runs scale better than one enormous run

A single run still has one ordered store, replay state and a per-isolate memory budget. More queue shards can fan its steps out, but every completion eventually returns to that ordered state.

Request-scoped batching removed quadratic response amplification during first fan-out. A turn gathers up to 500 items per call, with two calls in flight, and stale reports are bounded. A current QuickJS sample passed 30k parallel steps; 35k hit executor memory limits. Batching does not remove the isolate budget.

Use independent runs for independent work. Do not treat “no configured event cap” as unlimited throughput or unlimited memory in one run.

Sources and implementation

Repository links may require access. Vercel links point to public source or published documentation. Logical API names do not establish backend internals.

Browse topics or type to search

IntroductionA Workflow SDK v5 World built on Cloudflare Workers, Durable Objects and R2. Here is what runs where, why each part exists, and what the measurements actually show.Same SDK, different infrastructureA workflow is a log you can replay.Three jobs, kept separateA workflow is a log you can replay.Recorded results, not promisesA workflow is a log you can replay.Read the system in orderA workflow is a log you can replay.The architectureAn ordered store for each run, delivery capacity shared across shards, and execution kept off the queue objects.Where everything livesThe architectureOrder state, distribute executionThe architectureRouting without a global directoryThe architectureWhat the design buys, and costsThe architectureLife of a requestFollow the ordinary static-workflow start path, then the deliveries that turn the stored intent into completed work.Our World: accepting the runLife of a requestOur World: delivering and runningLife of a requestVercel: the corresponding pathLife of a requestWhat a response does and does not guaranteeLife of a requestEvery service, explainedWhat each component owns, why it exists, how it helps, and the limit it introduces. These are logical roles, not twelve separate deployed services.Router Worker and World clientEvery service, explainedRun-store shardsEvery service, explainedQueue shardsEvery service, explainedWorkflow runners and step runnersEvery service, explainedConsumer and retained executor WorkersEvery service, explainedIndex shardsEvery service, explainedHook token registriesEvery service, explainedRun streams and VM snapshotsEvery service, explainedR2 payloads and dead lettersEvery service, explainedAlarms and cron recoveryEvery service, explainedState and deliveryHow an event becomes durable work, why the outbox exists, and where transactions stop.The event is the durable recordState and deliveryWhy commit an outbox?State and deliveryHook claims cross transaction boundariesState and deliveryAt-least-once means side effects can repeatState and deliveryRetries, limits and recoveryState and deliveryExecution and replayWorkflow decisions execute inside a deterministic runtime. Step bodies do the I/O outside it. QuickJS and the native engine trade replay work for memory and compatibility.Workflow code is not step codeExecution and replayQuickJS is our defaultExecution and replayThe native engine uses a build-time shimExecution and replayWhat the replay numbers meanExecution and replayShards and placementSpread independent runs, preserve old routing, and keep state near execution where possible. More shards are capacity, not free speed.Time picks the epoch, hash picks the shardShards and placementA location hint is a hintShards and placementWhy creation throughput is highShards and placementMany runs scale better than one enormous runShards and placementPayloads and byte streamsUse values for control data and byte streams for bulk data. Offloading to R2 removes storage-message pressure, not every memory limit.A value still has to fit in memoryPayloads and byte streamsPass a handle, then move bytes incrementallyPayloads and byte streamsThe producer must outlive the step resultPayloads and byte streamsThe current multi-GiB reader problemPayloads and byte streamsCompared with VercelTwo implementations of the same World contract. We can inspect Vercel’s public adapter, not the internals behind its managed APIs.The services Vercel’s public client usesCompared with VercelThe same jobs, different ownersCompared with VercelHow their transport worksCompared with VercelWhere our design helpsCompared with VercelRead the Vercel limits with their runtime contextCompared with VercelMeasurements and limitsRecorded Workers results, the workload behind each number, and the failures that keep these from being unlimited-capacity claims.Latest regression checkMeasurements and limitsCreation is not completionMeasurements and limitsExecution resultsMeasurements and limitsLimits are not all the same kind of numberMeasurements and limitsKnown failure pointsMeasurements and limitsRun the probesMeasurements and limitsIdentity and deploymentsBindings authorize internal access. Your application protects public routes. Encryption and retained release routing solve separate problems.Internal calls use capabilitiesIdentity and deploymentsEncryption has a narrower scope than “all data”Identity and deploymentsKeep the code that owns old historyIdentity and deploymentsHow Vercel’s identity differsIdentity and deploymentsBuild, deploy and operateThe repository root is an example application. The reusable World lives in packages/world-cloudflare. Deploy the minimal mode first, then add retained executors if you need them.Start with the exampleBuild, deploy and operateKnow which config changes affect old runsBuild, deploy and operateDeploy state first, then the retained executorBuild, deploy and operateTest the adapter, the SDK and the deploymentBuild, deploy and operateOperational work still leftBuild, deploy and operate
Local search. Nothing sent to a server.Esc to close
Documentation