Skip to content

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:

total_objects_added ≈ tenant_count × (1 table + N indexes
                                      + M internal objects)

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

Last updated · 542 distilled / 1,571 read