Skip to content

release: keep compacted query attribution honest - #198

Merged
NikolayS merged 7 commits into
NikolayS:mainfrom
samo-agent:agent/recover-136-query-attribution
Jul 27, 2026
Merged

release: keep compacted query attribution honest#198
NikolayS merged 7 commits into
NikolayS:mainfrom
samo-agent:agent/recover-136-query-attribution

Conversation

@NikolayS

@NikolayS NikolayS commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Recovery / scope

Addresses #136.

This draft replaces auto-closed #190. GitHub closed that PR when its stacked base branch, agent/w2-b1, was deleted after #182 merged; it was not rejected and had no submitted reviews, inline threads, or issue comments.

This reworks and supersedes the approach in #150 without modifying that branch. It is now based directly on main at 2ccdf65c613b7f79e00e83fcd4d1d7566e60d5c6, which contains #180, #182, #186, #189, and #194. This PR does not resolve or close #122. It also does not claim to resolve #156.

Rebase recovery

Dropped as already upstream:

The rebased series is:

  • 1683b78 — test: expose compacted query attribution gaps
  • 21f1bab — fix: keep compacted query attribution honest
  • ac28efc — docs: explain compacted query attribution
  • 12f30cf — test: bound query-attribution rollup setup
  • 8660412 — test: verify rebased query-attribution interactions

Current-main rebase

The only textual conflict was in blueprints/AAS_USER_STORIES.md. It was resolved by retaining #186's cadence/coverage limitation and #180's until-only/inverted-window contract while adding this PR's exact-query/raw and compacted-residual rules.

#194 changed rollup_minute() / rollup_hour() to return processed time grains rather than physical database rows. The #136 minute fixture now calls rollup_minute(1), and both residual fixtures separately assert their processed-grain count and their single physical row.

What changed

  • Rollup-backed, unfiltered top('query_id') emits a positive NULL residual equal to max(total wait counts - preserved named query counts, 0) per source row. The residual enters the ranking stream before n, so compacted-away load can win a top-N slot.
  • The residual scans each row's query_counts once with GROUPING SETS and its wait_counts once. It avoids fix: keep compacted query attribution honest #150's second query-array scan and window-wide materialized expansion.
  • Every explicit query_id filter uses raw samples. It raises only when ordinary planning selects a matching, database-scoped rollup with retained coverage in the requested slice before the physical oldest raw sample.
  • A fallback rollup name alone is not treated as data-loss proof. Young installs remain usable when their first minute/hour rollup covers only the same retained minute as raw.
  • Valid rollup_1h.minute_counts must contain a non-NULL pre-raw minute before the hourly envelope counts as lost exact-query history; legacy/incomplete detail remains conservative.
  • compare() NULL-safely pairs rollup residuals, and summary() renders them as (other / unattributed).

Combined rule on the landed #182/#189 base

Reader Exact query behavior Rollup query residual Grain contract
aas / timeline Any explicit query_id forces raw; matching pre-raw rollup history raises, while a young same-minute rollup does not. None #182 effective bounds and honest extrema remain authoritative.
top Explicit query_id, or query breakdown plus a wait filter, forces raw. Unfiltered rollup query breakdowns emit the residual before ranking and n. Hourly query/wait arrays remain hour-grain with NULL sub-hour extrema.
compare Exact query filters force both sides raw. Each rollup-backed query side emits one residual; the full join pairs NULL keys exactly once. Inherits each side's disclosed source/effective grain.

The known _pick_source_agg() end-watermark/completeness gap remains #122 and is intentionally not claimed fixed.

RED / GREEN

Rollup minute residual competes for top-N

RED recorded by #190:

ERROR:  residual top-N mismatch: key=111 source=rollup_1m seconds=3.00 pct=100.00
CONTEXT:  PL/pgSQL function inline_code_block line 98 at ASSERT

GREEN on the recovered branch:

NOTICE:  issue #136 rollup_1m residual GREEN: named=3.00/11.54 residual=23.00/88.46 top1=NULL compare=NULL/0.00
DO

The block also exact-asserts two rows, 26.00 backend-seconds, 100.00%, one NULL-safe compare residual with delta 0.00, and the exact summary label.

rollup_1h top-100 compaction

RED on current main (2ccdf65):

ERROR:  rollup_1h top-100 mismatch: rows=100 total=300.00 pct=100.00 residual=/ named=3.00/1.00 source= bucket= peak= p99=
CONTEXT:  PL/pgSQL function inline_code_block line 126 at ASSERT

