Skip to content

Backport #509 and #512 to 0.6: replica identity and prune truncating only used children - #729

Merged
hardbyte merged 4 commits into
release/0.6.6from
claude/backport-509-512-0.6
Nov 12, 2026
Merged

hardbyte merged 4 commits into
release/0.6.6from
claude/backport-509-512-0.6

Conversation

@hardbyte

Copy link
Copy Markdown
Owner

Backports two main changes for the next 0.6 release.

Replica identity (#509)

PostgreSQL rejects UPDATE and DELETE on a table that is in a logical-replication publication and has no primary key, replica-identity index or FULL identity. A migrated 0.6 database has 19 such tables, so with awa in a FOR ALL TABLES publication the dirty-mark drain fails every maintenance tick and unique-claim release fails.

The schema stays at v40. A new repeatable patch, replica_identity, registered last in SCHEMA_PATCHES:

  • sets REPLICA IDENTITY FULL on awa.admin_dirty_queue_marks, awa.admin_dirty_kind_marks, and queue_terminal_count_deltas with each of its partitions in every queue-storage schema;
  • sets REPLICA IDENTITY USING INDEX idx_awa_jobs_unique on awa.job_unique_claims;
  • changes the v023 installer so schemas prepared later get it on every partition, since PostgreSQL doesn't copy the setting to partitions.

Unlike the earlier 0.6 patches it takes table locks: ACCESS EXCLUSIVE on each table it changes (and SHARE on idx_awa_jobs_unique) until it commits. Each lock wait is bounded by the session's lock_timeout, or 300 ms when none is set. A timeout fails with 55P03 and changes nothing, and a table that already has its identity is skipped. docs/upgrade-0.6.17-to-0.6.18.md describes the locks.

awa-model/tests/replica_identity_test.rs checks every table in awa and in custom schemas with and without receipts, the exact lock set, a re-run that locks nothing, and the bounded wait. With a FOR ALL TABLES publication it then runs the mark drain, unique-claim release, claim, complete, rotate, rollup and prune, including on partitions of a schema prepared after the patch.

Prune truncates only children that hold data (#512, with #516)

Queue, lease and claim prune take the same locks on every child of the sealed slot, in the same order, as in 0.6.17. After those locks are granted they TRUNCATE only the children with heap, TOAST, FSM or VM pages, or index pages beyond an empty btree, GiST or BRIN index. A child that never held a row keeps its relfilenode, so prune no longer writes WAL and catalog rows for empty partitions. Main's lease-rotation idle-gate change is not included, because 0.6 has no idle rotation gate. 0.6.17's empty-slot fast path is unchanged.

New tests in queue_storage_runtime_test cover used and unused children in queue and claim slots with reuse after the ring wraps, a vacuumed child with expanded indexes, and the BRIN block-size baseline.

Testing

Run locally on PostgreSQL 18: cargo test -p awa-model, queue_storage_runtime_test (275 passed, 1 ignored), migration_test, dirty_marks_wait_free_test, lane_ownership_roles_test, insert_many_schema_fallback_test, migrate_patch_cli_test, and the awa-worker prune unit tests. cargo fmt and clippy -D warnings are clean.

This and #726 both add a ## [Unreleased] CHANGELOG section, so whichever merges second needs a rebase.

https://claude.ai/code/session_01YSKjmBe32Fd2jj7gvxT6ZL


Generated by Claude Code

PostgreSQL rejects UPDATE and DELETE on a table that is in a logical-replication publication and has no primary key, replica-identity index or FULL identity. On 0.6.17, 19 awa tables have none: awa.admin_dirty_queue_marks, awa.admin_dirty_kind_marks, awa.job_unique_claims and the 16 partitions of awa.queue_terminal_count_deltas. With a FOR ALL TABLES or FOR TABLES IN SCHEMA awa publication, the dirty-mark drain fails on every maintenance tick and unique-claim release fails.

The 0.6 line keeps the schema at v40, so this ships as the repeatable schema patch replica_identity instead of migration v047. It sets REPLICA IDENTITY FULL on the two mark tables and on queue_terminal_count_deltas and each of its partitions in every queue-storage schema, and REPLICA IDENTITY USING INDEX idx_awa_jobs_unique on awa.job_unique_claims. Partitions do not inherit the setting from their parent, so the queue-storage installer sets it on the parent and every partition of a schema prepared later.

Each ALTER takes ACCESS EXCLUSIVE on its table (and SHARE on idx_awa_jobs_unique) until the patch commits. Each lock wait is bounded by the session's lock_timeout, or 300 ms when none is set; a timeout fails the patch with 55P03 and changes nothing. Tables that already have their identity are skipped, so a re-run takes no lock. The patch is last in SCHEMA_PATCHES so a single-transaction export holds the locks only at its end. A missing mark table keeps the patch pending, so it runs after wait_free_dirty_marks recreates one.

Tests cover every relation's identity in awa and custom schemas with and without receipts, the patch's exact lock set, its lock-free replay, its bounded lock wait, and UPDATE and DELETE on every table under a FOR ALL TABLES publication, including partitions of a schema prepared after the patch, with drain, unique-claim release, claim, completion, rotation, rollup and prune running. The release-gate rehearsal now expects the patch and allows exactly its locks.
…s (backport #512)

Queue, lease and claim prune truncated every child of the sealed slot, so a prune swapped relfilenodes and wrote WAL and catalog rows for partitions that never held a row, such as the receipt children of a schema without receipts.

Prune now runs TRUNCATE only on the children with heap, TOAST, free-space-map or visibility-map pages, or index pages beyond an empty btree, GiST or BRIN index, as main does after #512 and its follow-up #516. The probe runs after the slot's ACCESS EXCLUSIVE locks are granted. The lock statements, the children they name and their order are as in 0.6.17: queue prune still takes SHARE ROW EXCLUSIVE on all eight children and upgrades all eight to ACCESS EXCLUSIVE, and claim prune still locks its slot row only after its four children. A skipped child has no heap page, so it holds no row of any generation for slot reuse, the failed-row carry or the rollup fold to see.

The 0.6.17 empty-slot fast path still decides on visible rows, and 0.6 has no idle rotation gate, so main's change to the lease rotation probe does not apply.

Tests cover a receipt-mode queue and claim slot with used and unused children, reuse of the slot after the ring wraps, a vacuumed child with expanded indexes, and the BRIN free-space-map baseline per block size.
@hardbyte hardbyte added the full-ci Run the full CI matrix (Python build+test, E2E) on this PR label Nov 12, 2026 — with Claude
@coderabbitai

coderabbitai Bot commented Nov 12, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6831da39-6198-4c2e-96bc-c287f201daef

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 12, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-12T10:14:32.658064Z 8fc6f47 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8fc6f473f6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

…eated

External runners apply repeatable scripts in name order, so
R__replica_identity ran before R__wait_free_dirty_marks created the mark
tables and left them without an identity. wait_free_dirty_marks now sets
REPLICA IDENTITY FULL on a mark table it creates, and leaves tables that
already existed alone.
@hardbyte
hardbyte merged commit 7753818 into release/0.6.6 Nov 12, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

full-ci Run the full CI matrix (Python build+test, E2E) on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant