Core Concepts

Tenancy

How a tenant identity flows through every request, query, cache key, and capacity counter to keep tenants isolated.

Ductor is multi-tenant from the ground up. A tenant identity isn't bolted on at the API edge — it flows through every layer, so isolation is a property of the whole system rather than a check in one place. This page explains where tenancy lives; Entitlements covers what a tenant is allowed to do once it's isolated.

Tenancy is an end-to-end invariant

Every object Ductor persists is tenant-scoped: pools, rules, connections, workflow definitions, and every workflow run. The tenant identity travels with:

  • Every request — carried from the transport boundary through the application layer.
  • Every workflow tick — a run's tenant is loaded with its state and stays attached through the whole tick.
  • Every storage query — reads and writes filter on the tenant.
  • Every cache key — cached pools and rules are keyed by tenant, so one tenant's cache can't serve another's data.
  • Every capacity counter — the atomic Redis counters that enforce concurrency and rate limits are per-tenant.

Because the scoping is uniform, there's no single "did we remember to filter by tenant here?" gap. Isolation holds at storage, at cache, and at the hot path simultaneously.

Why isolation comes first

Tenant isolation is the precondition for everything in Entitlements. A quota only means something if one tenant's usage can't be charged to another; a plan ceiling only holds if load from tenant A can't consume tenant B's capacity. So the ordering is deliberate: isolate first, then enforce limits on top of clean boundaries.

The fair-scheduling connection

Tenancy also drives fairness, not just isolation. The tiered fair queue schedules work tenant-first: it picks the earliest-due tenant, then that tenant's earliest work, so a tenant with thousands of partitions can't starve a tenant with a handful. Fairness and isolation are two faces of the same tenant scoping.

System tenant

Not all work belongs to a customer. Internal machinery — most visibly coordinator wakeups — runs under a reserved system tenant on a dedicated partition. This keeps engine-internal traffic from competing with customer work in the fair scheduler and keeps the "every item has a tenant" invariant intact even for infrastructure tasks.

Run lineage within a tenant

Sub-workflows and ContinueAsNew create related runs, and that lineage is tracked without ever crossing a tenant boundary. A run carries its parent_run_id, root_run_id, and nesting_depth, so a tree of child runs and a chain of ContinueAsNew successors all stay attributable to one tenant and one originating request. See The DAG Workflow Model.

One identity, everywhere

The practical rule: if a piece of data or a unit of work exists in Ductor, it has a tenant. Storage rows, cache entries, capacity counters, and queue items all carry one — which is what makes "tenant isolation" a structural guarantee rather than a review checklist.

Where to go next