Practical comparison: database-per-tenant vs shared-schema (what to pick and why)
Long-form technical posts can be a lot. Below I break this topic into small, actionable pieces with war stories, a checklist, and a final scorecard so you can decide quickly.
Quick scenario β context before choices
Imagine youβre building a SaaS that will start with a handful of customers but aims to serve thousands in 2 years. Some customers will have strict data isolation requirements (finance, healthcare), others will be tiny free-tier tenants. How do you design the data layer?
This is where most systems break in production.
What “DB-per-tenant” actually buys you
- Isolation: noisy neighbor problems are physical β each DB is separate.
- Tailored ops: per-tenant backups, restores, and scaling.
- Security: easier to satisfy strict compliance for specific tenants.
What “Shared-schema” actually buys you
- Lower cost: fewer database instances to manage.
- Simpler deployment: single migration, single schema versioning flow.
- Easier analytics: cross-tenant queries are trivial.
What NOT to do (short anti-pattern list)
Micro-punchline
Choose per-tenant for isolation; choose shared-schema for speed of development and cost. Nothing magical β just trade-offs.
Implementation considerations (practical checklist)
- β Tenant identification: How does your app resolve tenant context? (subdomain, header, token claim)
- β Migrations: Can you run safe zero-downtime migrations across many DBs?
- β Backups & restores: Can you restore a single tenant quickly?
- β Monitoring & cost: Do you have alerts per database? Cost per tenant tracked?
Code patterns β sample switch (C#)
Simple factory that returns a connection string based on tenant id (very simplified):
// resolve tenant id from request context string tenantId = TenantContext.Current.Id; // pick strategy if (TenantRegistry.IsDedicated(tenantId)) { connectionString = TenantRegistry.GetDedicatedConnection(tenantId); } else { connectionString = SharedDatabase.ConnectionString; } // use DbContextOptionsBuilder with that connection string var options = new DbContextOptionsBuilder<AppDbContext>() .UseSqlServer(connectionString) .Options;
Migration strategies
With DB-per-tenant you must run migrations on N databases. Options:
- Central orchestration: a service that attempts to apply migrations sequentially or in parallel with backoff.
- Blue-green DBs: create a new DB, warm it, then cut over (complex but safer).
- Feature flags + tenant grouping: gradually migrate tenants and roll back if issues occur.
Performance tips
- Index per tenant if patterns differ per tenant.
- Use read-replicas for heavy-read tenants (DB-per-tenant enables per-tenant replicas).
- Consider caching layers (Redis) with tenant-scoped keys:
cache:{tenantId}:user:123.
Short war story
Mini checklist β when to choose DB-per-tenant
- β Tenant requires strict compliance or physical separation
- β Tenant’s workload is large and unpredictable
- β You need per-tenant backup/restore
Mini checklist β when to choose shared-schema
- β Most tenants are small and homogeneous
- β You want fast onboarding and low infra cost
- β Cross-tenant analytics are important
What to monitor (operational)
- Per-tenant DB CPU and I/O
- Slow query trends per tenant
- Cost per tenant (broken down daily)
- Number of connections per tenant
See also
I covered safe EF Core migrations and connection pooling in previous posts β worth a quick read if you’re implementing multi-tenant DBs.
βGood architecture is not about making perfect choices. Itβs about managing trade-offs wisely.β
β Unknown Architect
Final scorecard (quick comparison)
| Factor | DB-per-tenant | Shared-schema |
|---|---|---|
| Isolation | βββββ | βββ |
| Complexity | ββ | ββββ |
| Scale | βββββ | βββ |
| Cost | ββ | ββββ |
Closing thoughts
There is no one-size-fits-all. Start with the simplest approach that meets SLAs and be prepared to carve out specific tenants to dedicated infrastructure as they grow. The operational burden is real β plan migrations, monitoring, and cost-tracking ahead of time.
Actionable next steps (3-minute checklist)
- Instrument per-tenant metrics now (CPU, I/O, requests).
- Create a migration playbook for moving a tenant to a dedicated DB.
- Add a query-cost watchdog to detect expensive ad-hoc reports.
Enjoyed this? If you want I can convert this into a multi-part series with diagrams and per-tenant migration scripts.