Multi-Tenant Data Isolation
Tenancy is a spectrum between sharing everything and sharing nothing. The framework selects points on that spectrum deliberately, and then enforces the chosen boundary at multiple layers so a single bug is not a breach.
How it works
In the pooled model, all tenants share tables and every row has a tenant id. In the bridge model, each tenant has its own schema in a shared database. In the silo model, each tenant has its own database or cluster. Requests carry a verified tenant context, derived from the credential rather than from client-supplied parameters, and a data access layer applies it to every query.
Implementation steps
- Segment customers by risk, size, and compliance needs, and map segments to isolation tiers.
- Derive tenant context from the authenticated identity and propagate it through services, queues, and background jobs.
- In pooled storage, make the tenant id the leading column of primary keys and indexes, and enable row-level security with a session setting for the current tenant.
- Use a tenant-aware repository layer so raw queries without tenant context are impossible or fail tests.
- Add per-tenant rate limits and resource quotas to contain noisy neighbours.
- Encrypt with per-tenant keys for sensitive tiers, enabling cryptographic deletion.
- Automate tenant provisioning, export, tier migration, and offboarding.
Common pitfalls
- Trusting a tenant id sent by the client.
- Forgetting tenant scoping in caches, search indexes, file storage, and logs.
- Background jobs that run without tenant context and read everything.
- Unique constraints that are global rather than per tenant.
- Testing only the success path and never attempting cross-tenant access.
Metrics to watch
- Cross-tenant access test failures in CI.
- Queries executed without tenant context.
- Per-tenant resource consumption and throttling events.
- Tenant provisioning and migration duration.
- Share of tenants per isolation tier.
Example configuration
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.tenant_id')::uuid);
CREATE UNIQUE INDEX invoices_number_per_tenant
ON invoices (tenant_id, invoice_number);
Practical rollout
Make isolation testable. Build an automated suite that authenticates as tenant A and tries to read, update, and delete tenant B resources through every API and background path, and run it on each release. Review new data stores, caches, and file buckets for tenant scoping as part of design approval. Keep a documented procedure for promoting a tenant to a stronger tier, since customer growth or contracts will eventually require it.
Key takeaways
- Offer isolation tiers rather than a single model.
- Enforce tenancy in the database as well as in application code.
- Scope caches, indexes, jobs, and storage, not just tables.
- Test cross-tenant access explicitly and continuously.