Module A-14·19 min read

The operational difference between designing a partition and maintaining 500 of them at scale.

Module 14 — Advanced Partitioning: Pruning, Maintenance, and pg_partman

What this module covers: Module 7 introduced partitioning strategies. This module goes deeper into the mechanics that make partitioning actually work at scale: how the query planner prunes partitions (and what silently breaks it), declarative partitioning vs inheritance-based partitioning and when each applies, constraint exclusion, and the operational reality of maintaining hundreds of partitions over months and years using pg_partman.


Declarative Partitioning vs Inheritance-Based Partitioning

Postgres has two partitioning systems with very different operational profiles.

Inheritance-Based Partitioning (Pre-PG10)

Before PostgreSQL 10, partitioning was implemented via table inheritance and CHECK constraints:

sql

Problems with inheritance partitioning:

  • INSERT routing requires a trigger on every insert (trigger overhead on every write)
  • The trigger must be manually updated every time a new partition is added
  • Unique constraints cannot span partitions
  • Foreign keys to/from partitioned tables are not enforced
  • No automatic partition pruning in the query planner (requires constraint_exclusion = on)

Declarative Partitioning (PG10+)

Declarative partitioning is a first-class feature. The database handles routing, pruning, and constraint enforcement natively:

sql

Advantages of declarative over inheritance:

  • No trigger required for INSERT routing (handled by the executor natively)
  • Primary key and unique constraints work (must include partition key)
  • Foreign keys to partitioned tables are supported (PG12+)
  • Native partition pruning — no constraint_exclusion needed
  • ATTACH PARTITION / DETACH PARTITION for online maintenance

When to still use inheritance: very old databases still on PG9.x (rare), or when you need a child table to have additional columns beyond the parent (inheritance allows this, declarative does not).


Partition Pruning: How It Works and What Breaks It

Partition pruning is the planner optimization that skips scanning irrelevant partitions. It is the primary reason to partition: a query on timestamp > '2026-05-01' should only scan transactions_2026, not all historical partitions.

How Pruning Works

The planner examines the partition key predicate and eliminates partitions whose bounds cannot contain matching rows.

sql

What Silently Breaks Pruning

1. Type mismatch between predicate and partition key:

sql

2. Function wrapping the partition key:

sql

3. OR conditions across partition key:

sql

4. Partition key in a subquery:

sql

5. Runtime parameter (Postgres < 14):

sql

Postgres 14+ supports runtime partition pruning — even parameterized queries prune at execution time.

Verifying Pruning

sql

Constraint Exclusion (Legacy)

For inheritance-based partitioning, constraint_exclusion enables the planner to use CHECK constraints for pruning:

ini

With declarative partitioning, constraint_exclusion is irrelevant — native partition pruning handles it. Only enable constraint_exclusion = on if you are still using inheritance-based partitioning.


Partition Maintenance at Scale

The Problem: Partition Explosion

A table partitioned by week over 5 years has 260 partitions. By month, 60. Manually creating and managing these partitions is error-prone and operationally expensive.

The worst case: the table has no partition for the current date range. An INSERT fails with:

text

This is a write failure — all inserts are rejected until a partition covering the current time range is created.

pg_partman: Automated Partition Management

pg_partman is the standard extension for automated partition management. It:

  • Creates future partitions proactively on a schedule
  • Optionally detaches or drops old partitions based on retention policy
  • Maintains a premake buffer (creates N future partitions ahead of current time)
  • Works with pg_cron or external schedulers
sql

This creates:

  • All monthly partitions from 2024-01-01 to 4 months in the future
  • The partman.part_config entry tracking this table

Automated Maintenance with pg_cron

sql

run_maintenance_proc() checks all registered partition sets and:

  1. Creates new future partitions up to p_premake ahead
  2. Detaches or drops partitions older than the retention policy

Setting a Retention Policy

sql

retention_keep_table = true detaches old partitions — they become standalone tables, accessible for archival queries but no longer part of the partitioned set. This allows bulk exports to cold storage before dropping.

ATTACH PARTITION and DETACH PARTITION

Manual partition management for cases where pg_partman is not used:

sql

To make ATTACH PARTITION instant (skip the validation scan):

sql
sql

Partition-Local Indexes vs Global Indexes

Each partition has its own indexes. There are no global indexes spanning all partitions.

sql

Unique Constraints and Partitioning

Unique constraints on a partitioned table must include the partition key:

sql

For global uniqueness on a non-partition-key column (e.g., hash must be globally unique), the options are:

  1. Maintain a separate unpartitioned lookup table: CREATE TABLE transaction_hashes (hash BYTEA PRIMARY KEY, transaction_id BIGINT)
  2. Enforce uniqueness at the application layer
  3. Use hash as part of the partition key (impractical for range-based partitioning)

Summary

ConceptKey Takeaway
Declarative vs inheritanceUse declarative (PG10+) for all new work. Inheritance is legacy.
Partition pruningWorks automatically with declarative partitioning. Breaks with function wrapping, type mismatch, or OR conditions on partition key.
Verifying pruningEXPLAIN (VERBOSE) and look for "Partitions excluded". Always verify after partitioning.
pg_partmanAutomate partition creation and retention. Run via pg_cron hourly.
ATTACH PARTITIONUse NOT VALID + VALIDATE + ATTACH sequence for instant attachment of pre-loaded partitions.
DETACH CONCURRENTLYPG14+ — detach without blocking reads/writes.
Global unique constraintsMust include partition key. For true global uniqueness, use a separate lookup table.
Partition-local indexesNew partitions do not inherit parent's indexes automatically. pg_partman handles this.

Knowledge Check

A Senior DBA is investigating a performance regression in a PostgreSQL 13 application. A query against a PARTITION BY RANGE (timestamp) table, executed via a prepared statement with a timestamp parameter (e.g., PREPARE q AS SELECT * FROM transactions WHERE timestamp = $1;), is unexpectedly performing full table scans across all partitions. Which of the following is the MOST likely architectural reason for this behavior?


A DevOps engineer configures pg_partman for a mission-critical transactions table, partitioned monthly by timestamp. The requirement is to keep 12 months of active data online, but also retain older data for compliance audits in a separate, queryable archive without it being part of the active partitioned set. Which pg_partman configuration, combined with its maintenance process, achieves this goal most effectively?


A team needs to integrate a large, pre-existing transactions_legacy_2023 table (containing 2023 data) into a live, declaratively partitioned transactions table with minimal disruption to ongoing read and write operations. The transactions table is partitioned by timestamp. Which sequence of operations will achieve this with the least impact on the live system?

Test your knowledge with more question sets

Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.