A multi-tenant platform's database performance is fine for most tenants and severely degraded for a few. What are the likely causes and their fixes?
Show the full answer Hide the answer
The likely causes
1. Query plans chosen from aggregate statistics. The planner uses table-level statistics describing all tenants combined, and no individual tenant resembles the aggregate. A filter matching 2% of rows platform-wide may match 90% of one tenant's rows, so the chosen index scan visits nearly all of their rows through the index — far slower than a direct scan.
2. Plan caching amplifying it. The plan is chosen by whichever tenant executed the query first and then reused for everyone, so a plan tuned for a tenant with a thousand rows is applied to one with fifty million.
3. Indexes not leading with the tenant key. Without the tenant identifier as the leading column, selectivity is evaluated across the whole estate and scans are not confined to one tenant's rows.
4. Working set exceeding memory for large tenants. Small tenants' data stays cached; a large tenant's queries hit disk on every access, and their I/O also evicts everyone else's cached pages.
5. Data skew within a tenant — one project with a million issues, or one user with vast history — producing pathological cases inside an otherwise ordinary tenant.
The fixes, in order of leverage
1. Tenant as the leading column in every index. The single most important schema decision in a shared-table architecture. Selectivity is then evaluated within a tenant and every scan is confined.
2. Physical partitioning by tenant, giving per-partition statistics and eliminating the aggregate-statistics problem at its root.
3. Targeted plan management. Parameterise so plans cache per tenant shape, or force recompilation for queries known to be distribution-sensitive. Blanket recompilation costs CPU, so this must be targeted.
4. Per-tenant resource governance — statement timeouts and concurrency limits — so a pathological plan degrades that tenant rather than saturating shared capacity. Without this, one tenant's bad query is everyone's incident.
5. Guard rails on tenant-authored queries. Where tenants define their own filters, the query builder must impose a mandatory tenant predicate, a result cap, a join limit and a timeout — because tenant-authored queries are effectively untrusted input.
The architectural signal
Persistent per-tenant outliers are a tenancy signal, not merely a tuning problem. Beyond a certain size, sharing is a losing proposition regardless of tuning: the largest tenants pay a design cost imposed by the smallest.
The architecture should include an explicit promotion path — dedicated schema, database or cell — with a measured threshold. Discovering that need during an incident, with nothing prepared, is the common and avoidable outcome.