CONCEPT Cited by 1 source
Polymorphic usage tables (multi-tenant)¶
Definition¶
Polymorphic usage tables is the application-schema-layer
structural decision to use a small fixed set of generic
tables — keyed by some shape like
(tenant_id, entity_type, entity_id, …) — to store usage /
metadata / pre-computation data for many entity types, in
preference to creating one dedicated table per entity
type. The decision is load-bearing in multi-tenant SaaS at
scale, where the per-entity-type-table approach causes a
combinatorial explosion of database objects.
The 2026-05-14 Atlassian post is the first canonical wiki home for this concept. Verbatim framing:
"Naively adding one dedicated table per entity doesn't scale in Jira's multi-tenant architecture: a single new table becomes millions of tables across production, plus indexes and internal database objects. So we used two layers: ... For persistent pre-computation, we store data in the Jira relational database using a small set of generic tables (polymorphic 'usage' tables) instead of one table per entity type."
(Source: sources/2026-05-14-atlassian-optimisation-tools-for-jira-reducing-configuration-bloat)
The cost it solves¶
In a multi-tenant SaaS with per-tenant logical isolation implemented as separate schemas, separate databases, or shared physical clusters with per-tenant table-suffixing, adding a single new entity type with a dedicated table multiplies:
For Atlassian-scale Jira:
- Hundreds of thousands of tenants.
- Each new dedicated entity-type table → "millions of tables across production, plus indexes and internal database objects."
The cost shape:
- Postgres metadata RAM — every backend caches catalog entries for tables it touches; see concepts/catalog-bloat-multi-tenant for the broader failure mode.
- Operational overhead — each table needs schema migrations, monitoring, backup verification.
- Internal DB-object pressure — indexes, constraints, triggers, sequences all multiply.
The polymorphic table shape¶
Instead of field_usage, option_usage, role_usage,
scheme_usage, … as separate tables, a polymorphic table
might look like (schematic, not Atlassian-disclosed):
generic_usage_table
tenant_id bigint
entity_type enum-or-string -- 'field' | 'option' | 'role' | ...
entity_id bigint
scope_id bigint -- e.g. space, scheme, ...
usage_count bigint
last_used_at timestamp
computed_at timestamp
...
The shape trade:
- Wins: total table count is bounded by the small fixed number of generic tables, not by entity-type count × tenant count.
- Costs: queries scoped to a specific entity type need a
predicate on
entity_type; index design must consider the composite key shape; per-entity-type schema evolution is constrained by the shared table shape.
The 2026-05-14 post asserts the win is decisive at Jira's multi-tenant scale: "This design balances scale and cost — fewer database tables across tenants, reducing metadata RAM and operational overhead — and performance — still allows efficient, indexed queries over usage data."
Disambiguation¶
Polymorphic usage tables is not the same as:
- Schema-per-tenant — separate schemas per tenant; this is the failure mode polymorphic tables avoid.
- Table-per-tenant — separate tables per tenant; same failure mode at higher altitude. See concepts/catalog-bloat-multi-tenant for the Postgres-specific consequences.
- Polymorphic associations in ORMs (Rails-style
as: :commentable) — adjacent: same shape (one table with(parent_type, parent_id)), different motivation (extending an associative relation across multiple parent types in a single tenant). The multi-tenant version generalises the same column-shape decision to span multiple entity types across many tenants. - Entity-Attribute-Value (EAV) — adjacent: EAV uses a
generic table with
(entity, attribute, value)to represent arbitrary data. Polymorphic usage tables are more constrained: the columns are fixed, the categorisation is polymorphic, not the per-row attribute set.
When this is the right call¶
The polymorphic-usage-tables decision is the right call when:
- Tenant count × entity-type count is large enough that per-entity-type tables would exceed sensible operational bounds.
- Query patterns are predictable — usage queries are
generally "how many
(entity_type, entity_id)rows for this(tenant_id, scope_id)", not arbitrary schema-walking. - Per-entity-type schema variation is bounded — the data per entity type fits the shared column shape without requiring polymorphic-row JSON columns or joins to side-tables that defeat the point.
When the per-entity-type schemas diverge significantly, EAV or per-table evolution is preferred; when the tenant count is small enough that per-tenant catalog cost is bounded, table-per-tenant is preferred.
Seen in¶
- sources/2026-05-14-atlassian-optimisation-tools-for-jira-reducing-configuration-bloat (2026-05-14, Atlassian) — first canonical wiki home. Names the structural decision verbatim ("polymorphic 'usage' tables"), the rejected alternative (per-entity-type-table → millions of tables), the data shape claim (~1M records per entity type per tenant), and the trade balance (scale and cost vs performance). Coupled with Memcached for short-lived intra-job sharing — see patterns/tiered-state-management-memcache-plus-db.
Related¶
- systems/postgresql — host DB.
- systems/jira
- systems/atlassian-jira-optimisation-tools
- systems/atlassian-precomputation-framework
- concepts/catalog-bloat-multi-tenant — the Postgres-altitude cousin failure mode.
- concepts/configuration-bloat — the application-layer motivation that creates the data this concept stores.
- concepts/tenant-isolation
- patterns/polymorphic-usage-tables-for-multi-tenant-scale
- patterns/tiered-state-management-memcache-plus-db