Multi-Tenant SaaS on Azure: Pool vs Silo, Honestly Compared
The multi-tenancy decision is a commitment, not a setting. Pool (one database, tenant column) is cheapest and easiest until your largest customer demands their own region. Silo (one stack per tenant) is clean isolation and operational nightmare at scale. Bridge (shared compute, per-tenant data) tries to split the difference.
The Three Patterns
- Pool. One shared stack. Database, compute, storage. Tenants are separated by a column (
tenantId) on every row. - Silo. One complete stack per tenant. Separate database, separate compute, often separate resource group.
- Bridge. Shared compute, per-tenant data store (usually a separate Azure SQL database per tenant behind a shared app).
When Pool Wins
- Most tenants are small; the long tail dominates your economics.
- No per-tenant compliance or residency requirements.
- Shared features (search indexes, aggregations) genuinely benefit from cross-tenant data.
- Deploy velocity is a competitive advantage. One schema change, all tenants.
Pool's real cost is the discipline it demands. Every query must filter by tenantId; one missing filter is a cross-tenant data leak. Enforce it at the ORM layer with a row-level-security-like middleware, not in individual queries.
// Prisma extension that enforces tenant filter on every read/write
export function withTenantScope(tenantId: string) {
return prisma.$extends({
query: {
$allModels: {
async $allOperations({ model, operation, args, query }) {
const MULTI_TENANT_MODELS = ['Invoice', 'Customer', 'Project'];
if (!MULTI_TENANT_MODELS.includes(model!)) return query(args);
if (['findUnique', 'findFirst', 'findMany', 'count'].includes(operation)) {
args.where = { ...args.where, tenantId };
}
if (['create'].includes(operation)) {
args.data = { ...args.data, tenantId };
}
if (['update', 'updateMany', 'delete', 'deleteMany'].includes(operation)) {
args.where = { ...args.where, tenantId };
}
return query(args);
},
},
},
});
}
When Silo Wins
- A tenant requires data residency in a specific Azure region other than the pool's home.
- A tenant has regulatory obligations you do not want to inherit org-wide.
- A tenant pays enough to cover the operational overhead of their own stack.
- Blast radius of a bad deploy must not affect other tenants.
Silo's hidden cost is deploy and observability. Shipping a schema change to 50 silos in parallel, without breaking the order of operations, requires automation that most teams underestimate. If you cannot deploy to a silo as easily as to the pool, you will ship less often. And you will feel the drag.
When Bridge Is the Right Compromise
Bridge gives you per-tenant database isolation without per-tenant compute operations. Azure SQL Elastic Pools make this economical. You share compute across a set of per-tenant databases, with per-tenant backups, restores, and point-in-time recovery. The tradeoff is that each tenant's database gets its own connection string, and the application must route queries to the right database at request time.
// Bridge pattern — per-tenant Prisma client, cached by tenant ID
const clients = new Map<string, PrismaClient>();
export function prismaFor(tenantId: string) {
let client = clients.get(tenantId);
if (!client) {
const url = resolveTenantDbUrl(tenantId); // secret-store lookup
client = new PrismaClient({ datasources: { db: { url } } });
clients.set(tenantId, client);
}
return client;
}
The Cost Model, Honestly
- Pool cost scales sub-linearly with tenant count. New tenants are nearly free to add.
- Silo cost scales linearly. Every tenant brings a floor of fixed costs.
- Bridge with elastic pools is between the two. Not as cheap as pool, much cheaper than pure silo.
Noisy Neighbours
Pool's weakness is noisy neighbours: one tenant's heavy report run slows everyone. Mitigations include read-replica routing for analytics, per-tenant rate limiting, and tenant-aware connection-pool fairness. Plan for this at the query-planning stage, not the production-incident stage.
The Starting Point That Ages Well
Start with pool. Invest in tenant-aware querying, observability, and rate-limiting from day one. Move specific tenants to silos or bridge when. And only when. The pool's tradeoffs become a deal problem. The worst outcome is committing to silos early, before you know which deals you are chasing; you pay the isolation tax for customers who did not need it.