Skip to content

CONCEPT Cited by 1 source

Per-tenant dynamic code dispatch

Definition

Per-tenant dynamic code dispatch is the runtime shape in which a platform hosts a single, statically-registered entry point, but routes each incoming invocation to a different body of tenant- authored code — loaded at runtime, sandboxed, cached by tenant ID, thrown away when idle. The tenant writes plain, idiomatic code against the platform's API; the platform's request path decides which tenant's code runs, fetches that code from an artefact store, spins up a sandbox, and forwards the call.

The general three-layer shape:

platform-level binding  →  dispatcher (you)  →  tenant code (your customer)

The dispatcher is the only piece that knows about tenants. The platform below it sees its normal one-class-per-deploy shape; the tenant above sees their normal single-tenant API. The dispatcher is where "every interesting line" of the bridging code lives. (Source: Cloudflare Dynamic Workflows.)

Why it matters

Until this pattern, multi-tenant platforms that wanted to let each tenant ship their own logic had two options, both bad:

  1. Container-per-tenant: every customer gets their own container + database + disk + scheduler + orchestration glue. Cost floor is high; idle tenants are expensive. Realistically caps out at thousands of paying customers.
  2. Eval-ing tenant code in the platform's process: fast and cheap, but no sandbox — one misbehaving tenant can corrupt others, hit unbounded memory, or steal secrets.

Per-tenant dynamic code dispatch is the third option: isolate- level multi-tenancy with capability-based sandboxing. Idle tenants cost approximately nothing; active tenants share hardware through lightweight V8 isolates; the platform can now "reasonably serve tens of millions" of tenants per Cloudflare's claim, though the exact unit economics are directional rather than measured. (Source: sources/2026-05-01-cloudflare-introducing-dynamic-workflows-durable-execution-that-follows-the-tenant.)

Instances on the wiki

  • Dynamic Workers (compute) — per-request V8 isolate minted from tenant code; Tier 1 of the execution ladder.
  • Durable Object Facets (storage) — per-tenant SQLite database spun up on demand, platform-as-supervisor in front.
  • Dynamic Workflows (durable execution) — per-tenant run(event, step) function dispatched into a Dynamic Worker on each step boundary; the Workflows engine's durability machinery (IDs, step.sleep(), step.waitForEvent(), retries, hibernation) continues to work unchanged.

Pre-announced as coming for: queues (each producer ships its own handler), caches, databases, object stores, AI bindings, MCP servers.

Structural requirements

To make this pattern work safely and efficiently, the substrate needs:

  1. Fast isolate boot. The dispatch hop must be cheap enough that spinning up the tenant's code lazily at the step boundary is effectively free. Cloudflare's Dynamic Workers boot in single-digit milliseconds with a few MB of memory; the dispatch overhead is "essentially free."
  2. Capability-based sandboxing. The tenant's code must start with no ambient authority — the dispatcher grants each capability (a specific fetch origin, a specific R2 bucket, a specific DO namespace) explicitly through bindings. See concepts/capability-based-sandbox.
  3. RPC across the boundary. Bindings that cross from the dispatcher into the tenant's isolate must be serialisable RPC stubs, not plain objects. For Workers this means WorkerEntrypoint subclasses; structured-clone doesn't work for closures.
  4. Cache-by-tenant-ID. A multi-step workflow that sleeps for 24 h and resumes should hit the same cached tenant isolate if it's still warm; if it's been evicted, reloading the code and continuing should be transparent to the tenant.
  5. Envelope-routed metadata for persistent invocations. For durable primitives (workflows, queues), the dispatcher must thread a routing metadata envelope through any payload the engine persists, so that when the engine wakes the instance hours later, the metadata rides along. See concepts/envelope-wrap-and-unwrap-metadata-routing.
  6. Routing metadata is not authorization. "Don't put secrets in there." The metadata is a routing hint; the isolation / authorization boundary remains the sandbox's capability model. (Source: sources/2026-05-01-cloudflare-introducing-dynamic-workflows-durable-execution-that-follows-the-tenant.)

Load-bearing design choices in Cloudflare's shape

  • The binding the tenant sees (env.WORKFLOWS, env.DB, etc.) is a specialised subclass that the runtime builds per tenant by looking up a class name in cloudflare:workers exports. That's why the Worker Loader must export the binding class.
  • The platform API (e.g. the real Workflow engine, the real DO substrate) is left completely unchanged — the dispatcher is 100% additive glue. New dynamic-binding shapes are deliberately "envelope-and-unwrap glue between the static binding you've always had and the dynamic version you can now hand to your customers." (Source: sources/2026-05-01-cloudflare-introducing-dynamic-workflows-durable-execution-that-follows-the-tenant.)

Distinction from adjacent shapes

  • Not just plugin loading. Plugins (Postgres extensions, Jenkins plugins, WordPress plugins) typically run in the host's process with ambient authority, relying on a revocable- cap firewall to keep them contained. Per-tenant dynamic code dispatch starts from no-ambient-authority and grants capabilities explicitly.
  • Not just serverless-eval. AWS Lambda + container-per- tenant gives isolation but at VM-sized cost. The load-bearing difference here is isolate-level density.
  • Not just multi-tenant SaaS. Classic multi-tenant SaaS ships a single codebase that all tenants execute, usually with tenant IDs on rows. Per-tenant dynamic code dispatch runs different code per tenant.
  • Not just function-as-a-service with per-function sandboxing. FaaS is typically you ship one function; dispatch decides which instance runs it. Here, dispatch decides which function body runs — the code itself is the dynamic artefact.

Seen in

Last updated · 438 distilled / 1,268 read