GREEN:

NOTICE:  issue #136 rollup_1h top-100 GREEN: rows=101 total=400.00 pct=100.00 residual=100.00/25.00
DO

The fixture stores 100 named rows / 300 seconds after hourly top-set truncation while wait totals remain 400 seconds. The result is 101 rows / 400 seconds / 100%, with a 100-second residual and NULL peak/p99 at hour grain.

Young-install exact query filters

Current-main RED is a source-honesty failure, not an exception:

ERROR:  young-install aas mismatch: source=rollup_1m buckets=1 seconds=7.00
CONTEXT:  PL/pgSQL function inline_code_block line 143 at ASSERT

ERROR:  same-minute hourly query mismatch: error=<none> source=rollup_1h seconds=7.00
CONTEXT:  PL/pgSQL function inline_code_block line 166 at ASSERT

GREEN:

NOTICE:  issue #136 young-install GREEN: same-minute 1m/1h rollups ignored; aas=raw/7.00 until-only=raw/7.00 inverted=validated timeline=raw/0.12 top=raw/7.00 compare=raw/raw; other-db allowed; old rollup rejected
DO

Exact assertions cover aas (raw, one bucket, 7 seconds), timeline (60 rows, one data row, 0.12 AAS), top (7 seconds, 100%), compare (raw/raw, delta 0, 100/100), database scoping, and matching old-history rejection.

The strengthened fixture retains the young raw row and asserts the ordering between the logical ring boundary, old rollup window, and physical oldest sample. A mutation that passed _raw_retention_start() instead of _raw_oldest_sample() failed as intended:

ERROR:  ash.aas query_id retention error mismatch: ash.aas query_id filter should require raw
mutant_exit=3

Residual cost measurement retained from #190

PostgreSQL 17.9, 43,200 rollup_1m rows, 100 query pairs and 32 wait pairs per row:

Variant Median Relative
#182 base, no residual 27.425866s baseline
Straight #150 residual / second query scan 34.828003s 27.0% slower than base
Final per-row grouped stream 4.159544s 88.1% faster than naive; 84.8% faster than base

Naive #150 spilled about 20 GiB of temp reads/writes; the final stream spilled about 79.5 MiB. Naive and final outputs were identical: 4,363,200 rows and total count 29,160,000. This is adjacent to #156's cost class but does not resolve it.

Validation

CI remains responsible for the full PostgreSQL 14-19 matrix. This PR stays draft.

Landing verification (current main)

NikolayS#194 deliberately changed rollup_minute() from counting inserted database rows to counting every processed completed minute. The NikolayS#136 fixture starts twenty minutes in the past, so its unbounded setup call now correctly returns roughly twenty rather than the stale expected value 1. Limit that setup call to one grain and describe the assertion as one completed minute. The expected value remains 1 and every compacted-attribution assertion is unchanged. NikolayS#194 already records the return-contract change in RELEASE_NOTES.md.
@samo-agent
samo-agent force-pushed the agent/recover-136-query-attribution branch from e9944ed to 12f30cf Compare July 27, 2026 23:13
@NikolayS
NikolayS marked this pull request as ready for review July 27, 2026 23:16

@samo-agent samo-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex review standing in for REV — audit verdict: PASS on exact head 12f30cf.

Hand rebase: based directly on main 2ccdf65. The only conflict was blueprints/AAS_USER_STORIES.md; I retained both #186s historical-cadence/#137 limitation and #198s exact-query/raw plus NULL-residual rules.

RED/GREEN: current main is discriminating RED after the #194 setup adaptation: named compacted query load is 3.00 seconds at 100.00% instead of 11.54%. Exact head is GREEN: minute named/residual 3.00/11.54 and 23.00/88.46 (the NULL residual wins top-1); hourly top-100 returns 101 rows/400.00 seconds/100%, with NULL residual 100.00/25.00; young same-minute rollups stay raw while genuinely older coarser history is rejected.

#194 assertion adaptation: before, an unbounded rollup_minute() setup asserted return=1 and described one database row. After #194, that function intentionally counts every processed completed minute, so the 20-minute-old fixture correctly returned 20. The setup now calls rollup_minute(1), still asserts 1, and says one completed minute. No #136 attribution predicate changed. RELEASE_NOTES.md already records #194s return-contract behavior.

