Salesforce's published description of its multi-tenant architecture (SIGMOD 2009) stores every tenant's records in shared physical tables with generic columns, and keeps each tenant's object definitions, fields and relationships as metadata interpreted at runtime - rather than giving each tenant its own schema. What forced that choice, what did it cost, and where would copying it be a mistake?
Show the full answer Hide the answer
The situation they were in
A platform where every customer can add their own objects and fields, and where the vendor must upgrade all customers at once. Those two requirements together rule out per-tenant schemas. With tens of thousands of tenants each holding custom objects, a per-tenant-schema design means tens of thousands of distinct schemas, and every platform upgrade becomes a schema migration executed that many times, any one of which can fail.
The documented design inverts it: data lives in a small number of wide, generic shared tables, and what the data means is metadata. A tenant's custom field is a row in a metadata table plus a position in a generic column, not a column in a table of their own.
What they chose, and why it fit
- No per-tenant DDL. Creating a custom field is a metadata insert, which is why it is instant
and self-service. In a per-tenant-schema world it is an
ALTER TABLEwith a lock and a maintenance window. - One physical schema to upgrade. The platform can ship a change to every tenant simultaneously because there is one structure, not many.
- Pivot-style index tables rather than per-column indexes, since a generic column holds different types for different tenants. Queries are compiled from metadata against those structures.
- Tenant id as the leading key everywhere, which makes tenant isolation a property of every query and makes a missing predicate a correctness bug rather than a performance one.
What it cost them
The query optimiser cannot see the shape of the data. Statistics on a generic column pooled across tenants are close to meaningless, so the platform maintains its own tenant-level statistics and does its own planning. That is a database engine's worth of work the team now owns.
Everything flows through the metadata interpreter. No team can hand-write a direct SQL query against the storage, so tooling, reporting and bulk operations must all be built on the platform's own abstractions. The abstraction's coverage becomes a hard limit on what anyone can express.
Noisy neighbours are structural, because tenants share tables and share the buffer pool. The answer is governor limits — hard per-tenant caps on query time, rows scanned, calls per day — which is why a platform with this architecture is also a platform with conspicuous limits. The limits are not stinginess; they are the isolation mechanism.
Where copying it would be a mistake
If your tenants do not define their own schema, you are paying the entire cost for nothing. A B2B SaaS product with a fixed data model should use ordinary tables with a tenant id and an index on it, and should reach for separate databases per tenant when isolation or data residency demands it.
If you have tens of tenants rather than tens of thousands, per-tenant schemas or per-tenant databases are simpler, give real isolation, allow per-tenant restore, and make a noisy neighbour impossible. The migration pain that forced this design does not exist at 40 tenants.
If your largest tenant is a large fraction of the data, shared tables put that tenant in everyone's buffer pool. A hybrid is usual and sensible: shared infrastructure for the long tail, dedicated instances for the handful of large accounts.
And the honest general caution: this design was published in 2009 by a company that had been running it for years, with an engineering organisation sized to own a query planner. The paper describes what their constraints forced, not what is generally advisable.
Common weak answers
- "Shared tables scale better." They do not inherently. What they buy is upgrade and provisioning cost, and they pay in isolation and optimisability.
- "Use a column per custom field." It works to a few hundred fields and then hits the engine's column limit, and every tenant's DDL now contends on one table.
- "Row-level security solves isolation." It enforces the predicate; it does nothing about shared buffer pools, shared statistics or a tenant monopolising query time.