Skip to main content

God Table

A god table absorbs columns for many unrelated concepts until it becomes a wide, contended bottleneck that every team fears to change. Split it along its natural entities using normalization and bounded contexts, migrating with expand-and-contract.

A god table is the data-layer cousin of the god object: one table that grows to hold attributes for many distinct concepts. It starts as users, then accrues billing fields, preference flags, audit columns, feature toggles, and denormalized counters until it has 150 columns, most of them nullable.

Because so much of the application reads and writes this one table, it becomes a hotspot for both contention and change. Every team touches it, so every schema change is high-risk and every migration locks a table the whole system depends on.

Why It Happens

Adding a column is the path of least resistance. When a new feature needs to store one more attribute about a user, bolting it onto the existing users table is faster than designing a new table and a join. Over years, with many developers each making the locally rational choice, the table sprawls. Fear of migrations on a large, busy table then discourages the cleanup that would split it.

Why It Hurts

Wide rows waste I/O: a query reading two columns still pages in the whole row. Many nullable columns signal that the table is modeling several entities at once, violating normalization. The table becomes a single point of contention as unrelated workloads compete for the same rows and locks. Schema changes are dangerous because the table is large and universally referenced, so teams avoid them, and the design ossifies. Indexes proliferate to serve disparate access patterns, slowing writes.

Warning Signs

  • A table with dozens or hundreds of columns, many of them nullable.
  • Columns whose names belong to clearly different subdomains (billing, auth, analytics).
  • Nearly every feature joins to or updates this one table.
  • Migrations on the table are treated as scary, all-hands events.

Better Alternatives

Apply normalization: split the table along its natural entities so each table models one concept with non-null, meaningful columns. Use domain-driven design to identify bounded contexts and give each its own tables. One-to-one satellite tables can hold optional or rarely accessed attribute groups, keeping the core row narrow and hot. Move denormalized counters and projections into purpose-built tables or a cache.

How to Refactor Out of It

Refactor incrementally with the expand-and-contract pattern. Create the new, focused table; dual-write to both old and new during a transition; backfill historical data; migrate readers to the new table; then drop the old columns. Add foreign keys and constraints on the new tables to lock in the cleaner model. Track which feature areas own which columns so future additions land in the right home rather than back on the god table.