Troubleshoot¶
What a message means, why it happened, and what to do. Findings appear in
plan.findings and plan.describe(); warnings among them also land in result.issues.
Exceptions are raised by plan() / apply() or reported on result.error by the
maintainer.
The plan says nothing to do but I expected partitions¶
- The windows exist already. Check
service.inspect(config). - The creation rule is
CreateNextIfand the newest partition does not satisfy it yet. - The cursor is not where you think: an integer axis reads
max(key)— an empty table starts atorigin; a time axis reads the clock in the calendar'stz. - The run was planned in
RECONCILEmode (reconcile()creates nothing ahead).
range_overlap — a wanted window overlaps a partition I did not configure¶
[warning] range_overlap: public.events needs a partition for 2028_03 but public.events_oddweeks already covers part of it with bounds the scheme did not produce; creating it would fail, and detaching the other is not this library's decision.
A partition whose bounds are not on the grid sits where a wanted window should go. PostgreSQL would refuse the overlap, and detaching someone else's partition is not the library's call. Decide by hand: detach and re-attach it with grid-aligned bounds, split it, or leave it and accept that this window is not managed. See Change a scheme safely for the granularity-change case.
unmanaged_partition (INFO)¶
The partition's bounds are not a window of the grid, nor inside one. It is inspected and left alone: never expired, never dropped. This is the normal state of a hand-attached archive. If it should be managed, its bounds have to be the scheme's — check timezone (a month created under another zone straddles two cells) and granularity.
legacy_leaf (INFO)¶
The scheme expects a branch (RANGE → HASH) but this partition is a plain table, created
before the level below existed. A plain table cannot gain partitions. It holds valid data
and stays; new partitions follow the new shape.
modulus_preserved (INFO), hash_gap_historical_modulus, modulus_repaired (INFO)¶
The configured bucket count changed. A complete set at the old modulus is kept; an incomplete one is repaired at its own modulus, because a bucket at the new modulus would overlap. Rebucketing history is a data migration, not maintenance.
non_uniform_incomplete — hash buckets at mixed moduli leave a gap¶
Some siblings use one modulus, some another, and together they do not tile the keyspace;
rows hashing into the gap are rejected. No repair is provably safe, so it is reported.
Look at the buckets (inspect), work out which residue classes are missing, and add
them by hand at the modulus that fits (each modulus must be a factor of the next larger
one, and residue classes must not overlap).
default_holds_rows — a DEFAULT sibling holds rows for a hash or list member¶
Only a RANGE window can be selected by its key, so rows belonging to a hash bucket or a list group are not moved automatically. Move them out of the DEFAULT partition (insert them through the parent after the member exists, or delete and re-insert) and the next run attaches the member.
name_unusable¶
The name the scheme produces is either taken by a relation with other bounds, or over
PostgreSQL's 63-byte limit (which truncates silently and would make two partitions
collide). Rename the stray relation, or shorten the table name / name_suffix.
It is also reported when a wanted window's name is held by a detached table under
DropNever: that table was handed to another process, so it is neither re-attached nor
collided with. Attach it yourself, or rename it and the next run creates the partition.
detach_pending (INFO)¶
An earlier DETACH … CONCURRENTLY was interrupted. The partition is still attached in the
catalog, invisible through the parent, and rejects its own rows. The same maintain()
call completes it (DETACH … FINALIZE, reason detach_finalize), re-plans, and
re-attaches the table when its window is still wanted — or retires it under the drop
policy. To finish it by hand instead:
strategy_mismatch, column_mismatch¶
A branch is partitioned by another method, or on another key, than the scheme asks for —
RANGE (created_at) → LIST (region) where the configuration says HASH (tenant_id).
Repartitioning an existing branch is a rewrite, so it is left alone and reported; new
branches follow the scheme. Fix the scheme if the tree is right, or migrate the branch by
hand.
non_uniform_complete (INFO)¶
Hash siblings use different moduli (2 and 4, say) but together still tile the keyspace. Legal, and left as it is.
unconvergeable¶
A partition was not created because part of its subtree could not be planned — a name refused, a group in conflict. Attaching a branch with a hole in its child set would reject rows, so the whole partition waits. The findings for the subtree say why.
grace_pending, drop_deferred (INFO)¶
A detached orphan waiting out its grace period, or one whose DropAfter(when=…)
condition does not hold yet. Expected; the drop comes when the policy says.
unreadable_bound, unbounded_partition¶
A bound the level's axis cannot read (a partition keyed differently than configured, or
a codec mismatch), or an open-ended one (MINVALUE / MAXVALUE). Never pruned. If the
codec is wrong, fix boundary_codec; an open-ended partition is by definition current.
foreign_partition (INFO)¶
A foreign table is in the tree under a LocalLeaves configuration. It is someone else's;
nothing is created, detached or dropped. Configure ForeignLeaves if it should be
managed.
list_values_conflict¶
A configured LIST group claims a value another partition already owns. A value belongs to exactly one partition; detach the other partition first, or change the group.
coverage_unknown¶
Either a child's name contains a dot and cannot be addressed by qualified-name DDL, so the child set cannot be read completely; or hash siblings use moduli whose least common multiple is too large to check coverage. Nothing is planned for that branch. Rename the child, or simplify the moduli.
PartitionReferencedError — a detach was refused by a foreign key¶
detach: public.ci_builds__2026_06: PartitionReferencedError: Partition public.ci_builds__2026_06 is still referenced by rows of another table: removing partition "ci_builds__2026_06" violates foreign key constraint "ci_artifacts_build_id_created_at_fkey1"
Rows of another table reference rows of this partition through a foreign key on the
parent; PostgreSQL will not detach it. The run goes on; the issue repeats until the
referencing rows are gone. Put Unreferenced() in the retention rule to keep such
partitions out of the plan — see Handle foreign keys.
RowMoveRefusedError — a move was refused by an ON DELETE action¶
Two causes, both fail-closed. A foreign key referencing the table declares
ON DELETE CASCADE, SET NULL or SET DEFAULT: such an action fires on the DELETE half
of a move and would delete or rewrite the referencing rows, so the move is refused before
a single row leaves. Or the rows being moved are referenced through any foreign key: a
row mid-move is outside the referenced tree, so even NO ACTION refuses at the end of
the statement — atomically, with every row in place. Either way a move issue names the
constraint. Delete or repoint the referencing rows first, or drop the foreign key for the
migration and re-create it after the move. See
Row moves and ON DELETE actions.
InvalidPartitionConfigError¶
Raised by plan() before any DDL; reported on result.error by the maintainer.
| Message | Cause | Fix |
|---|---|---|
Table 'public.events_flat' is not partitioned |
the parent is a plain table | CREATE TABLE … PARTITION BY; see Partition an existing table |
Partition type mismatch … config='range' actual='list' |
the root's method differs from the scheme's | match the scheme to the table |
Partition column mismatch … config='occurred_at' actual='created_at' / Partition key mismatch |
the key differs, or composite key order differs | match the scheme's key to PARTITION BY |
Subpartition column(s) 'tenant_id' missing from unique constraint(s) (id, created_at) |
a nested level's column is not in every UNIQUE / PRIMARY KEY |
add the column to the constraints |
… PostgreSQL refuses a foreign table as a partition of a table with a unique index or primary key |
ForeignLeaves on a parent with a unique index |
local leaves, or drop the constraint |
Partition column(s) […] are mixed-case / partitions on an expression |
a key the library cannot address | use lowercase column keys |
ensure_partitions needs a progression root |
ensure_partition(s) on a HASH or grouped LIST root |
those sets are fixed; use reconcile() |
partition_data drains a DEFAULT partition into RANGE windows; this root is not a RANGE level |
the mover on a non-RANGE root | only RANGE roots have a DEFAULT to drain |
Timezone mismatch¶
Timezone mismatch: the period calculator works in 'Europe/Helsinki' but repository DDL runs in 'UTC'. Pass ddl_timezone='Europe/Helsinki' to the repository, or align the calculator's tz.
The calendar and the DDL disagree on what midnight is. Pass the same zone to
TimeBoundaries(tz=…) (or the flat tz) and to PostgresPartitionRepository(ddl_timezone=…).
LockAcquisitionError¶
Another maintainer holds the table's lock. Not an error: skip the tick. If it persists,
look for a stuck run (pg_locks for advisory locks; the Redis key for the Redis manager)
or a pool of size one, which deadlocks the advisory manager against its own DDL.
PlanStaleError¶
The relation the plan decided about is not the one holding the name any more — it was
dropped and recreated between plan and apply, or re-attached. The operation is skipped
(recorded as an issue with continue_on_error, raised otherwise). Plan again.
UnmanagedPartitionDropError, PartitionAttachedError¶
drop_partition refused a table without the marker, or one that is still attached. Both
guards are the point of safe drops; adopt legacy tables with adopt_partition, and
detach before dropping.
PartitionTopologyError¶
The execution-time twin of a warning finding: a DEFAULT sibling holding rows for a hash or list member, a name taken by a relation with other bounds. Recorded as an issue with the finding's reason; the run goes on. The remedy is the finding's.
PartitionAlreadyExistsError, PartitionNotFoundError, PartitionDetachInProgressError¶
Repository-level errors the executor normally absorbs: a name already taken is a lost
race (benign) or a conflict (name_unusable); a relation that vanished between plan and
apply is skipped; a detach already pending on another connection is retried next tick.
Seen directly only when calling the repository yourself.
DropRetryExhaustedError¶
The drop could not take its lock within drop_max_retries attempts — a long transaction
holds the table. Find it in pg_stat_activity, or raise drop_lock_timeout_ms.
ValueError at construction¶
Refused before touching the database: a name_suffix without its placeholder, a
composite LIST key, two levels on one column, a table name too long for the scheme's
suffixes, CreateAhead on a sliding list, a negative grace, a foreign-table option
template with an unknown placeholder. The message names the field.
Orphan marker mismatch at construction¶
Orphan marker mismatch: the repository marks detached partitions with 'app:orphan-parent=' but the metadata provider looks for 'pg-partsmith:orphan-parent='.
marker_prefix was passed to one of the two and not the other. The repository stamps the
marker on detach; the provider finds orphans by it. Left alone, nothing detached would
ever be seen again — no error, just partitions accumulating — so the pair is refused where
it is wired. Pass the same prefix to both, or to neither.
Partition upper bound carries no timezone and none was given (WARNING)¶
is_partition_closed read a bound with no offset — which is what a timestamp or date
key produces — while neither the provider's ddl_timezone nor a boundaries argument said
which zone wrote it, so the server's session default decided. The answer may be hours early
or late. Pass the table's own boundaries, which know both the zone and the codec:
A timestamptz key never triggers this: its literals carry an offset.
Rows are being rejected: no partition of relation "events" found for row¶
PostgreSQL's error, not the library's: a row's key falls into a window with no partition.
Either the tick has not run for longer than create_ahead_count covers, the table has
no DEFAULT partition and receives out-of-range rows, or a hash set has a gap
(non_uniform_incomplete). Run a tick; check plan.findings.
Everything looks right but nothing was created¶
- Two replicas with different configurations for one table undo each other. Deploy one configuration everywhere.
- The engine points at a replica (
pg_is_in_recovery()); DDL needs the primary. - The maintenance role does not own the table. Partition DDL requires ownership; check
result.errorforpermission denied.