You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
#357 removed the decode scratch that inflated fetch cache entries, and that was a real fix worth ~63x on a narrow indexed point query. It did not remove the cliff. The 32 MB cap is still there, and a wide enough projected prefix still falls off it.
On merged main (23bd094, with #357), the same rows and the same Index Scan, varying only the number of aggregated columns:
aggregate columns
time
1
2,343 ms
2
2,834 ms
3
2,775 ms
4
2,833 ms
5
134,147 ms
6
165,095 ms
7
173,681 ms
A 47x jump between four and five columns. Flat either side of it. That is the same threshold signature #353 had, at a different position.
Correcting the record
I closed #353 stating "the cliff is gone rather than relocated", and said the same in my review of #357. That was wrong, and it was wrong because I generalised from one fixture shape.
My verification used count(*), max(usage_user), a narrow projection, and measured three group sizes. Within that shape the cliff really is gone. I concluded the cliff was gone in general. The correct claim was "gone at this projection width".
Worse, the counter-evidence was already in front of me. In the same run that showed q1 at 274 ms and q2 at 1,959 ms, q3 measured 135,911 ms against 161,972 before, an improvement of only 1.19x. q2 and q3 differ only in one aggregate versus five. I read the two large improvements and did not interrogate the one that barely moved.
#353 itself is correctly closed: its specific instance, the default stripe_row_limit putting a narrow query over the cap, is fixed. This issue is the general form that survives.
Why the cap is the wrong shape
The problem is not the value of COLUMNAR_FETCH_CACHE_MAX_BYTES (src/columnar_reader.c). It is that exceeding it degrades by ~47x with no intermediate behaviour. An entry one byte over the cap is not retained at all, so every fetch re-decodes the whole group.
Raising the cap moves the cliff again. #357 effectively did that by shrinking entries ~3x, and the cliff moved from "any wide table" to "five or more aggregate columns". Both are inside the space of ordinary queries.
What would remove it rather than move it
Retain a partial entry. Cache what fits and re-decode only the remainder, so going over the cap costs proportionally rather than totally. This is the only option that makes the failure gradual.
Cache per column.fix: keep the by-row-number decode's scratch out of the fetch cache (#353) #357's body rules this out for the deferred index fetch, which decodes a prefix rather than the touched columns. That objection stands for the current fetch path; it may not stand if the fetch path could be made to request only what the slot needs, which is a larger change.
Make the cap adaptive to work_mem or the table's decoded width. Still a cliff, just a differently placed one, and it makes the position depend on a setting users tune for other reasons.
(1) is the only one that changes the shape of the failure rather than its position.
SET max_parallel_workers_per_gather =4;
SELECT date_trunc('minute',time) m, max(usage_user), max(usage_system),
max(usage_idle), max(usage_nice), max(usage_iowait)
FROM cpu_pgc
WHERE hostname ='host_1'ANDtime>='2024-01-01'ANDtime<'2024-01-01'+ interval '12 hours'GROUP BY1;
Drop one max() and it runs in about 2.8 seconds. Keep five and it takes about 134.
#355 is that the planner takes an index scan on a columnar table for ordering without modelling per-row fetch cost. These compound: the planner chooses the index scan believing fetches are cheap, and then whether the query takes 2.8 seconds or 134 depends on which side of this cliff its projection lands. Fixing either one alone leaves the other able to produce the same surprise.
Summary
#357 removed the decode scratch that inflated fetch cache entries, and that was a real fix worth ~63x on a narrow indexed point query. It did not remove the cliff. The 32 MB cap is still there, and a wide enough projected prefix still falls off it.
On merged main (
23bd094, with #357), the same rows and the sameIndex Scan, varying only the number of aggregated columns:A 47x jump between four and five columns. Flat either side of it. That is the same threshold signature #353 had, at a different position.
Correcting the record
I closed #353 stating "the cliff is gone rather than relocated", and said the same in my review of #357. That was wrong, and it was wrong because I generalised from one fixture shape.
My verification used
count(*), max(usage_user), a narrow projection, and measured three group sizes. Within that shape the cliff really is gone. I concluded the cliff was gone in general. The correct claim was "gone at this projection width".Worse, the counter-evidence was already in front of me. In the same run that showed q1 at 274 ms and q2 at 1,959 ms, q3 measured 135,911 ms against 161,972 before, an improvement of only 1.19x. q2 and q3 differ only in one aggregate versus five. I read the two large improvements and did not interrogate the one that barely moved.
#353 itself is correctly closed: its specific instance, the default
stripe_row_limitputting a narrow query over the cap, is fixed. This issue is the general form that survives.Why the cap is the wrong shape
The problem is not the value of
COLUMNAR_FETCH_CACHE_MAX_BYTES(src/columnar_reader.c). It is that exceeding it degrades by ~47x with no intermediate behaviour. An entry one byte over the cap is not retained at all, so every fetch re-decodes the whole group.Raising the cap moves the cliff again. #357 effectively did that by shrinking entries ~3x, and the cliff moved from "any wide table" to "five or more aggregate columns". Both are inside the space of ordinary queries.
What would remove it rather than move it
work_memor the table's decoded width. Still a cliff, just a differently placed one, and it makes the position depend on a setting users tune for other reasons.(1) is the only one that changes the shape of the failure rather than its position.
Reproduction
Bench host,
cpu_pgc(100M rows, 21 columns,(hostname, time DESC)index), merged main:Drop one
max()and it runs in about 2.8 seconds. Keep five and it takes about 134.Script:
/root/width.shonpgcolumnar-audit.Interaction with #355
#355 is that the planner takes an index scan on a columnar table for ordering without modelling per-row fetch cost. These compound: the planner chooses the index scan believing fetches are cheap, and then whether the query takes 2.8 seconds or 134 depends on which side of this cliff its projection lands. Fixing either one alone leaves the other able to produce the same surprise.