Cross-checks: #180 until-only/inversion/cross-horizon PASS; #182 grain honesty/partial-hour PASS; #194 one/two-datid, empty-grain, caught-up, row-count, and watermark contracts PASS. #189 physical/logical raw boundaries remain; #181s dynamic non-admin reader bundle covers the new helper. #122 remains explicitly unclaimed. Workflow YAML and diff checks pass. No merge-blocking finding.

@NikolayS
NikolayS marked this pull request as draft July 27, 2026 23:17
@NikolayS
NikolayS marked this pull request as ready for review July 27, 2026 23:18

@samo-agent samo-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex review standing in for REV — head correction / final delta: PASS on exact head 8660412.

The prior review was submitted after this head had already advanced, so its GitHub commit_id is correctly 8660412 although its opening sentence named the previously verified 12f30cf. The only new commit is test-only: it separately asserts #194s processed grain and physical row count, and adds until-only plus inverted-window coverage to the young exact-query fixture. The full canonical #136 step passes on exact 8660412. No product assertion was weakened and the PASS verdict stands.

@samo-agent samo-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex review standing in for REV — final CI-trigger head: PASS on exact head 5ef411f. This final commit is deliberately empty: its tree is byte-identical to reviewed head 8660412 (tree 47e7e3b296727cbf10043b9362bc53d61b4c5137) and exists only because draft-to-ready did not trigger the pull_request workflow. All prior RED/GREEN and cross-check conclusions therefore apply unchanged; the newly triggered full PR workflow must still pass before merge.

@NikolayS

Copy link
Copy Markdown
Owner Author

Temporarily closing and reopening to trigger the required pull_request CI event; the draft-to-ready and fork synchronize transitions created no workflow run.

@NikolayS NikolayS closed this Jul 27, 2026
@NikolayS NikolayS reopened this Jul 27, 2026
GitHub Actions rejects workflow files above 512,000 bytes before creating a run. The NikolayS#136 inline SQL raised test.yml from 506,109 to 535,600 bytes, which left NikolayS#198 with no check suite. Move that exact 749-line payload to devel/tests/query_attribution_assert.sql and invoke it with psql -f; the SQL is byte-identical after removing YAML indentation and every assertion remains unchanged. The workflow is now 506,353 bytes.

@samo-agent samo-agent left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Codex review standing in for REV — final verdict: PASS on exact head e349edd. Full PR CI is green across docs lint, PG14/15/16/17/18/19beta1, and the PG17 cron-off lane: https://github.com/NikolayS/pg_ash/actions/runs/30314259819. The final CI repair is assertion-neutral: GitHub rejected the prior 535,600-byte workflow above its 512,000-byte ceiling before creating any check suite. The 749-line #136 SQL payload is byte-identical after extraction (22,068 bytes, 30 assertions, SHA-256 26cdc4e29aa87e4de9664f26a73de1c40bd1b3a82d831e889ce3059eb0b08cb7), the psql connection and ON_ERROR_STOP behavior are unchanged, and test.yml is now 506,353 bytes. Prior discriminating RED/GREEN and #180/#182/#189/#194 cross-checks stand unchanged. No merge-blocking finding.

@NikolayS
NikolayS merged commit 6d72583 into NikolayS:main Jul 27, 2026
8 checks passed
@samo-agent
samo-agent deleted the agent/recover-136-query-attribution branch July 27, 2026 23:34
samo-agent added a commit to samo-agent/pg_ash that referenced this pull request Jul 27, 2026
Combining NikolayS#192 with NikolayS#198 and NikolayS#199 makes test.yml 513,005 bytes, above GitHub Actions' 512,000-byte workflow limit. Move NikolayS#199's exact 110-line transaction fixture to devel/tests and invoke it with the same psql connection and ON_ERROR_STOP behavior. The payload is byte-identical after removing YAML indentation; all nine assertions remain. The combined inventory is 848 inline plus 184 shared, 1,032 total.
samo-agent added a commit to samo-agent/pg_ash that referenced this pull request Jul 27, 2026
After NikolayS#198, grant_reader() correctly discovers 44 non-admin reader functions rather than the pre-NikolayS#198 count of 43. Update both the explicit-role and default pg_monitor feature contracts, and name _exact_query_uses_coarser(integer,integer,name) in a direct EXECUTE assertion so an unrelated 44th grant cannot mask a missing helper. The initial exact PG14/PG17 feature run failed 44-versus-43; all four feature-bearing surfaces now pass on both majors.
NikolayS pushed a commit that referenced this pull request Jul 28, 2026
* build: add pg_cron release-gate image

