Skip to content

chore: prepare 0.6.10 release - #582

Merged
hardbyte merged 28 commits into
release/0.6.6from
ccr-37ca53f6-xza211-release-0.6.10
Nov 3, 2026
Merged

hardbyte merged 28 commits into
release/0.6.6from
ccr-37ca53f6-xza211-release-0.6.10

Conversation

@hardbyte

@hardbyte hardbyte commented Nov 2, 2026 •

Copy link
Copy Markdown
Owner

Summary

Prepare the 0.6 maintenance line for v0.6.10.

  • Admin dashboard reads on queue storage, backported from Read queue overviews from live work on queue storage #580, Bound the dashboard's state counts and timeseries on queue storage #581 and Keep admin dashboard reads off retained history, with a guard test #584.
    • admin::queue_overviews, state_counts and state_timeseries built every current job through terminal_jobs, which expands each completion batch and joins it back to ready_entries. Their cost therefore grew with retained history. One production deployment saw the queue overview take 22 s, re-run by an open dashboard.
    • They now read the live-work tables the worker queue gauges read, and count completions from batch sizes less tombstoned members. The timeseries only reads rows finalised within its window, measured on the database clock.
    • From Keep admin dashboard reads off retained history, with a guard test #584:
      • Available rows are found through each lane's segments from its claim cursor, as claims find them. A lane range without the ready key's leading ring slot scanned every retained ready row.
      • Recent deletions are matched against each affected completion batch once, rather than one batch scan per deletion. That scan took 5.4 s on a production-sized fixture.
      • The timeseries probes each terminal row's ready key instead of reading through terminal_jobs.
      • Queue lag is the age of the oldest available job again, as in 0.6.9.
    • admin_read_cost_test serves every endpoint the UI polls against ten times more retained history and fails if a read does more work. On the head before the Keep admin dashboard reads off retained history, with a guard test #584 port it fails for /api/queues and the timeseries.
    • JIT is off for all three reads.
    • running keeps 0.6's leases-only definition.
    • The workers' gauge query is unchanged on this branch.
  • cancel_by_unique_key / cancel_by_unique_key_tx on queue storage, backported from Find unique-key cancel candidates without scanning retained ready rows #586 for Avoid queue-storage scans in cancel_by_unique_key candidate lookup #585.
    • The candidate lookup built every available ready row before filtering by key, so it read every retained ready row. It also missed jobs running on receipt claims not yet materialised into leases, which therefore couldn't be cancelled by key.

    • Given a queue, it now starts from that queue's ready rows holding the key (lane index) and checks only those jobs: available past the claim cursor and not tombstoned, in leases, or on an open receipt claim found by job_id. With no such row, only the deferred_jobs key index is read.

    • Without a queue it walks available rows lane by lane from the claim cursor and reads open receipt claims in full (Queue storage: cancel_by_unique_key without a queue still reads every retained receipt claim #587).

    • Benchmark (d87459ac). Setup: 180,200 retained ready rows and 120,300 retained row-local claims in other queues, plus 300 long-running open claims; timed through cancel_by_unique_key_tx, rolled back, p50 of 100.

      0.6.9 first 0.6.10 lookup (claim ring read in full) this head
      missing key 2.8 s 9.7 s 1.7 ms
      cancel a scheduled job 2.8 s 9.2 s 11 ms
      cancel an available job 2.8 s 10.5 s 20 ms

      The cost test now retains row-local claims; the first 0.6.10 lookup fails it (434 → 32,912 rows read).

    • A job moved to another queue while available isn't found by a lookup naming its original queue; cancel it by id.

  • DLQ retries, purges and moves refresh the admin UI's cached stats and queue counts. These are the self-contained invalidation hunks from Consolidate admin polling and fence cached observations #579. Its scheduler and cache rewrite are not part of this backport.
  • The runtime page hides the storage transition card once the transition is finalized, backported from Hide the storage transition card once the transition is finalized #589. It stayed up on every cluster running queue storage.
  • Abandoned admin requests no longer hold database connections, backported from Stop abandoned admin requests from holding database connections #583.
    • A request the client gives up on leaves its query running, and sqlx holds the connection until the query ends.
    • If the query ended in an error, sqlx's release check hard-closed the connection. That hangs over TLS, so the pool lost the slot for good. sqlx 0.8.6 on this branch does the same as 0.9 on main, and pool_limits_test fails here without the fix.
    • awa serve now builds its pool with awa_ui::admin_pool_options / admin_connect_options:
      • a 60 s statement timeout (--statement-timeout, AWA_STATEMENT_TIMEOUT);
      • a release hook that keeps the connection;
      • client_connection_check_interval on Postgres 14+.
  • Restricted runtimes can use pre-provisioned lane sequences (backport of Allow restricted runtimes to use pre-provisioned queue lanes #501).
    • These started as three commits prepared on chore/release-0.6.10 on 2026-10-06 and never PR'd.
    • The fix ships as the idempotent schema patch prepared_lane_sequences, plus awa storage prepare-queue / QueueStorage::prepare_queue.
    • This PR also fixes the docs' pgroles example, which was missing the sequence UPDATE grant.
    • Changed from the cherry-pick: the patch replaces only the three lane helpers. As prepared, it re-ran awa.install_queue_storage_substrate for every queue-storage schema. That holds exclusive locks on hot tables (including leases and deferred_jobs) until commit, rebuilds the head-sync triggers and scans retained ready entries. The helpers now live in awa.refresh_lane_sequence_helpers(schema), as on main (v049).
      • The installer calls the refresh function, so new schemas get the fixed helpers.
      • On existing databases the patch calls only the refresh, under the per-schema install advisory lock.
  • 0.6.9 → 0.6.10 is a tested rolling upgrade with binary rollback.
    • Patch detection checks every substrate. The prepared_lane_sequences probe looked at the three helpers in awa only. A custom queue-storage schema restored or prepared with the 0.6.9 helpers was missed once awa passed, and a database whose only active substrate was a custom schema could never pass. The probe now checks the installer, awa.refresh_lane_sequence_helpers and every substrate's helpers, from the catalogs, so a schema the migrating role cannot use is still reported. migrations::schema_patch_status names what each missing patch still has to repair. Regression test: awa patched, a custom schema with the old helpers, and a third schema the migrator cannot use.
    • awa migrate fixes.
      • It refuses --from / --to / --version when applying; it used to ignore them and migrate fully.
      • --sql output is one transaction holding the migration advisory lock, with ON_ERROR_STOP guidance; --no-transaction prints it bare.
      • It reports the schema version, each patch and what it updated, and storage routing before and after (migrations::run_with_report), and exits 1 naming any schema a patch could not reach.
      • CLI tests: an interrupted patch (backend terminated mid-patch) leaves nothing applied and a retry completes it; exported SQL that fails under psql -v ON_ERROR_STOP=1 leaves nothing applied and then applies cleanly.
    • awa storage prepare-queue works on lanes a 0.6.9 runtime created. Found by the rehearsal. A runtime with schema CREATE owns the lane sequences it creates. Provisioning such a lane as the owner failed with permission denied for sequence, because the head tables' BEFORE INSERT triggers run even for a row ON CONFLICT DO NOTHING then skips. Lane inserts now skip rows that exist, under the same per-lane advisory lock. Regression test included.
    • Release gate: patch_upgrade_rehearsal_test, a new CI job on PostgreSQL 17 and 18. release.yml already requires ci.yml to pass on the tagged SHA. See "Upgrade rehearsal" below.
    • docs/upgrade-0.6.9-to-0.6.10.md:
      • The one upgrade path, and what operators don't need to do.
      • Rollback: leave the patch installed.
      • The optional procedure for revoking runtime CREATE, with a per-sequence grant, since GRANT ... ON ALL SEQUENCES run by the owner fails on runtime-owned sequences.
  • CI kept working on today's runners.
  • Version bump to 0.6.10 across every manifest, internal pin and lockfile, with the CHANGELOG section.

Left out on purpose: feat(migrations): record schema patches in the awa.schema_patches ledger (54f0bc6a on that branch). Its message says 0.7 creates the same table from a numbered migration, but main has no schema_patches table. A 0.6.10 database would therefore carry a table that 0.7 knows nothing about. It can ship once main has a matching migration.

No new migration version. The admin fixes need no migration: only the process serving the admin UI, or calling the admin API, has to run 0.6.10 to get them. The one API addition is the new awa_ui::pool helpers.

Measurements

Admin reads

The data is 50,000 compact completions plus 50,000 queued jobs on this branch:

Read v0.6.9 0.6.10
queue_overviews (awa queue stats) no result after 300 s 50–115 ms
state_counts not measured ~50 ms
state_timeseries (60 min, every completion in the window) not measured 350–490 ms

The v0.6.9 figure comes from running the v0.6.9 awa binary's queue stats against the same database. It was killed at 300 s, and its server-side query was still running ten minutes later. These timings predate the #584 port.

admin_read_cost_test, tuples read per request with small and then ten times more retained history:

Endpoint Before the #584 port This head
/api/queues 2.7k → 5.0k 4.2k → 4.7k
/api/stats/timeseries 8.6k → 31k 6.1k → 6.6k
/api/stats 2.1k → 2.6k 4.1k → 4.6k

A queue-scoped cancel_by_unique_key lookup of a missing key read 720 rows, then 5,206 with ten times the retained completions. It now stays flat in both that case and beside another queue's 5,000-job backlog.

awa migrate under live load

Setup:

  • The database was created and switched to queue storage with the v0.6.9 binary, then seeded with about 28,000 completed jobs.
  • A load harness then ran producers on three queues, with a new queue every 2 s so lanes are created through the patched helpers, and workers completing jobs.
  • Partway through, the database was migrated, while a monitor sampled pg_locks every 50 ms.
Patch as cherry-picked (installer) This PR (helpers only)
Patch wall time 23.2 s 22 ms (209 ms for all of awa migrate)
Worker completions during the patch none for the whole 23 s longest gap between completions ≤ 49 ms
Blocked backends (max) 5 0
Enqueue errors 1 deadlock 0

After the run:

  • All three helpers carry the provisioning check, and the head-sync triggers are untouched.
  • Every lane head has its sequence.
  • A second awa migrate reports nothing pending.
  • The exported R__prepared_lane_sequences.sql applied twice with psql to a fresh v0.6.9 database is also clean, and awa migrate then finds nothing pending.

Upgrade rehearsal

awa/tests/patch_upgrade_rehearsal_test.rs starts from released artifacts only, the awa-cli and awa-pg 0.6.9 wheels from PyPI.

  1. The 0.6.9 CLI migrates as an owner role and activates queue storage in a custom schema (prepare-queue-storage-schema, prepare, enter-mixed-transition --quiesced, finalize).
  2. 0.6.9 Python workers and producers then run as a separate runtime role, with the grants a working 0.6.9 deployment needs (schema CREATE included). They bank a backlog on one queue and run retries, jobs scheduled 45 s out, and 1.5 s jobs.
  3. The candidate awa migrate --sql --pending runs in an owner transaction, which must hold no relation lock on any awa or queue-storage table.
  4. The real awa migrate runs while a 0.6.9 producer enqueues and jobs are in flight. A sampler checks that the owner holds no lock above RowExclusive. The banked lanes must keep their exact sequence positions, the other lanes may only advance, and the report must say v40, helpers updated in awa and the custom schema, and routing unchanged.
  5. Candidate Rust workers join. A 0.6.9 worker is killed with a slow job in flight, and the candidate client is restarted. A lane is created after the patch.
  6. Rollback: the candidate stops, the 0.6.9 awa migrate runs and the patch must stay applied, and 0.6.9 workers restart and serve old queues and one created after the rollback. The 0.6.9 awa queue stats reads as the runtime role.
  7. Tightening, as the owner: prepare-queue for every queue, the per-sequence grant and its verification, then revoke CREATE. The 0.6.9 workers restart and serve provisioned queues; an unprovisioned queue fails with the provisioning hint.
  8. Accounting: every enqueued job completes, and twice only if its first worker was killed. Retried jobs complete on attempt ≥ 2, and scheduled jobs run once due and after the patch. Per-queue available / running / terminal must equal 0 / 0 / enqueued.

Three consecutive local runs (PostgreSQL 16) passed in about 100 s each, for example: 1,120 jobs, 4 workers killed, awa migrate 160 ms, 8 enqueues and 24 jobs in flight during it, and no strong locks sampled. Before the prepare-queue fix, the run failed at the tightening step with permission denied for sequence.

The PG 17/18 runs happen in CI. This container can't run them: apt.postgresql.org is blocked, and I didn't run third-party Postgres binaries here.

Validation

  • cargo fmt --all -- --check and SQLX_OFFLINE=true cargo clippy --all-targets --all-features -- -D warnings are clean.
  • In the awa-python workspace, cargo check --all-targets --all-features and uv lock --check pass.
  • The release workflow's version-consistency check, run locally with GITHUB_REF_NAME=v0.6.10, reports every surface at 0.6.10.
  • Tests, against PostgreSQL 16:
    • awa-model: lib 80 passed, plus every integration suite. The new prepared_lane_patch_replaces_helpers_without_table_locks asserts that the patch holds no relation lock on queue-storage objects and that the installer holds AccessExclusiveLock on leases.
    • least_privilege_migration_test passes;
    • dirty_marks_wait_free_test: 6 passed;
    • migration_test: 44 passed;
    • queue_storage_runtime_test: 120 passed. This includes:
    • awa-worker lib: 46 passed;
    • all awa-ui suites pass, including pool_limits_test and admin_read_cost_test;
    • frontend storage-transition.test.ts: 17 passed;
    • help_cli_test passes;
    • migrate_patch_cli_test: 3 passed (refused range flags, interrupted patch and retry, exported SQL under ON_ERROR_STOP);
    • least_privilege_runtime_test: 4 passed, including the stale custom schema and the runtime-created lane;
    • patch_upgrade_rehearsal_test: passed 3 consecutive runs.

Release gate

After merge, wait for the automatically triggered full CI branch-push run on the exact release/0.6.6 merge commit. Tag that exact commit v0.6.10 only after it passes; the release workflow enforces this exact-SHA gate. #496 is merged, so the v0.6.10 release takes its notes from this PR's CHANGELOG.md section.

hardbyte and others added 5 commits November 1, 2026 23:16
…sequences (0.6 schema patch)

Backport of #501 to the 0.6 line. The lane helpers and head-sync triggers ran
CREATE SEQUENCE IF NOT EXISTS even when the sequence existed, which requires
schema CREATE and defeats the documented migrator/runtime role split. They now
skip the DDL when the sequence exists and raise SQLSTATE 42501 with a
provisioning hint when it does not and the role lacks schema CREATE.

0.6 cannot add migration versions, so the change ships as the idempotent schema
patch prepared_lane_sequences: it redefines awa.install_queue_storage_substrate
and re-runs it for every installed queue-storage schema with its existing slot
counts and receipt mode (lease_claims or lease_claim_batches in
claim_ready_runtime). Fresh installs pick up the helper through v023.

v023 is split in two files so the patch embeds only the helper definition: the
default-schema installer, legacy fixups and schema_version row stay in
v023_install_default_queue_storage_substrate.sql, which V23_UP still appends.
The patch lint now skips the installer body, whose guarded ALTER/DROP TABLE
statements run only when the function is called.

Add QueueStorage::prepare_queue and `awa storage prepare-queue` to provision all
priorities, shards and stripes as the migrator.
…etained history

Backport of #580 and #581 to the 0.6 line.

On queue storage, admin::queue_overviews, state_counts and state_timeseries
built every current job through queue_storage_current_jobs_cte, whose
terminal_jobs branch expands each completion batch and joins it back to
ready_entries, so their cost grew with retained history. One production
deployment saw queue_overviews take 22 s, polled by an open admin
dashboard. queue_state_counts is not maintained once queue storage is
active, so it can't stand in.

- queue_overviews reads the live-work tables the worker gauges read, counts
  available rows exactly per lane from its claim cursor, and takes
  completed_last_hour from completion-batch sizes (less tombstoned members)
  and completed done_entries rows.
- state_counts counts completions from batch sizes less tombstones, and
  available rows per lane.
- state_timeseries bounds every source by the window before any join;
  terminal rows come only from done rows and batches finalised within it.
- JIT is off for all three; compiling cost more than running.

Running keeps 0.6's leases-only definition.
@hardbyte hardbyte added the full-ci Run the full CI matrix (Python build+test, E2E) on this PR label Nov 2, 2026 — with Claude
@coderabbitai

coderabbitai Bot commented Nov 2, 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: 083f3fa8-2253-46e6-9b2e-5d0b2587499d

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

Tip

🎯 Customize your risk assessments

Guide how CodeRabbit assesses merge risk and security architecture risk. Highlight sensitive changes and explain your security boundaries using Merge Risk Instructions and Security Risk Instructions in your review settings. Security architecture risk assessments require the Advanced plan or above.

✅ Automatic approvals, with your rules

Let CodeRabbit approve pull requests once required threads are resolved, pre-merge checks pass, and your risk limits are met. Enable Automatic approvals in review settings, with overrides for teams or file paths.

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

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.

@chatgpt-codex-connector

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

Copy link
Copy Markdown

Codex Review Summary

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-02T00:19:30.344434Z d7ca816 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: d7ca816325

ℹ️ 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".

Comment thread docs/security.md Resolved
Lane cursors need UPDATE on their sequences for setval; the SQL grant
block already said so, the pgroles manifest did not.
Rust 1.99's clippy flags the must_use that async-trait 0.1.89 puts on
the boxed future of Worker::perform. main already locks 0.1.92, whose
expansion passes; the 0.6 lockfiles follow it.
ubuntu-latest now resolves to 26.04, where the pinned Playwright refuses
to install Chromium. main pins the job the same way.
…ctions

Backport of #583.

When a client gives up on an admin request, the server drops it but
Postgres keeps running its query, and sqlx holds the connection until the
query ends. If the query then ended in an error, sqlx's release check
closed the connection with a hard close, which hangs on a TLS connection,
so the pool lost that slot for good (sqlx 0.8.6 here, as 0.9 on main).

awa serve now builds its pool with awa_ui::admin_pool_options and
admin_connect_options: a 60 s statement timeout as a startup option
(--statement-timeout / AWA_STATEMENT_TIMEOUT), a release hook that reads
an abandoned query's result and keeps the connection, and
client_connection_check_interval = 5s on Postgres 14+.
…e patch

The 0.6.10 prepared_lane_sequences patch re-ran
awa.install_queue_storage_substrate for every installed queue-storage schema.
The installer takes exclusive locks on hot tables such as leases and
deferred_jobs, rebuilds the head-sync triggers and scans retained ready
entries for backfills, all inside the migration transaction. Under live load
on a v0.6.9 database it stalled every worker and producer for 23 s and
deadlocked one enqueue.

The three lane helpers (ensure_lane_sequences and the two head-sync trigger
functions) now live in awa.refresh_lane_sequence_helpers(schema), as on main.
The installer calls it, so new schemas get the fixed helpers. On existing
databases the patch calls only the refresh, under the per-schema install
advisory lock. It takes no table locks and reads no queue rows: the same live
rehearsal applied it in 22 ms with no completion gap and no errors.

The applied probe now checks all three helpers and the refresh function. A
new test asserts the patch holds no relation lock on queue-storage objects,
and that the installer does.
…backport of #584)

A review of the #580/#581 backport found the last-hour completed count
scanning completion batches once per recent tombstone (5.4 s on a
production-sized fixture), and queue lag reading each lane's first row,
which hides an older job a queue move or priority aging appends behind
newer ones.

- Available counts walk each lane's segments from its claim cursor and
  probe the ready key per segment; a range without the key's leading ring
  slot scanned every retained ready row. They also yield the oldest
  available run_at, which is the queue lag again, as in 0.6.9.
- The tombstone check expands each recent batch in a tombstoned lane once
  and matches its members by hash.
- The timeseries reads its window's terminal rows with ready-key probes
  instead of through terminal_jobs, which joined them to every ready row.
- DLQ retry, purge and move invalidate the dashboard caches, as job
  mutations already do (from #579).

admin_read_cost_test serves every polled endpoint against ten times more
retained history and fails if a read does more work; on the previous head
it fails for /api/queues and the timeseries.
hardbyte and others added 10 commits November 2, 2026 12:06
…ed ready rows (backport of #586)

On queue storage, cancel_by_unique_key and cancel_by_unique_key_tx built
every available ready row before filtering by key, reading every retained
ready row, and missed jobs running on receipt claims not yet materialised
into leases, so those couldn't be cancelled by key (#585).

The key is now filtered where each source is read: available rows are
walked lane by lane from the claim cursor through ready_segments (only the
given queue's lanes when a queue is passed), scheduled and retryable rows
use the deferred unique-key index, and running jobs come from leases and
open receipt claims. open_receipt_running_claims_sql is ported from main,
with the closed-claim-batch skip.

The queue-storage tests from #586 cover each live state, oldest-first
among jobs sharing a key, the tx variant's rollback and commit-time
notice, and rows read staying flat as history and other queues' backlogs
grow. Against the previous lookup they fail: the running job isn't found,
and a scoped missing-key lookup reads 720 rows, then 5,206.
Port of the #586 follow-up: enter the callback wait so the test
cancels a job in waiting_external, and note that the open-receipt-claim
leg costs claim-ring retention.
…has it

The prepared_lane_sequences probe checked the three helpers in awa only.
A custom queue-storage schema restored or prepared with the 0.6.9 helpers
passed once awa did, and a database whose only active substrate was a
custom schema could never pass. The probe now checks the refresh
function, the installer and the helpers of every installed substrate,
from the catalogs so a schema the migrating role cannot use is reported.

migrations::schema_patch_status names what each missing patch still has
to repair, and migrations::run_with_report returns the schema versions,
the migrations and patches applied, what each patch repaired or still
lacks, and storage routing before and after.
- Refuse --from, --to and --version when applying; they only select a
  range to render, and the apply path always migrates fully.
- Wrap --sql output in one transaction that takes the migration
  advisory lock, with ON_ERROR_STOP guidance in the header;
  --no-transaction prints it bare for runners that own the transaction.
- Report the result: schema version, each patch and what it updated,
  and storage routing before and after. Exit 1 when a patch is still
  missing on some schema, naming it.

Tests cover the refused flags, a patch interrupted mid-run (nothing
applied, retry completes it) and exported SQL that fails under psql
with ON_ERROR_STOP and then applies cleanly.
A runtime role with schema CREATE owns the lane sequences it creates.
Provisioning such a lane as the owner (awa storage prepare-queue, the
first step of revoking runtime CREATE) failed with permission denied
for sequence: the head tables' BEFORE INSERT triggers run even for a
row ON CONFLICT DO NOTHING then skips, and they read and set the lane's
sequence. Lane inserts now skip rows that exist, under the same
per-lane advisory lock.
Released 0.6.9 artifacts (the awa-cli and awa-pg wheels) install the
schema as an owner role and run a separate runtime role in a custom
queue-storage schema with the grants a working 0.6.9 deployment needs.
Over a banked backlog with retries, scheduled jobs and slow jobs in
flight, the candidate awa migrate patches under traffic, with no lock
above RowExclusive on any awa or queue-storage relation (one owner
transaction audit plus sampling during the real run), idle lanes at
their exact positions, live lanes only advancing, and routing
unchanged. Candidate and released workers then run together through
killed and restarted workers; the fleet rolls back to 0.6.9 (including
its awa migrate, which must leave the patch in place); privileges are
tightened as the owner and the released workers restart. Every job must
complete, retried jobs on a later attempt, with per-queue counts
matching what was enqueued.

CI runs it on PostgreSQL 17 and 18 alongside the other full-CI jobs;
release.yml already requires this workflow to pass on the tagged SHA.
Evidence is uploaded as JSON.
One path: install the 0.6.10 CLI, run awa migrate once as the owner
under traffic, roll processes. States what does not need doing, what
the report means, how external runners apply the patch, how to roll
back (leave the patch installed), and the optional procedure for
revoking runtime CREATE, with a per-sequence grant because GRANT ON ALL
SEQUENCES fails on sequences a 0.6.9 runtime created.
cargo runs the rehearsal from awa/, so AWA_CANDIDATE_CLI=target/debug/awa
pointed at awa/target. Resolve it, and a relative CARGO_TARGET_DIR, from
the workspace root as the released-artifact paths already are.
The 0.6.10 lookup read every open receipt claim before filtering by
key. A long-running job keeps the claim ring from pruning, so on a
database with 120,000 retained row-local claims each lookup took about
9.7 s, worse than 0.6.9's 2.8 s on the same data.

Given a queue, the lookup now reads that queue's ready rows holding the
key through the lane index and checks only those jobs: available past
the claim cursor and not tombstoned, in leases, or on an open receipt
claim found by job_id. With no such row only the deferred_jobs key
index can hold the key, so that query alone answers, which also skips
planning the full lookup across partitioned tables (about 10 ms).

Measured on 180,200 retained ready rows and 120,300 claims in other
queues, through cancel_by_unique_key_tx rolled back: 1.7 ms for a
missing key, 11 ms and 20 ms to cancel a scheduled and an available
job (p50 of 100); 0.6.9's lookup took 2.8 s.

The cost test now also retains row-local claims; against the previous
lookup it reads 434 rows, then 32,912.
Review follow-ups on the queue-scoped unique-key lookup:

- Match leases by job_id, as open receipt claims already are. A job
  moved to another queue while available is leased on its new lane, so
  matching by its original ready row's lane missed it once it ran or
  waited there; 0.6.9 found it.
- Read the ready-row check and the deferred_jobs lookup in one
  statement, so a job promoted from deferred_jobs between two reads is
  not missed by both.
- Say what the compact-batch leg of the job-restricted open-claims
  query reads: every retained batch header, unnesting only batches that
  hold one of the jobs.

New test: two jobs moved to another queue, one running on a receipt
claim and one waiting on a callback, are both found by a lookup naming
their original queue.
The runtime page showed the card on every cluster running queue storage,
long after the cutover finished. It now appears only while an engine is
prepared or a mixed transition is in flight.
test_failure_path_metrics_reach_prometheus timed out waiting for its three
completed jobs: with the default one-second ring rotation, maintenance can
prune completed rows before the test counts them. The failure-path and
dashboard tests, which both count completed jobs, now build their clients
with a one-hour queue and lease rotation, as #588 did for the job output
tests on main.

hardbyte commented Nov 3, 2026

Copy link
Copy Markdown
Owner Author

Telemetry OTLP validation failed on d37e49ec, a frontend-only commit. test_failure_path_metrics_reach_prometheus timed out waiting for 3 completed jobs: it found 0, with the breakdown showing only failed: 2. This is the same race #588 fixed on main. The default one-second ring rotation lets maintenance prune completed rows before the test counts them. efb9a519 builds the failure-path and dashboard telemetry tests' clients with a one-hour rotation, matching #588. Neither test depends on rotation or pruning.


Generated by Claude Code

… its worker

The rehearsal failed on PostgreSQL 17 with "fail_once job 331 never
retried". A worker killed after claiming a fail_once job but before its
handler returns leaves no Retry event; heartbeat rescue then runs attempt 2,
which completes as designed. The accounting now requires a Retry event only
when attempt 1 started on a worker that survived, and still requires every
fail_once job to complete on attempt 2 or later.

hardbyte commented Nov 3, 2026

Copy link
Copy Markdown
Owner Author

Patch upgrade rehearsal (0.6.9, pg17) failed on efb9a519 with fail_once job 331 never retried. The upgrade itself succeeded. Only the final accounting assertion failed, and it was too strict.

A worker can be killed after claiming a fail_once job but before its handler returns. That leaves no Retry event, and heartbeat rescue then runs attempt 2, which completes as designed. Postgres logged two clients dropped with open transactions at 02:54:07, when the test kills workers c and d.

968e02f0 now requires a Retry event only when attempt 1 started on a worker that survived. Every fail_once job must still complete on attempt 2 or later. The rehearsal passes locally on PG16: 1,120 jobs, 4 workers killed, no strong locks.


Generated by Claude Code

…key cancel

The admin store is rebuilt from the active schema name with the default
config, so a queue-scoped unique-key lookup bound only the logical queue
name and missed available, running and waiting jobs in a striped queue's
queue#N lanes. The physical queues now come from the schema's queue_lanes.

A job that finishes between the lookup and the cancel made the
queue-storage branch return JobNotFound; it now returns None, as the
canonical engine does and the docs say.

Both predate 0.6.10. Port of the same fixes on main (#586); the new
striped-lanes test fails on the previous lookup.
…k, release date

A runner that tracks only schema versions sees nothing new in 0.6.10, and
the exported patch skips queue-storage schemas the running role cannot use
without failing. The upgrade guide now says to add the R__ script, run it as
the owner and confirm with awa migrate --sql --pending. The CHANGELOG links
the guide absolutely so the link works in the GitHub release body.
@hardbyte
hardbyte merged commit 5786c31 into release/0.6.6 Nov 3, 2026
17 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.

2 participants