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:
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:
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_exclusionneeded ATTACH PARTITION/DETACH PARTITIONfor 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.
What Silently Breaks Pruning
1. Type mismatch between predicate and partition key:
2. Function wrapping the partition key:
3. OR conditions across partition key:
4. Partition key in a subquery:
5. Runtime parameter (Postgres < 14):
Postgres 14+ supports runtime partition pruning — even parameterized queries prune at execution time.
Verifying Pruning
Constraint Exclusion (Legacy)
For inheritance-based partitioning, constraint_exclusion enables the planner to use CHECK constraints for pruning:
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:
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_cronor external schedulers
This creates:
- All monthly partitions from
2024-01-01to 4 months in the future - The
partman.part_configentry tracking this table
Automated Maintenance with pg_cron
run_maintenance_proc() checks all registered partition sets and:
- Creates new future partitions up to
p_premakeahead - Detaches or drops partitions older than the retention policy
Setting a Retention Policy
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:
To make ATTACH PARTITION instant (skip the validation scan):
Partition-Local Indexes vs Global Indexes
Each partition has its own indexes. There are no global indexes spanning all partitions.
Unique Constraints and Partitioning
Unique constraints on a partitioned table must include the partition key:
For global uniqueness on a non-partition-key column (e.g., hash must be globally unique), the options are:
- Maintain a separate unpartitioned lookup table:
CREATE TABLE transaction_hashes (hash BYTEA PRIMARY KEY, transaction_id BIGINT) - Enforce uniqueness at the application layer
- Use
hashas part of the partition key (impractical for range-based partitioning)
Summary
| Concept | Key Takeaway |
|---|---|
| Declarative vs inheritance | Use declarative (PG10+) for all new work. Inheritance is legacy. |
| Partition pruning | Works automatically with declarative partitioning. Breaks with function wrapping, type mismatch, or OR conditions on partition key. |
| Verifying pruning | EXPLAIN (VERBOSE) and look for "Partitions excluded". Always verify after partitioning. |
pg_partman | Automate partition creation and retention. Run via pg_cron hourly. |
ATTACH PARTITION | Use NOT VALID + VALIDATE + ATTACH sequence for instant attachment of pre-loaded partitions. |
DETACH CONCURRENTLY | PG14+ — detach without blocking reads/writes. |
| Global unique constraints | Must include partition key. For true global uniqueness, use a separate lookup table. |
| Partition-local indexes | New partitions do not inherit parent's indexes automatically. pg_partman handles this. |
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 & RegisterDiscussion
0Join the discussion