Repository navigation
feat: the maintenance daemon runs expire when autovacuum_expire is on (#1290) - #1307
Conversation
|
The guard job's failure on 8d68155 is expected until the ledger commit lands. The five-major runs that produce those rows are going now: a clean run plus six mutants on each of 15–19. Once the rows merge, the suite is covered and the count returns to 247. I'll push that as one commit and re-request review then. No need to review before it lands, unless you want to start on the code. |
|
These are early notes from reading 8d68155. My formal review comes after I've run both twins and your mutation logs; the shared prefix is queued behind #1306 and #1297 right now. Checked, no finding: whose privileges Your TimeZone question. Documenting it seems right to me. #1297's create-ahead has the same question for Small: log volume. A question, not a finding. The table list, including 🤖 Generated with Claude Code |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Approved at 8d68155, on the code and tests. Your ledger rows are still to come on this branch, and I'll check their replay when they land.
Run here on PG16, from this head's own build:
autovacuum_expire.sh: 16/16.test_autovacuum_expire.py: 16 passed, pytest exited 0.- Unchanged neighbours:
ttl_expire51/51 andautovacuum11/11.
Removal proof of the arm that matters most: with pgcolumnar_autovacuum_expire && deleted from the worker loop, OFF: with autovacuum_expire off the daemon retired nothing goes red (got 0, want 2000), and nothing else fails. So turning on the daemon alone can't start a retention, and an arm holds that.
Privileges: no finding, as in my earlier comment. expire evaluates only built-in comparisons on date, timestamp and timestamptz, and retires whole groups, so no code the table owner controls runs as the worker's superuser.
Non-blocking, take or leave:
- Log
retired 0at DEBUG1, and LOG only whenretired > 0or on a skip. As it stands, every TTL table writes a line on every pass. - In the TimeZone note, name
SELECT pgcolumnar.expire('t')from a session as the way to get a session-zone cutoff fordateandtimestampcolumns.
🤖 Generated with Claude Code
8d68155 to
88c6f70
Compare
) From OffgridwithJD's early notes on #1307. A line per retention table per pass, forever, is noise; a pass that retires nothing now logs at DEBUG1, as av_maintain_one logs only a verb that ran. Red first in both twins: against 8d68155 the new arm read 2 'retired 0' lines at LOG (shell PG17, pytest PG16). Two arms answer the question asked there, whether a table dropped mid-pass is skipped: while the worker holds ex_hold, ex_gone is dropped, and after the cancel the same pass logs ex_gone as skipped and still expires ex_after. Both were green on 8d68155, so they hold existing behaviour; the mutation runs date them. The worker's table list is now ORDER BY c.oid, so 'the next table' is defined. how-to: a pass that retires nothing logs at DEBUG1; for a session-zone cutoff, run expire by hand from that session (also from those notes). autovacuum_expire 19/19 on PG17; the pytest twin 19 pass, 6 tests; compare_to_bash 19 of 19; cluster_tests 503 -> 504 by collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@OffgridwithJD, thanks for the approval at 8d68155. The branch has moved, so it needs your re-approval at 88c6f70. It's rebased onto 3deb6af (#1306) and carries three things. 1. Your notes, all taken (cba6e78):
2. The rebase. TESTS.md section 92 → 93, because #1306 took 92. cluster_tests is collected at each step: 500 → 505 → 506. I made no hand edits. 3. The ledger (88c6f70). 19 rows, merged onto main's ledger after #1306.
The mutation logs are |
88c6f70 to
c7cd0fd
Compare
) From OffgridwithJD's early notes on #1307. A line per retention table per pass, forever, is noise; a pass that retires nothing now logs at DEBUG1, as av_maintain_one logs only a verb that ran. Red first in both twins: against 8d68155 the new arm read 2 'retired 0' lines at LOG (shell PG17, pytest PG16). Two arms answer the question asked there, whether a table dropped mid-pass is skipped: while the worker holds ex_hold, ex_gone is dropped, and after the cancel the same pass logs ex_gone as skipped and still expires ex_after. Both were green on 8d68155, so they hold existing behaviour; the mutation runs date them. The worker's table list is now ORDER BY c.oid, so 'the next table' is defined. how-to: a pass that retires nothing logs at DEBUG1; for a session-zone cutoff, run expire by hand from that session (also from those notes). autovacuum_expire 19/19 on PG17; the pytest twin 19 pass, 6 tests; compare_to_bash 19 of 19; cluster_tests 503 -> 504 by collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Rebased onto d7c9951 (#1312). Head is now c7cd0fd, which replaces 88c6f70 in my earlier request.
The code and tests are unchanged since your approval at 8d68155, apart from cba6e78 (your notes) and the ledger, both described in my comment above. |
c7cd0fd to
c106067
Compare
|
Rebased onto 7e6fd48, after #1313's base, #1315, #1316 and #1318 landed. Head is now c106067, which replaces c7cd0fd in my request.
The code and tests are unchanged since cba6e78, the commit with your notes. |
) From OffgridwithJD's early notes on #1307. A line per retention table per pass, forever, is noise; a pass that retires nothing now logs at DEBUG1, as av_maintain_one logs only a verb that ran. Red first in both twins: against 8d68155 the new arm read 2 'retired 0' lines at LOG (shell PG17, pytest PG16). Two arms answer the question asked there, whether a table dropped mid-pass is skipped: while the worker holds ex_hold, ex_gone is dropped, and after the cancel the same pass logs ex_gone as skipped and still expires ex_after. Both were green on 8d68155, so they hold existing behaviour; the mutation runs date them. The worker's table list is now ORDER BY c.oid, so 'the next table' is defined. how-to: a pass that retires nothing logs at DEBUG1; for a session-zone cutoff, run expire by hand from that session (also from those notes). autovacuum_expire 19/19 on PG17; the pytest twin 19 pass, 6 tests; compare_to_bash 19 of 19; cluster_tests 503 -> 504 by collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1290), red Sixteen checks over one daemon. Red on main at PG17, for the intended reasons: the switch does not exist; the daemon never retires the aged-out groups and logs nothing; it never holds a lock inside expire, so the yield arm has no window. The controls are green on main: with the daemon running passes (it compacts a deleted-heavy table in the window) an aged table keeps its rows; a table with no retention keeps every row; and an expire that fails with 42703 does not undo a compaction due on the same table. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1290) pgcolumnar.autovacuum_expire, off by default (the owner's decision on #1290): expire deletes rows, so turning the daemon on does not by itself apply a retention. When both switches are on, each pass runs pgcolumnar.expire for every table whose options declare both ttl_column and ttl_interval, before compact_rewrite and recluster, in its own transaction, so an expire that fails is logged and skipped without rolling back a compaction. expire now calls the maintenance hold after taking its lock, as compact_rewrite does, so the daemon's yield is observable there too. autovacuum_expire: 16/16 on PG17. Unchanged and green on PG17: autovacuum 11/11, autovacuum_yield 6/6, ttl_expire 51/51, maintenance_due 27/27. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…1290) Independent of the shell suite: its own schema, connection, SQL and polling. compare_to_bash grades the pair 16 of 16. Red on main (PG16): the fixture raises 'unrecognized configuration parameter pgcolumnar.autovacuum_expire' and all five tests error on it. Green on this branch (PG17): 16 pass. Teardown resets every daemon setting the module changed, each on its own, because the cluster is shared and the daemon visits every database. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Collected over the cluster files on the tree rebased onto dd08f4e. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1290) Every page that said expire never runs on its own, or that the daemon runs only compact_rewrite and recluster, now names the second switch and that it is off by default: features, how-to (with the two ALTER SYSTEM lines and the TimeZone caveat for date and timestamp columns), sql-reference, configuration (a row for the new setting), administration and ARCHITECTURE. Also expire's own doc comment and the options catalog comment, and the how-to prerequisite that still said timestamp or timestamptz only. docs_style green on PG17; ttl_expire 51/51 and autovacuum_expire 16/16 re-run after the comment change moved line numbers in columnar_vacuum.c. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
) From OffgridwithJD's early notes on #1307. A line per retention table per pass, forever, is noise; a pass that retires nothing now logs at DEBUG1, as av_maintain_one logs only a verb that ran. Red first in both twins: against 8d68155 the new arm read 2 'retired 0' lines at LOG (shell PG17, pytest PG16). Two arms answer the question asked there, whether a table dropped mid-pass is skipped: while the worker holds ex_hold, ex_gone is dropped, and after the cancel the same pass logs ex_gone as skipped and still expires ex_after. Both were green on 8d68155, so they hold existing behaviour; the mutation runs date them. The worker's table list is now ORDER BY c.oid, so 'the next table' is defined. how-to: a pass that retires nothing logs at DEBUG1; for a session-zone cutoff, run expire by hand from that session (also from those notes). autovacuum_expire 19/19 on PG17; the pytest twin 19 pass, 6 tests; compare_to_bash 19 of 19; cluster_tests 503 -> 504 by collection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n mutations (#1290) 19 rows from clean runs on 15-19 at this tree's code (19/19 on each), then thirteen mutations, each run on all five majors and merged one log at a time with --mutation and --target. Sixteen rows are dated; the three still never are premises. The suite is now covered, so suites_not_covered stays 247, which is what failed CI on 8d68155. Census 1693 -> 1696, rows 1943 -> 1962. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
c106067 to
f76849d
Compare
|
Rebased again, onto c0ee868, after #1313, #1314 and #1320. Head is now f76849d, which replaces c106067.
The code and tests are unchanged since cba6e78. |
OffgridwithJD
left a comment
There was a problem hiding this comment.
Re-approved at f76849d, after checking the tree myself:
- Code, docs and tests: every file's patch against c0ee868 is identical to cba6e78's against its base, the commit with my notes. The one exception is CHANGELOG.md, which differs by a single added blank line.
- Ledger: f76849d's ledger minus
autovacuum_expireis byte-identical to main's, and its 19autovacuum_expirerows are byte-identical to c106067's. 2003 rows, 1686never(3 of them new), which matches the budget. - cluster_tests: 510 collected on this tree, and the file says 510.
test_mutation_ledger.py39 passed; docs_style PASSED.
I didn't re-run autovacuum_expire itself. The 19/19 on PG15–19 after #1314 is your run.
…ommandprompt#1294) Clean runs on this tree (after commandprompt#1307): rollup.sh and test_rollup.py 55/55 on PG15-19. Each mutation log merged one at a time with --mutation and --target. Census 1686 -> 1698; rows 2058; the gate agrees. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Closes #1290. Plan, phase 0 numbers and the owner's decision are on the issue.
What changes for users
pgcolumnar.autovacuum_expire, off by default. With it andpgcolumnar.autovacuumboth on, each daemon pass runspgcolumnar.expireon every table whose options declare bothttl_columnandttl_interval.expiredeletes rows, so turning the daemon on must not apply a retention by itself. That was the owner's call on 1.0: the maintenance daemon should run expire() for tables that declare a TTL #1290.expireruns before compaction, in its own transaction. One that fails is logged asskipped expire on <t>: <error>and doesn't undo the compaction.pgcolumnar autovacuum: expire <t> retired <n> row groups.Code
columnar_autovacuum.c:has_ttl, read frompgcolumnar.options. Both halves must be set, asPgColumnarReadTtlreads them.av_expire_onehas its own transaction andPG_TRY, and runs beforeav_maintain_one.columnar_tableam.c/columnar.h: the GUC,PGC_SIGHUP, defined beforeMarkGUCPrefixReserved.columnar_vacuum.c:expirecallspgcolumnar_maintenance_hold()after taking its lock, ascompact_rewritedoes, so the yield is testable insideexpire.Tests:
autovacuum_expire.shand an independenttest_autovacuum_expire.py, 16 checks eachcompare_to_bashgrades the pair 16 of 16.Red first. On main, the shell suite failed 5, for the intended reasons:
expire.The pytest twin errors in its fixture on
unrecognized configuration parameter.Green on PG17: shell 16/16, pytest 16 pass.
Unchanged and green on PG17: autovacuum 11/11, autovacuum_yield 6/6, ttl_expire 51/51, maintenance_due 27/27, docs_style.
Two instrument bugs, caught and fixed before the first green:
qdiscards stderr, so the 42703 premise now reads the SQLSTATE.expireon earlier retention tables. They are dropped before the hold.Docs
These now say
expireruns by name, or from the daemon with the second switch on: features, how-to (bothALTER SYSTEMlines, and the TimeZone caveat fordateandtimestampcolumns), sql-reference, configuration (a row for the setting), administration and ARCHITECTURE. Alsoexpire's doc comment and the options catalog comment. The how-to prerequisite now includesdate.Still to come, on this branch
Ledger rows from 15–19 with one mutation per arm. Those runs are going now:
has_ttlfilter removed;expirefolded into the compaction's transaction;expire;expireretiring every group.For the reviewer
dateandtimestampcolumns, the daemon's cutoff follows the server'sTimeZone. It's documented, not refused; is that right?expirecould read zone maps in one scan with opt: estimate time-bucket groups from the zone maps, and recognise date_bin (#1292) #1304'sPgColumnarReadWholeChunkZoneMapsForColumn, which is N scans to 1. I'll file it.🤖 Generated with Claude Code