Keep the Docker tag independent from the numeric package major so the same image definition supports PostgreSQL 14 through 19beta2.

Relates to #160

* test: share release-gate SQL assertions

Source cron, degraded-mode, and schema snapshots from shared SQL while preserving all 594 workflow assertions. Include attnum in schema snapshots so column-order divergence is visible.

Relates to #160

* feat: add Docker release-gate runner

Run every gate surface across bounded parallel PostgreSQL workers, reuse exact CI step bodies, emit validated TSV and human summaries, and guarantee ID-scoped container and volume teardown.

Relates to #160

* test: add behavioral feature coverage

* test: add features release-gate surface

* test: align feature contracts with privilege fixes

* test: expose rollup return contract mismatch

* fix: normalize ash.config upgrade ordinals

Rebuild the v1.5 upgrade table in canonical fresh-install order while preserving the singleton row, defaults, constraints, indexes, ownership, ACLs, and comments. Reject unsupported custom dependencies and catalog properties transactionally instead of dropping them.

Add an upgrade regression that seeds non-default values and custom catalog objects, verifies preservation, and proves migration reapplication keeps the replacement table OID.

* docs: clarify config normalization transaction

* test: adapt release gate to current main

* test: include current PG17 upgrade coverage

* test: distinguish rollup progress from rows

* test: isolate feature pgss state

The shared feature step runs after the full workflow has accumulated hundreds of pg_stat_statements entries. Dropping and recreating the extension does not clear the preloaded shared state, so the exact marker query can be missing even though the reader code is correct.

Reset pg_stat_statements immediately after recreating it. This preserves the exact ash.top()/ash.samples() enrichment assertions while making their fixture independent of prior workflow activity.

* test: reap cron-path workload

Tag H-CI-3's three synthetic pg_sleep clients, terminate only those tagged backends on every exit, and wait for their client processes. The prior step left all three sessions live for later workflow steps, so features_lifecycle.sql correctly observed four active sleepers instead of its single owned fixture. Keep the exact active_count = 1 and decoded-row assertions unchanged; restore the quiescent precondition they test.

* test: make report diagnostics PG19-safe

PostgreSQL 19 beta 2 resolves text || jsonb as JSON concatenation, so both B5 ASSERT message expressions raise on the JSON token before reporting the contract. Cast the coverage object explicitly to text. The asserted bounds and minutes-with-data predicates are unchanged; this is a diagnostic typing fix, not a behavior change. The old exact head fails on PG19beta2, while the canonical fresh-install surface passes with these casts.

* test: share stale-rollup selector gate

Combining #192 with #198 and #199 makes test.yml 513,005 bytes, above GitHub Actions' 512,000-byte workflow limit. Move #199's exact 110-line transaction fixture to devel/tests and invoke it with the same psql connection and ON_ERROR_STOP behavior. The payload is byte-identical after removing YAML indentation; all nine assertions remain. The combined inventory is 848 inline plus 184 shared, 1,032 total.

* fix: floor report bounds before epoch conversion

ash.ts_from_timestamptz() rounds fractional seconds while casting to int4. report() previously converted first and divided by 60 afterward, so an endpoint at :59.5 or later crossed into the next minute before the documented minute floor. Floor v_from/v_to in timestamp space first. Add a deterministic +59.9-second feature assertion: the old code shifts both bounds and reports three of four data-bearing minutes; the corrected code preserves the exact four-minute coverage. Record the public behavior change in RELEASE_NOTES.md. No assertion is weakened.

* test: include query-history helper in reader bundle

After #198, grant_reader() correctly discovers 44 non-admin reader functions rather than the pre-#198 count of 43. Update both the explicit-role and default pg_monitor feature contracts, and name _exact_query_uses_coarser(integer,integer,name) in a direct EXECUTE assertion so an unrelated 44th grant cannot mask a missing helper. The initial exact PG14/PG17 feature run failed 44-versus-43; all four feature-bearing surfaces now pass on both majors.

---------

Co-authored-by: samo-agent <280144521+samo-agent@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: 2.0 readers prefer a stale rollup over complete raw data Performance: combine rollup prefixes with raw tails during routine lag

2 participants