[PATCH] minor optimization for ineq_histogram_selectivity()
Hello,
When studying the weird planner issue reported here [1]/messages/by-id/CAECtzeVPM4Oi6dTdqVQmjoLkDBVChNj7ed3hNs1RGrBbwCJ7Cw@mail.gmail.com, I came up with
the attached patch. It reduces the probability of calling
get_actual_variable_range().
The patch applies to the master branch.
How to test :
CREATE TABLE foo (a bigint, b TEXT) WITH (autovacuum_enabled = off);
INSERT INTO foo SELECT i%213, md5(i::text) from
generate_series(1,1000000) i;
VACUUM ANALYZE foo;
SELECT * FROM pg_stats WHERE tablename = 'foo' AND attname='a'\gx
CREATE INDEX ON foo(a);
DELETE FROM foo WHERE a = 212;
EXPLAIN (BUFFERS) SELECT count(a) FROM foo WHERE a > 208;
Without this patch, you will observe at least 4694 shared hits (which
are mostly heap fetches). If you apply the patch, you will observe very
few of them.
You should run the EXPLAIN on a standby, if you want to observe the heap
fetches more than one time (because of killed index tuples being ignored).
Best regards,
Frédéric
[1]: /messages/by-id/CAECtzeVPM4Oi6dTdqVQmjoLkDBVChNj7ed3hNs1RGrBbwCJ7Cw@mail.gmail.com
/messages/by-id/CAECtzeVPM4Oi6dTdqVQmjoLkDBVChNj7ed3hNs1RGrBbwCJ7Cw@mail.gmail.com
Attachments:
0001-minor-optimization-for-ineq_histogram_selectivity.patchtext/x-patch; charset=UTF-8; name=0001-minor-optimization-for-ineq_histogram_selectivity.patchDownload
From d7602f0731a3c5cac59cf2fc81bc336f800cb11a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Yhuel?= <frederic.yhuel@dalibo.com>
Date: Mon, 24 Oct 2022 16:33:26 +0200
Subject: [PATCH] minor optimization for ineq_histogram_selectivity()
Avoid calling of get_actual_variable_range() when possible.
With this change, 'probe' can still reach sslot.nvalues - 1 if needed,
so the algorithm still works correctly.
But if the right end of the hitogram bin is equal
to sslot.values[sslot.nvalues - 2], 'probe' will stay strictly less
than sslot.nvalues - 1 in the while loop, and we save a call to
get_actual_variable_range().
Use of get_actual_variable_range() can sometimes lead to surprising
slow planning time (see [1] and [2])
[1] https://www.postgresql.org/message-id/flat/CAECtzeVPM4Oi6dTdqVQmjoLkDBVChNj7ed3hNs1RGrBbwCJ7Cw%40mail.gmail.com
[2] https://www.postgresql.org/message-id/flat/36adf760-680b-7a4a-e019-64f4eaaf6ff7%40gmail.com
---
src/backend/utils/adt/selfuncs.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 69e0fb98f5..c1bc67705c 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -1108,7 +1108,7 @@ ineq_histogram_selectivity(PlannerInfo *root,
while (lobound < hibound)
{
- int probe = (lobound + hibound) / 2;
+ int probe = (lobound + hibound - 1) / 2;
bool ltcmp;
/*
--
2.30.2
On 10/24/22 17:26, Frédéric Yhuel wrote:
Hello,
When studying the weird planner issue reported here [1], I came up with
the attached patch. It reduces the probability of calling
get_actual_variable_range().The patch applies to the master branch.
How to test :
CREATE TABLE foo (a bigint, b TEXT) WITH (autovacuum_enabled = off);
INSERT INTO foo SELECT i%213, md5(i::text) from
generate_series(1,1000000) i;
VACUUM ANALYZE foo;
SELECT * FROM pg_stats WHERE tablename = 'foo' AND attname='a'\gx
CREATE INDEX ON foo(a);
DELETE FROM foo WHERE a = 212;
EXPLAIN (BUFFERS) SELECT count(a) FROM foo WHERE a > 208;
With the above example, the variables "lobound", "hibound", and "probe"
would vary like this :
without patch :
lobound hibound probe
---------------------------------------
0 101 50
51 101 76
77 101 89
90 101 95
96 101 98
99 101 100
99 100 99
99 99
with patch :
lobound hibound probe
---------------------------------------
0 101 50
51 101 75
76 101 88
89 101 94
95 101 97
98 101 99
98 99 98
99 99
So we find the correct right end of the histogram bin (99) in both
cases, but "probe" doesn't reach 100 in the latter one, and
get_actual_variable_range() is never called.
Now, if we'd run the query SELECT count(a) FROM foo WHERE a > 211 :
without patch :
lobound hibound probe
---------------------------------------
0 101 50
51 101 76
77 101 89
90 101 95
96 101 98
99 101 100
99 100 99
100 100
with patch :
lobound hibound probe
---------------------------------------
0 101 50
51 101 75
76 101 88
89 101 94
95 101 97
98 101 99
100 101 100
100 100
Here, the correct right end of the histogram bin (100) is also found is
both cases.
I'm well aware that an example doesn't prove the correctness of an
algorithm, though.
Best regards,
Frédéric
On 10/24/22 17:26, Frédéric Yhuel wrote:
Hello,
When studying the weird planner issue reported here [1], I came up with
the attached patch. It reduces the probability of calling
get_actual_variable_range().
This isn't very useful anymore thanks to this patch:
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=9c6ad5eaa957bdc2132b900a96e0d2ec9264d39c
Unless we want to save a hundred page reads in rare cases.
=?UTF-8?Q?Fr=c3=a9d=c3=a9ric_Yhuel?= <frederic.yhuel@dalibo.com> writes:
On 10/24/22 17:26, Frédéric Yhuel wrote:
When studying the weird planner issue reported here [1], I came up with
the attached patch. It reduces the probability of calling
get_actual_variable_range().
This isn't very useful anymore thanks to this patch:
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=9c6ad5eaa957bdc2132b900a96e0d2ec9264d39c
I hadn't looked at this patch before, but now that I have, I'm inclined
to reject it anyway. It just moves the problem around: now, instead of
possibly doing an unnecessary index probe at the right end, you're
possibly doing an unnecessary index probe at the left end. It also
looks quite weird compared to the normal coding of binary search.
I wonder if there'd be something to be said for leaving the initial
probe calculation alone and doing this:
else if (probe == sslot.nvalues - 1 && sslot.nvalues > 2)
+ {
+ /* Don't probe the endpoint until we have to. */
+ if (probe > lobound)
+ probe--;
+ else
have_end = get_actual_variable_range(root,
vardata,
sslot.staop,
collation,
NULL,
&sslot.values[probe]);
+ }
On the whole though, it seems like a wart.
regards, tom lane
On 11/23/22 16:59, Tom Lane wrote:
=?UTF-8?Q?Fr=c3=a9d=c3=a9ric_Yhuel?= <frederic.yhuel@dalibo.com> writes:
On 10/24/22 17:26, Frédéric Yhuel wrote:
When studying the weird planner issue reported here [1], I came up with
the attached patch. It reduces the probability of calling
get_actual_variable_range().This isn't very useful anymore thanks to this patch:
https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=9c6ad5eaa957bdc2132b900a96e0d2ec9264d39cI hadn't looked at this patch before, but now that I have, I'm inclined
to reject it anyway. It just moves the problem around: now, instead of
possibly doing an unnecessary index probe at the right end, you're
possibly doing an unnecessary index probe at the left end.
Indeed... it seemed to me that both versions would do an unnecessary
index probe at the left end, but I wasn't careful enough :-/
It also
looks quite weird compared to the normal coding of binary search.
That's right.
I wonder if there'd be something to be said for leaving the initial
probe calculation alone and doing this:else if (probe == sslot.nvalues - 1 && sslot.nvalues > 2) + { + /* Don't probe the endpoint until we have to. */ + if (probe > lobound) + probe--; + else have_end = get_actual_variable_range(root, vardata, sslot.staop, collation, NULL, &sslot.values[probe]); + }On the whole though, it seems like a wart.
Yeah... it's probably wiser not risking introducing a bug, only to save
an index probe in rare cases (and only 100 reads, thanks to 9c6ad5ea).
Thank you for having had a look at it.
Best regards,
Frédéric