Skip to content

DEFAULT reconciliation cannot attach the window a live writer is inserting into, and partition_data raises on it #64

Description

@AlexeyShalaev

Measured on 1.5.0 against PostgreSQL 17.11: a plain events table with two million rows over the last year, swapped under a new parent as its DEFAULT partition the way guide/partition-existing-table.md says, with one writer inserting now() rows through events every 5 ms the whole time. Nothing else is running.

The first tick plans CREATE events__2026_09 / __2026_10 / __2026_11 (create_ahead). The two future months attach; the current month never does:

--- 3. the first tick: this month and two ahead, this month's rows move out of DEFAULT
Failed to attach after reconciliation retries
...
sqlalchemy.exc.IntegrityError: ... CheckViolationError: updated partition constraint for default partition "events_legacy" would be violated by some row
[SQL: ALTER TABLE "public"."events" ATTACH PARTITION "public"."events__2026_09" FOR VALUES FROM ('2026-09-01') TO ('2026-10-01')]
Attach failed after reconciliation; returned rows to DEFAULT partition
    created 0, issues 0, error 'IntegrityError: ...'
    live rows: 0 in this month's partition, 87 left in DEFAULT
    writer: 19 inserts, p50 0.5 ms, longest 604 ms, 2 over 100 ms

The cause is the shape of _attach_with_reconcile (aio/services/execution.py, and the sync twin): the attach fails on the DEFAULT conflict, the window's rows are moved out of DEFAULT in their own transaction, the attach is retried in another. Between the move's commit and the attach's scan the writer has inserted one more row for the window, so the scan finds it and the attach fails again, DEFAULT_CONFLICT_MAX_RETRIES times in a row, and the rows are moved back. Under any steady write rate the current month cannot be attached, which is the one window every live table is writing into, and the state after the tick is the state before it: every insert keeps landing in DEFAULT. The run reports error set and issues empty; each retry costs the writer a full scan of the two-million-row DEFAULT under ACCESS EXCLUSIVE (the 604 ms above).

partition_data reaches the same window last and does worse: it fills the detached events__2026_09, fails the attach the same way, moves the rows back, and then raises the IntegrityError out of partition_data instead of reporting a move issue with complete=False, which is what the guide promises for a window it cannot handle. The eleven older months drained fine before that (lowest count 29764 of 129764 on the oldest month while it was mid-move, as the guide warns).

What I think the fix is: do the move and the attach in one transaction, under the lock the attach takes anyway. ATTACH already needs ACCESS EXCLUSIVE on the DEFAULT sibling for its scan, so BEGIN; LOCK TABLE <default> IN ACCESS EXCLUSIVE MODE; DELETE ... RETURNING / INSERT; ALTER TABLE ... ATTACH PARTITION ...; COMMIT holds the same lock for the move plus the scan and cannot lose the race; a writer waits for that transaction and is then routed to the new partition. The rows also stop needing a compensating move-back for this path, since a failed attach rolls the move back with it. For a large window the move itself can be long, so it may be worth draining most of the window in batches first (which partition_data already does) and taking the locked move-and-attach only for what is left, so the exclusive window is a batch and a scan, not the whole month. partition_data should report an attach it still cannot do as a move issue and return complete=False, never raise. The DEFAULT reconciliation section of concepts/execution.md and the guide's numbered list follow.

Lab: migrate_lab.py in https://github.com/bedrock-python/bedrock-python.github.io/tree/docs/production-python-series/docs/blog/lab/2026-09-07-partition-existing-table (the writer is Load._writer; steps 3 and 4 are the ones above).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions