Skip to content
Navigation

Type at least two characters. Search covers page titles, headings, tags and database names.

↑ ↓ to navigateEnter to openEsc to close0 pages
PostgreSQLadvanced

PostgreSQL Partitioning

Declarative range, list and hash partitioning — creating partitions, verifying pruning, and automating partition lifecycle.

2 min readAdvancedUpdated Edit this page

Declarative partitioning splits a table into child tables by a partition key. The main operational payoff is dropping a partition instead of deleting rows.

Creating a partitioned table

CREATE TABLE events (
    id          bigint GENERATED ALWAYS AS IDENTITY,
    tenant_id   bigint      NOT NULL,
    event_type  text        NOT NULL,
    payload     jsonb       NOT NULL DEFAULT '{}'::jsonb,
    created_at  timestamptz NOT NULL,
    -- The partition key must be part of every unique constraint.
    PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
 
CREATE TABLE events_2026_07 PARTITION OF events
    FOR VALUES FROM ('2026-07-01') TO ('2026-08-01');
CREATE TABLE events_2026_08 PARTITION OF events
    FOR VALUES FROM ('2026-08-01') TO ('2026-09-01');
 
-- Indexes created on the parent are created on every partition.
CREATE INDEX events_tenant_created_idx ON events (tenant_id, created_at DESC);

Range partitioning suits time series. List partitioning suits a small fixed set such as region. Hash partitioning spreads writes evenly when there is no natural range key.

Verifying pruning

Pruning is the reason to partition, so confirm it rather than assume it:

EXPLAIN (ANALYZE, BUFFERS)
SELECT count(*) FROM events
WHERE created_at >= '2026-07-01' AND created_at < '2026-07-15';
-- The plan should reference only events_2026_07.

If the plan shows every partition, the predicate is not on the partition key in a form the planner can use — usually because it is wrapped in a function, or because the value arrives from a join.

Retention by detach and drop

-- Detach first: the table becomes independent and can be archived or inspected.
ALTER TABLE events DETACH PARTITION events_2025_07 CONCURRENTLY;
 
-- Then drop, after the archive copy is verified.
DROP TABLE events_2025_07;

DETACH ... CONCURRENTLY avoids the exclusive lock that a plain detach takes on the parent. Verify the archived copy before dropping — a drop is only recoverable from backup.

Costs to plan for

  • Planning time grows with partition count. Keep the total in the tens or low hundreds and measure planning time on your real queries.
  • Unique constraints must include the partition key; global uniqueness on another column is not enforceable.
  • Converting an existing table copies the data. Build the partitioned table alongside and migrate — see Zero-Downtime Migrations.
  • Foreign keys referencing a partitioned table are supported in recent versions; confirm behaviour for the version you run before designing around them.