| From: | Alexander Lakhin <exclusion(at)gmail(dot)com> |
|---|---|
| To: | David Rowley <dgrowleyml(at)gmail(dot)com>, Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us> |
| Cc: | pgsql-hackers <pgsql-hackers(at)postgresql(dot)org> |
| Subject: | Re: Test tidscan,sql is not immune to autovacuum in v14 |
| Date: | 2026-08-20 09:00:01 |
| Message-ID: | a58fdc63-4c21-4877-b5e9-b59f9b8ca13e@gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hello David and Tom,
20.08.2026 03:32, David Rowley wrote:
> I experimented, and I see the costs come out quite different if that
> were changed to:
>
> SELECT count(*) FROM tenk1 WHERE ctid IN (SELECT DISTINCT ctid FROM tenk1);
>
> The winning plan is;
>
> QUERY PLAN
> -------------------------------------------------------------------------------------------
> Aggregate (cost=1191.26..1191.27 rows=1 width=8)
> -> Hash Join (cost=695.00..1166.26 rows=10000 width=0)
> Hash Cond: (tenk1.ctid = tenk1_1.ctid)
> -> Seq Scan on tenk1 (cost=0.00..445.00 rows=10000 width=6)
> -> Hash (cost=570.00..570.00 rows=10000 width=6)
> -> HashAggregate (cost=470.00..570.00 rows=10000 width=6)
> Group Key: tenk1_1.ctid
> -> Seq Scan on tenk1 tenk1_1 (cost=0.00..445.00
> rows=10000 width=6)
> (8 rows)
My "good" plan in REL_14_STABLE is:
EXPLAIN (VERBOSE)
SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
QUERY PLAN
--------------------------------------------------------------------------------------
Aggregate (cost=1177.26..1177.27 rows=1 width=8)
Output: count(*)
-> Hash Join (cost=569.89..1152.27 rows=9995 width=0)
Hash Cond: (t1.ctid = t2.ctid)
-> Seq Scan on public.tenk1 t1 (cost=0.00..444.95 rows=9995 width=6)
Output: t1.ctid
-> Hash (cost=444.95..444.95 rows=9995 width=6)
Output: t2.ctid
-> Seq Scan on public.tenk1 t2 (cost=0.00..444.95 rows=9995 width=6)
Output: t2.ctid
(10 rows)
versus "bad":
EXPLAIN (VERBOSE)
SELECT count(*) FROM tenk1 t1 JOIN tenk1 t2 ON t1.ctid = t2.ctid;
QUERY PLAN
--------------------------------------------------------------------------------------
Aggregate (cost=1177.33..1177.34 rows=1 width=8)
Output: count(*)
-> Hash Join (cost=569.89..1152.34 rows=9995 width=0)
Hash Cond: (t2.ctid = t1.ctid)
-> Seq Scan on public.tenk1 t2 (cost=0.00..445.00 rows=10000 width=6)
Output: t2.ctid
-> Hash (cost=444.95..444.95 rows=9995 width=6)
Output: t1.ctid
-> Seq Scan on public.tenk1 t1 (cost=0.00..444.95 rows=9995 width=6)
Output: t1.ctid
(10 rows)
> and if I force the other Hash Join option via the debugger, I get:
>
> QUERY PLAN
> -------------------------------------------------------------------------------------
> Aggregate (cost=1302.50..1302.51 rows=1 width=8)
> -> Hash Join (cost=1040.00..1277.50 rows=10000 width=0)
> Hash Cond: (tenk1_1.ctid = tenk1.ctid)
> -> HashAggregate (cost=470.00..570.00 rows=10000 width=6)
> Group Key: tenk1_1.ctid
> -> Seq Scan on tenk1 tenk1_1 (cost=0.00..445.00
> rows=10000 width=6)
> -> Hash (cost=445.00..445.00 rows=10000 width=6)
> -> Seq Scan on tenk1 (cost=0.00..445.00 rows=10000 width=6)
> (8 rows)
>
> There's probably also some argument to the distinct semi-join query
> giving the code a bit more exercise due to the Hash Agg.
>
> Naturally, the current INNER JOIN query produces the same cost for
> each join order, under normal circumstances.
>
> This makes me wonder how significant the estimates varied in the two
> calls to estimate_rel_size() in the problem case you saw?
At the commit 74388a1ac, with this logging added:
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -60,2 +60,3 @@
#include "utils/syscache.h"
+#include "utils/lsyscache.h"
@@ -1255,5 +1256,9 @@ vac_estimate_reltuples(Relation relation,
*/
+char *rel_name = get_rel_name(relation->rd_id);
if (old_rel_pages == total_pages &&
scanned_pages < (double) total_pages * 0.02)
+{
+elog(LOG, "!!!vac_estimate_reltuples| rel_name: %s, old_rel_tuples: %lf", rel_name, old_rel_tuples);
return old_rel_tuples;
+}
@@ -1275,2 +1280,3 @@ vac_estimate_reltuples(Relation relation,
total_tuples = old_density * unscanned_pages + scanned_tuples;
+elog(LOG, "!!!vac_estimate_reltuples| rel_name: %s, floor(total_tuples + 0.5): %lf", rel_name, floor(total_tuples + 0.5));
return floor(total_tuples + 0.5);
I can see the following:
2026-08-20 04:23:48.978 EDT client backend[672080] pg_regress/sanity_check LOG: !!!vac_estimate_reltuples| rel_name:
tenk1, old_rel_tuples: 10000.000000
2026-08-20 04:23:48.978 EDT client backend[672080] pg_regress/sanity_check CONTEXT: while scanning relation "public.tenk1"
2026-08-20 04:23:48.978 EDT client backend[672080] pg_regress/sanity_check STATEMENT: VACUUM;
when the plan is expected, versus
2026-08-20 04:27:53.033 EDT client backend[674543] pg_regress/sanity_check LOG: !!!vac_estimate_reltuples| rel_name:
tenk1, floor(total_tuples + 0.5): 9995.000000
2026-08-20 04:27:53.033 EDT client backend[674543] pg_regress/sanity_check CONTEXT: while scanning relation "public.tenk1"
2026-08-20 04:27:53.033 EDT client backend[674543] pg_regress/sanity_check STATEMENT: VACUUM;
at 74388a1ac~1 (with the "bad" plan).
>> Given the current statistics, we won't see failures of this ilk anymore,
>> because it is not reproduced in REL_15_STABLE..master, due to 74388a1ac +
>> 4496020e6, which resulted in a different reltuples value returned for tenk1
>> during sanity_check/VACUUM and that indirectly affected the plan change.
> I had also thought that it might not be worth troubling over given
> that v14 has less than 3 months to live, but I believe it's generally
> bad practice to have queries in tests where multiple plans are so
> close together in cost. They're just too prone to very subtle changes
> that can result in rare plan changes (as per what you're reporting).
I absolutely agree with your and Tom's points -- the worthwhile thing here
to me is the evidence how (auto)vacuum/analyze can affect the planner's
decisions transiently (in a very small window). I remember a similar story
when tenk2's reltuples drifted 10000 ->9995 [1], but in that case it
affected sensitive queries that followed the drift.
Thank you for your attention to this anomaly!
[1] https://www.postgresql.org/message-id/66eb9a6e-fc67-a230-c5b1-2a741e8b88c6%40gmail.com
Best regards,
Alexander
| From | Date | Subject | |
|---|---|---|---|
| Next Message | shveta malik | 2026-08-20 09:03:56 | Re: Support EXCEPT for TABLES IN SCHEMA publications |
| Previous Message | shveta malik | 2026-08-20 08:39:37 | Re: Support EXCEPT for TABLES IN SCHEMA publications |