PostgreSQL Table Partitioning: Range, Hash, and When to Shard

A table with a few million rows is a convenience. A table with several billion rows is an operational commitment. Queries that used to return in milliseconds start scanning far more data than they need. Index maintenance slows every write. VACUUM runs for hours. Deleting last year's data turns into a multi-day job that bloats the table it was meant to shrink.

Declarative table partitioning is PostgreSQL's built-in answer to this problem. It splits one logical table into smaller physical tables while your application keeps querying a single name. Done well, it keeps queries fast, makes retention nearly instant, and shortens maintenance windows. Done badly, it adds planning overhead and operational complexity for no benefit.

This guide covers how to choose between range and hash partitioning, how to design the partition key, how to confirm the planner actually skips partitions, and how to automate the partition lifecycle. It also explains where partitioning stops and sharding begins. The examples use PostgreSQL 14 or newer, and the syntax is the same on current releases.

Key takeaways

  • Partitioning splits one table into smaller physical tables inside a single PostgreSQL server. Sharding spreads data across multiple servers. They solve different problems.
  • Use range partitioning for time-ordered data and retention. Use partitioning to spread load evenly when no natural range exists.