feat: pgcolumnar_autovacuum — the maintenance daemon autovacuum cannot reach (#415) - #624
Conversation
jdatcmd
left a comment
There was a problem hiding this comment.
Approved. The daemon is faithful to core autovacuum, and the yield is genuinely load-bearing — verified on my lane.
Reviewed the whole daemon (launcher + worker + verb execution) and both suites against 7b08c4b-rebased HEAD, PG17 non-assert (your gate ran the assert builds).
What holds up, checked closely:
- The two invariants are real, not asserted. Lazy verbs only: the worker calls exactly
compact_rewriteandrecluster(both SUEL), never the AccessExclusive verbs — grep-confirmed. Thecompact_rewrite(rel, %g)2-arg call is valid (max_groupsdefaults to 0). The yield:av_mark_as_autovacuumsetsMyProc->statusFlags |= PROC_IS_AUTOVACUUMand mirrors it intoProcGlobal->statusFlags[pgxactoff]underProcArrayLock— that is verbatim the coreAutoVacWorkerMainpattern, so a lock waiter's deadlock-timeout check finds and cancels the worker. - Injection-safe. The table list is built with
quote_ident, and every verb SQL wraps it inquote_literal_cstr, so a hostile table name is a string literal cast to regclass, never code. - Transaction discipline is right. Each op is its own
StartTransactionCommand/PushActiveSnapshot/PG_TRY; thePG_CATCHcopies the error message out toTopMemoryContextbeforeFlushErrorState, logs where it failed, and continues — so a cancel or a mid-flightDROPaborts only that op.
The yield removal proof, re-run by me: neutering PROC_IS_AUTOVACUUM makes the AccessExclusive request time out at 57014 instead of being granted, the "granted fast" arm reds (slow(15s)), and the cancel-log arm reds — all three, on that one mutation, while the premises (launcher running, SUEL held mid-hold) stay green. That is exactly the shape a safety claim needs: the arm that would catch the daemon blocking production reds when the yield is removed. autovacuum 11/11, autovacuum_yield 6/6, restored green after the proof.
Two non-blocking notes (robustness, not safety — a follow-up, don't gate this):
sort_keyround-trips through a comma-joined string (maintenance_duereturnsarray_to_string(sorted_by, ','), the workerstring_to_array(...,',')s it back). A clustering column whose name literally contains a comma splits wrong;reclusterthen errors on the bogus column and thePG_CATCHskips it — so the table silently never reclusters, but nothing breaks. Passing thename[]through without the string detour closes it.char q[512]for the verb SQL:snprintftruncates safely, but a very longschema.table+ sort-key list would produce truncated SQL that errors and is skipped.psprintfremoves the ceiling. Same benign failure mode as (1).
Both are the good failure direction (skip-and-log, never corrupt or block), which is why they're notes rather than a request-changes.
This closes out #415's daemon. Merge when ready.
|
Approved and ready to land, but it now conflicts with today's merges and needs a rebase onto
The branch is on your fork, so I cannot push the rebase from the |
…ot reach (commandprompt#415) pgColumnar's online maintenance (compact_rewrite, recluster) lives in extension functions, not table-AM callbacks, so core autovacuum never runs it and a table's dead rows and clustering decay accumulate until someone runs the verbs by hand. This is the daemon commandprompt#415 asked for, on the merged measurement and maintenance_due() policy function and the self-gating recluster (commandprompt#415 part A, PR commandprompt#614). Shape mirrors core autovacuum: a launcher registered from _PG_init (needs shared_preload_libraries, which pgColumnar already requires) wakes every naptime and starts one worker per database; each worker asks maintenance_due() which columnar tables want attention and runs the recommended verb via SPI. Two invariants make it safe unattended: 1. It calls ONLY the ShareUpdateExclusiveLock verbs (compact_rewrite and the now-self-gating recluster), never vacuum/vacuum_sorted/cluster, so it cannot block readers or writers by construction. 2. Autovacuum's yield: the worker sets PROC_IS_AUTOVACUUM, so core's lock manager cancels its maintenance op the instant a backend queues for a conflicting stronger lock; each op runs in its own transaction inside PG_TRY, so a cancel aborts only that op and the worker continues. Off by default (pgcolumnar.autovacuum). GUCs: autovacuum, autovacuum_naptime, autovacuum_compact_threshold (0.2), autovacuum_recluster_threshold (0.05). test/autovacuum.sh: OFF leaves a deleted-heavy table alone (the control), enabling it (SIGHUP) compact_rewrites it (dead rows -> 0, survivors intact) and reclusters a decayed one (appended -> 0), disabling it stops maintenance. test/autovacuum_yield.sh: the yield, proven deterministically. A dev GUC (pgcolumnar.maintenance_hold_ms) holds SUEL inside compact_rewrite interruptibly; the suite catches the daemon mid-hold, requests AccessExclusive, and asserts the lock is granted FAST (the daemon yielded) not after the hold, and that the daemon logged the cancel. Removal proof (driver): dropping PROC_IS_AUTOVACUUM makes the lock time out (57014) -- the yield is load-bearing. Design in design/ISSUE_415_AUTOVACUUM.md; depends on PR commandprompt#614 for the safe recluster.
…commandprompt#415) The daemon section in docs/administration.md landed with the feature. This adds the four operator GUCs (pgcolumnar.autovacuum, autovacuum_naptime, autovacuum_compact_threshold, autovacuum_recluster_threshold) to the maintenance table in the configuration reference, documents the dev/test pgcolumnar.maintenance_hold_ms under internal settings (it is visible in pg_settings), and records the daemon under CHANGELOG Added.
…g changes Backfills the documentation for the work that merged today, audited against the SQL surface on main. sql-reference.md gains an "Object storage" section covering the three URL schemes (s3://, http://, https://), the separate objstore module loaded on first remote use, the credential model (ambient AWS_* environment for the function API; server endpoint/region plus user-mapping secrets for the FDW; credentials_required and the ambient-is-a-privilege rule), the pgcolumnar.objstore_allowed_endpoints allow-list with its unconditional link-local refusal, and export to s3:// with its multipart nothing-visible-before-complete property. The Import/export and external-Parquet intros, read_parquet, the FDW, export_parquet, and export_arrow point at it. parquet_schema's signature gains the field_id column (commandprompt#388) and describes it (NULL vs 0). configuration.md gains the allow-list GUC under a new Object storage subsection, since it is security-relevant and superuser-only. CHANGELOG Unreleased: object-storage read/write, the credential model, the allow-list, parquet_schema.field_id (Added); export atomicity and the batch-fold EXPLAIN report (Fixed). Docs only. No em/en-dash; every added sentence is within the 25-word STE limit; docs_style.sh passes 9/9. The pgcolumnar_autovacuum daemon (commandprompt#624) is documented with its own PR and is not covered here; recluster and sort_status were documented by commandprompt#625. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
9dc1440 to
4a81265
Compare
|
Rebased onto current
Re-gated on the rebased tree, not on the pre-rebase approval: preflight — ChronicallyJD |
jdatcmd
left a comment
There was a problem hiding this comment.
Re-approved on the rebased HEAD (c20ff3a + 4a81265). Rebase is clean, docs added, re-verified end to end.
The rebase resolved the four append-collisions correctly, confirmed by reading the merged regions:
run_all_versions.shSUITES: bothautovacuum/autovacuum_yieldand today'sobjstore_*/export_sinkare present and interleaved, none dropped.columnar.h: the four daemon externs and the two objstore externs both survive.- The GUC block and CHANGELOG merged without loss.
And the new second commit means the daemon now ships its own docs/administration.md, config-reference GUCs, and CHANGELOG entries, which closes the "daemon docs come separately" note from the #626 review.
Re-ran the full bar on my PG17 non-assert lane against this HEAD:
- Build clean,
-Wshadow -Werror. autovacuum11/11,autovacuum_yield6/6.docs_style.sh9/9 (the daemon config docs and theobjstore_allowed_endpointsentry coexist).- Yield removal proof re-run: neutering
PROC_IS_AUTOVACUUMreds all three yield arms (AccessExclusive times out at 57014, "granted fast" fails, cancel-log fails) while the premises stay green. The safety claim's load-bearing check still fails exactly when the yield is removed.
Everything from my first review stands. The two non-blocking robustness notes (comma-in-column-name string_to_array round-trip; q[512] truncation) remain optional follow-ups. Merging.
Ran bench/run_bench.sh (6M, PG18.4 non-assert) on current main 122fd5c. The storage, query, and mutation numbers are materially unchanged from the 2026-08-12 run: today's work is on the maintenance path and does not touch those code paths. Refreshed the header stamp and the "What changed" section to record the re-run. Added an "Online maintenance" section with the two measured changes today: - recluster self-gate (#614): a redundant recluster on an already-clustered 10M row table goes from 14,750 ms (rewrites all 67 groups, layout digest changes) to 0.96 ms (returns 0, layout byte-identical). Measured as a true before/after by building the pre-#614 commit bd983d9 and the current .so and running the same fixture on each. - autovacuum daemon (#624): a foreground scan is 174.3 ms with the daemon off and 173.9 ms with it on and actively compact_rewriting a separate deleted-heavy table. Non-blocking under lock conflict is proven separately by test/autovacuum_yield.sh. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TgAk1gqeME7DHpJw8xxybu
What
pgcolumnar_autovacuum— a maintenance daemon for the online upkeep core autovacuum cannot reach. pgColumnar'scompact_rewriteandreclusterlive in extension functions, not table-AM callbacks, so core autovacuum never runs them; a table's dead rows and clustering decay accumulate until someone runs the verbs by hand. This is the daemon #415 asked for, built on the mergedmaintenance_due()policy function and the self-gatingrecluster(Part A, #614, merged).Shape
Mirrors core autovacuum: a launcher registered from
_PG_init(needsshared_preload_libraries, which pgColumnar already requires) wakes every naptime and starts one worker per database; each worker asksmaintenance_due()which columnar tables want attention and runs the recommended verb over SPI, each op in its own transaction insidePG_TRY.Two invariants make it safe unattended
ShareUpdateExclusiveLockverbs (compact_rewriteand the now-self-gatingrecluster), nevervacuum/vacuum_sorted/cluster, so by construction it cannot block a reader or a writer — SUEL does not conflict withSELECT/INSERT/UPDATE/DELETE.PROC_IS_AUTOVACUUM, so core's lock manager cancels its maintenance op the instant a backend queues for a conflicting stronger lock (anALTER/DROP/TRUNCATEtakingAccessExclusiveLock). The cancel aborts only that op; the worker continues. A bounded hiccup, never an indefinite block.Off by default (
pgcolumnar.autovacuum). GUCs:autovacuum,autovacuum_naptime(60s),autovacuum_compact_threshold(0.2),autovacuum_recluster_threshold(0.05).Proof
test/autovacuum.sh— OFF leaves a deleted-heavy table alone (the control that keeps "it compacted" from being vacuously true), enabling it (SIGHUP)compact_rewrites it (dead rows → 0, survivors intact) and reclusters a decayed table (appended groups → 0), disabling it stops maintenance.test/autovacuum_yield.sh— the yield, proven deterministically. A dev GUC (pgcolumnar.maintenance_hold_ms) holds SUEL insidecompact_rewriteinterruptibly; the suite catches the daemon mid-hold (pollingpg_locks), requestsAccessExclusive, and asserts the lock is granted fast (the daemon yielded within ~deadlock_timeout) not after the full hold, and that the daemon logged the cancel. Removal proof: droppingPROC_IS_AUTOVACUUMmakes the lock time out (57014) — the yield is load-bearing, not decoration.Gate
Full bar, on the rebased-onto-main HEAD:
autovacuum+autovacuum_yieldpreflighted on assert builds pg15/16/17, full matrix (run_all_versions.sh) on pg18a + pg19a — all suites green on both.autovacuum_yieldis registered in the matrix.Notes
design/ISSUE_415_AUTOVACUUM.md.recluster.🤖 Generated with Claude Code