Summary
On current main (85402194873ccc607630e68b225e142649f2cd3b), both public rollup functions violate their documented return-value contracts when one time grain contains more than one database:
ash.rollup_minute(integer) is documented as “Returns minutes processed.”
ash.rollup_hour() is documented as “Returns hours processed.”
Each instead returns the number of (time grain, datid) rows upserted. One completed minute/hour with two databases returns 2, although exactly one minute/hour was processed.
Found while filling the systematic behavioral release-gate coverage requested by #160.
Exact reproduction
Run this after a fresh install in a database other than postgres so the fixture has two distinct database OIDs:
truncate table ash.sample;
truncate table ash.rollup_1m, ash.rollup_1h;
truncate table ash.wait_event_map restart identity;
insert into ash.wait_event_map (state, type, event)
values ('active', 'CPU*', 'CPU*');
update ash.config
set current_slot = 0,
sample_interval = interval '1 second',
last_rollup_1m_ts = ash.ts_from_timestamptz(
date_trunc('hour', statement_timestamp()) - interval '1 hour'
),
last_rollup_1h_ts = null
where singleton;
with anchor as (
select ash.ts_from_timestamptz(
date_trunc('hour', statement_timestamp()) - interval '1 hour'
) as sample_ts
), database_ids as (
select oid as datid
from pg_database
where datname in (current_database(), 'postgres')
), wait_id as (
select id
from ash.wait_event_map
where state = 'active' and type = 'CPU*' and event = 'CPU*'
)
insert into ash.sample (sample_ts, datid, active_count, data, slot)
select anchor.sample_ts,
database_ids.datid,
1,
array[-wait_id.id::int, 1, 0]::integer[],
0
from anchor
cross join database_ids
cross join wait_id;
select count(distinct datid) as distinct_datids_in_one_minute
from ash.sample;
select 1 as expected_documented_minutes_processed,
ash.rollup_minute(1) as actual_rollup_minute_return;
select count(*) as minute_rows_written from ash.rollup_1m;
update ash.config
set last_rollup_1m_ts = ash.ts_from_timestamptz(
date_trunc('hour', statement_timestamp())
),
last_rollup_1h_ts = ash.ts_from_timestamptz(
date_trunc('hour', statement_timestamp()) - interval '1 hour'
)
where singleton;
select 1 as expected_documented_hours_processed,
ash.rollup_hour() as actual_rollup_hour_return;
select count(*) as hour_rows_written from ash.rollup_1h;
Captured on PostgreSQL 17.9:
distinct_datids_in_one_minute = 2
expected_documented_minutes_processed = 1
actual_rollup_minute_return = 2
minute_rows_written = 2
expected_documented_hours_processed = 1
actual_rollup_hour_return = 2
hour_rows_written = 2
The minute watermark advances by exactly 60 seconds and the hour watermark by exactly 3600 seconds, confirming that one time grain was processed in each call.
Root cause
Both functions accumulate the DML row count:
get diagnostics v_count = row_count;
v_total := v_total + v_count;
Because the rollup keys include datid, ROW_COUNT measures (grain, datid) rows rather than processed grains.
Expected behavior
Either:
- return the documented number of distinct minute/hour grains processed, independent of database count; or
- deliberately redefine and document the return value as rollup rows written, with callers and tests updated accordingly.
The first option matches the current catalog comments and the batch-limit meaning of rollup_minute(batch_minutes).
Summary
On current
main(85402194873ccc607630e68b225e142649f2cd3b), both public rollup functions violate their documented return-value contracts when one time grain contains more than one database:ash.rollup_minute(integer)is documented as “Returns minutes processed.”ash.rollup_hour()is documented as “Returns hours processed.”Each instead returns the number of
(time grain, datid)rows upserted. One completed minute/hour with two databases returns2, although exactly one minute/hour was processed.Found while filling the systematic behavioral release-gate coverage requested by #160.
Exact reproduction
Run this after a fresh install in a database other than
postgresso the fixture has two distinct database OIDs:Captured on PostgreSQL 17.9:
The minute watermark advances by exactly 60 seconds and the hour watermark by exactly 3600 seconds, confirming that one time grain was processed in each call.
Root cause
Both functions accumulate the DML row count:
Because the rollup keys include
datid,ROW_COUNTmeasures(grain, datid)rows rather than processed grains.Expected behavior
Either:
The first option matches the current catalog comments and the batch-limit meaning of
rollup_minute(batch_minutes).