- A tenancy model with pooled and dedicated tiers, chosen per tenant rather than per platform
- Per-tenant limits that stop one tenant's load from degrading others
- Tenant context carried end to end, so usage, cost and incidents are visible per tenant
- Isolation evidence produced by automated tests, ready for enterprise security reviews
Anonymised composite. Client names, identifying details and confidential figures are withheld; public facts are cited in the references.
The situation
A B2B SaaS platform had grown on a single shared architecture: one application deployment, one database cluster, every tenant in the same collections. That served it well while tenants were similar in size. As larger customers arrived, two problems appeared together. A heavy import or reporting job from one tenant could slow the service for everyone. And enterprise prospects began asking, in security reviews, how their data was isolated and whether they could have dedicated infrastructure.
The team's instinct was to give big customers their own copy of everything. That answers the sales question and creates an operations problem: many stacks to upgrade, monitor and pay for.
What had to be true
- Isolation as a choice per tenant, not one model forced on all.
- One codebase and one release process across every tier.
- Per-tenant limits at every shared resource.
- Tenant context everywhere: in routing, data access, logs, traces and cost.
- Isolation proven by tests, so the answer to a security review is evidence.
Architecture
AWS's tenant isolation guidance frames the options as silo (dedicated resources), pool (shared resources) and bridge (a mix chosen per layer)1. The design adopted that vocabulary directly: a pooled tier for most tenants and a dedicated tier for those whose requirements justify it, running the same services.
The data flow, end to end
- AuthenticateToken carries the tenant identifier; nothing downstream trusts a header alone
- RouteGateway looks up the tenant's tier and sends the request to pooled or dedicated services
- LimitPer-tenant rate and concurrency limits applied before work starts
- Access dataData layer injects the tenant filter or selects the tenant's database
- ObserveTenant context propagated on traces, metrics and logs
- AccountUsage aggregated per tenant for capacity, cost and plan enforcement
On the data side, MongoDB's guidance compares database-per-tenant with shared collections keyed by a tenant field, and notes that the shared model needs every query to filter by tenant3. That requirement was moved out of individual queries and into one data access layer. Detail in MongoDB multi-tenant SaaS patterns.
The hard part: noisy neighbours at every layer
Noisy neighbours are rarely one bottleneck. Limits had to exist at each shared resource, or load simply moved to the next one. The Kubernetes documentation treats noisy neighbours as a core multi-tenancy concern and points to resource quotas and node isolation as controls2; quotas cap aggregate consumption per namespace5. The layers addressed were:
- Requests: per-tenant rate and concurrency limits at the gateway.
- Background work: per-tenant queues or fair scheduling, so one tenant's bulk import cannot starve others.
- Compute: dedicated namespaces with quotas for the dedicated tier; resource requests and limits in the pool.
- Database: indexes that start with the tenant key, query timeouts, and heavy reporting moved off the primary path.
None of that is manageable without seeing it per tenant. OpenTelemetry baggage carries key-value context such as a tenant identifier across service boundaries4, which let traces and metrics be broken down by tenant. More in Kubernetes multi-tenancy.
Isolation you cannot test is a claim. Isolation with a failing test when it breaks is a control.
How the engagement ran
- Define tier offers
- Agree who qualifies
- Use isolation evidence
- Tenant context and data layer
- Limits at each layer
- Isolation test suite
- Provision dedicated tier
- Per-tenant dashboards
- Migrate tenants between tiers
- Shared resource inventory
- Noisy neighbour incidents
- Enterprise requirements
- Tenant context end to end
- Data access layer
- Per-tenant observability
- Gateway limits
- Fair background work
- Dedicated tier provisioning
- Isolation tests in CI
- Tier migration runbook
- Security review pack
Key decisions and trade-offs
| Decision | Alternative | Why |
|---|---|---|
| Pooled default, dedicated by exception | A stack per enterprise tenant | Isolation where it pays, without multiplying operations |
| One codebase across tiers | Forked enterprise edition | One release process; no drift between tiers |
| Tenant filter in a data access layer | Filter in each query | One place to enforce and test |
| Gateway API for routing | Routing logic in each service | Tier routing declared at the edge |
| Isolation tests in CI | Annual manual review | Continuous evidence; regressions caught before release |
What I'd do again, and differently
Again: get tenant context right before anything else, and make isolation a test suite rather than a document. Differently: agree the tier definitions with product and sales earlier. The engineering was straightforward once everyone agreed what "dedicated" promised and who could buy it.
If your platform is facing the same pressures, the fractional CTO page describes how I work with teams on it, or analyse your challenge and bring the result to a call.
- 1SaaS Tenant Isolation Strategies: core isolation conceptsAmazon Web Services
- 2Multi-tenancyKubernetes
- 3
- 4BaggageOpenTelemetry
- 5Resource QuotasKubernetes