Chapter 17 · Security, Privacy, Governance, and Auditability
Tenant Isolation and Ownership Boundaries
Model tenant ownership explicitly and compare shared-schema, separate-schema, and separate-database isolation patterns while preventing cross-tenant access.
Learning outcomes
In a multi-tenant application, many customer organizations share one software platform. The database must preserve a critical invariant: one tenant must not read, modify, or infer another tenant's data unless explicitly authorized. Tenant isolation must be visible in keys, foreign keys, indexes, queries, and authorization rules.
Compare database-per-tenant, schema-per-tenant, and shared-schema designs.
Model tenant_id as part of ownership.
Prevent cross-tenant foreign-key and uniqueness mistakes.
Design defense-in-depth with row-level security and testing.
Three common isolation models
| Pattern | Isolation | Operational cost |
|---|---|---|
| Database per tenant | Strong physical/logical | High at many tenants |
| Schema per tenant | Strong namespace isolation | Migration complexity |
| Shared tables + tenant_id | Logical/policy isolation | Operationally efficient |
Shared-schema baseline
Customer( tenant_id, customer_id, ...)Every tenant-owned row needs an unambiguous tenant owner.
Tenant ownership propagates
You can derive tenant ownership through joins, but storing tenant_id on hot child tables often simplifies isolation, partitioning, and indexing.
Composite keys for tenant safety
PRIMARY KEY (tenant_id, customer_id)FOREIGN KEY (tenant_id, customer_id)REFERENCES Customer(tenant_id, customer_id)This prevents a row from pairing tenant A with customer_id belonging to tenant B.
A foreign key on customer_id alone may be structurally valid yet insufficient for tenant ownership.
Global surrogate IDs
If customer_id is globally unique, tenant_id may not be needed in the PK, but tenant ownership still needs enforcement. Options include composite unique constraints and tenant-aware FKs.
Tenant-scoped uniqueness
UNIQUE (tenant_id, work_order_number)UNIQUE (tenant_id, sku)allows different tenants to use the same business code while preserving uniqueness inside each tenant.
Global uniqueness when required
Some values, such as externally issued tokens or shared infrastructure names, may need global uniqueness. Do not mechanically prefix every constraint with tenant_id; choose the domain scope.
Tenant-aware indexes
(tenant_id, customer_id)(tenant_id, status_code, opened_at)Tenant-first indexes often align with application predicates and keep access inside one tenant range.
Always scope queries
Dangerous:
SELECT * FROM work_orderWHERE work_order_id = :id;Safer:
SELECT * FROM work_orderWHERE tenant_id = :tenant AND work_order_id = :id;Row-level security
A DB policy can add another layer:
tenant_id = current_setting('app.tenant_id')Exact syntax varies by DBMS. Connection-pool context must be set/reset safely.
Connection pooling hazard
If tenant context persists on a reused connection, the next request can inherit the wrong tenant. Context-setting and cleanup must be deterministic.
Background jobs
Jobs often process many tenants. They need explicit tenant iteration and should not bypass isolation accidentally just because they run with elevated credentials.
Support/admin access
Cross-tenant support access should be:
- separate from normal tenant sessions;
- strongly authorized;
- audited;
- time-limited where practical.
Tenant deletion/export
Explicit tenant_id makes:
export tenant 17delete tenant 17reconcile tenant 17far easier than discovering ownership indirectly across dozens of tables.
Partitioning by tenant
Large systems may partition or shard using tenant_id. This can improve locality and isolation but creates hot-tenant and rebalancing considerations.
Noisy-neighbor problem
Security isolation does not guarantee performance isolation. One tenant with extreme workload can consume shared CPU/I/O. Rate limits, resource governance, or separate placement may be needed.
WorkshopHub tenant model
| Entity | Tenant rule |
|---|---|
| Customer | Direct tenant owner |
| Asset | Must belong to Customer in same tenant |
| WorkOrder | Must belong to Asset in same tenant |
| Part | Could be tenant-local or global catalog—decide explicitly |
| Technician | Tenant-local, shared, or multi-tenant employment must be modeled intentionally |
Isolation tests
Automate tests that attempt:
- read A's row using B's session;
- create B child referencing A parent;
- update by ID without tenant predicate;
- reuse pooled connection after tenant switch;
- run export/delete for one tenant only.
Practice: cross-tenant FK
Asset owner
Customer IDs are only unique within each tenant. Asset stores tenant_id and customer_id. What FK should it use?
Review answer
Use a composite foreign key (tenant_id, customer_id) referencing the Customer key/unique constraint with the same columns. Referencing customer_id alone cannot distinguish tenants.
Summary and next lesson
Tenant isolation is a schema invariant, not just a WHERE-clause convention. Tenant-aware keys, foreign keys, indexes, scoped queries, row-level policies, and explicit admin pathways provide defense in depth. The next lesson turns to auditability and provenance.
References
- DBMS documentation on row-level security and roles.
- OWASP multi-tenant application security guidance.
- Cloud/database architecture guidance for tenant isolation models.