Обсуждение: pg_plan_advice
As I have mentioned on previous threads, for the past while I have been working on planner extensibility. I've posted some extensibility patches previously, and got a few of them committed in Sepember/October with Tom's help, but I think the time has come a patch which actually makes use of that infrastructure as well as some further infrastructure that I'm also including in this posting.[1] The final patch in this series adds a new contrib module called pg_plan_advice. Very briefly, what pg_plan_advice knows how to do is process a plan and emits a (potentially long) long text string in a special-purpose mini-language that describes a bunch of key planning decisions, such as the join order, selected join methods, types of scans used to access individual tables, and where and how partitionwise join and parallelism were used. You can then set pg_plan_advice.advice to that string to get a future attempt to plan the same query to reproduce those decisions, or (maybe a better idea) you can trim that string down to constrain some decisions (e.g. the join order) but not others (e.g. the join methods), or (if you want to make your life more exciting) you can edit that advice string and thereby attempt to coerce the planner into planning the query the way you think best. There is a README that explains the design philosophy and thinking in a lot more detail, which is a good place to start if you're curious, and I implore you to read it if you're interested, and *especially* if you're thinking of flaming me. But that doesn't mean that you *shouldn't* flame me. There are a remarkable number of things that someone could legitimately be unhappy about in this patch set. First, any form of user control over the planner tends to be a lightning rod for criticism around here. I've come to believe that's the wrong way of thinking about it: we can want to improve the planner over the long term and *also* want to have tools available to work around problems with it in the short term. Further, we should not imagine that we're going to solve problems that have stumped other successful database projects any time in the foreseeable future; no product will ever get 100% of cases right, and you don't need to get to very obscure cases before other products throw up their hands just as we do. But, second, even if you're OK with the idea of some kind of user control over the planner, you could very well be of the opinion that what I've implemented here is utter crap. I've certainly had to make a ton of very opinionated decisions to get to this point, and you are entitled to hate them. Of course, I did have *reasons* for making the decisions, so if your operating theory as to why I did something is that I'm a stupid moron, perhaps consider an alternative explanation or two as well. Finally, even if you're OK with the concept and feel that I've made some basically reasonable design decisions, you might notice that the code is full of bugs, needs a lot of cleanup, is missing features, lacks documentation, and a bunch of other stuff. In that judgement, you would be absolutely correct. I'm not posting it here because I'm hoping to get it committed in November -- or at least, not THIS November. What I would like to do is getting some design feedback on the preliminary patches, which I think will be more possible if reviewers also have the main pg_plan_advice to look at as a way of understanding why the exist, and also some feedback on the pg_plan_advice patch itself. Now I do want to caveat the statement that I am looking for feedback just a little bit. I imagine that there will be some people reading this who are already imagining how great life will be when they put this into production, and begin complaining about either (1) features that it's missing or (2) things that they don't like about the design of the advice mini-language. What I'd ask you to keep in mind is that you will not be able to put this into production unless and until something gets committed, and getting this committed is probably going to be super-hard even if you don't creep the scope, so maybe don't do that, especially if you haven't read the README yet to understand what the scope is actually intended to be. The details of the advice mini-language are certainly open to negotiation; of everything, that would be one of the easier things to change. However, keep in mind that there are probably LOTS AND LOTS of people who all have their own opinions about what decisions I should have made when designing that mini-language, and an outcome where you personally get everything you want and everyone who disagrees is out of luck is unlikely. In other words, constructive suggestions for improvement are welcome, but please think twice before turning this into a bikeshedding nightmare. Now is the time to talk about whether I've got the overall design somewhat correct moreso than whether I've spelled everything the way you happen to prefer.[2] I want to mention that, beyond the fact that I'm sure some people will want to use something like this (with more feature and a lot fewer bugs) in production, it seems to be super-useful for testing. We have a lot of regression test cases that try to coerce the planner to do a particular thing by manipulating enable_* GUCs, and I've spent a lot of time trying to do similar things by hand, either for regression test coverage or just private testing. This facility, even with all of the bugs and limitations that it currently has, is exponentially more powerful than frobbing enable_* GUCs. Once you get the hang of the advice mini-language, you can very quickly experiment with all sorts of plan shapes in ways that are currently very hard to do, and thereby find out how expensive the planner thinks those things are and which ones it thinks are even legal. So I see this as not only something that people might find useful for in production deployments, but also something that can potentially be really useful to advance PostgreSQL development. Which brings me to the question of where this code ought to go if it goes anywhere at all. I decided to propose pg_plan_advice as a contrib module rather than a part of core because I had to make a WHOLE lot of opinionated design decisions just to get to the point of having something that I could post and hopefully get feedback on. I figured that all of those opinionated decisions would be a bit less unpalatable if they were mostly encapsulated in a contrib module, with the potential for some future patch author to write a different contrib module that adopted different solutions to all of those problems. But what I've also come to realize is that there's so much infrastructure here that leaving the next person to reinvent it may not be all that appealing. Query jumbling is a previous case where we initially thought that different people might want to do different things, but eventually realized that most people really just wanted some solution that they didn't have to think too hard about. Likewise, in this patch, the relation identifier system described in the README is the only thing of its kind, to my knowledge, and any system that wants to accomplish something similar to what pg_plan_advice does would need a system like that. pg_hint_plan doesn't have something like that, because pg_hint_plan is just trying to do hints. This is trying to do round-trip-safe plan stability, where the system will tell you how to refer unambiguously to a certain part of the query in a way that will work correctly on every single query regardless of how it's structured or how many times it refers to the same tables or to different tables using the same aliases. If we say that we're never going to put any of that infrastructure in core, then anyone who wants to write a module to control the planner is going to need to start by either (a) reinventing something similar, (b) cloning all the relevant code, or (c) just giving up on the idea of unambiguous references to parts of a query. None of those seem like great options, so now I'm less sure whether contrib is actually the right place for this code, but that's where I have put it for now. Feedback welcome, on this and everything else. Perhaps more than any other patch I've ever written, I know I'm playing with fire here just by putting this out on the list, but I'm nevertheless hopeful that something good can come of it, and I hope we can have a constructive discussion about what that thing should be. I think there is unquestionably is a lot of demand for the ability to influence the planner in some form, but there is a lot of room for debate about what exactly that should mean in practice. While I personally am pretty happy with the direction of the code I've written, modulo the large amount of not-yet-completed bug fixing and cleanup, there's certainly plenty of room for other people to feel differently, and finding out what other people think is, of course, the whole point of posting things publicly before committing them -- or in this case, before even finishing them.[3] If you're interested it contributing to the conversation, I urge you to start with the following things: (1) the README in the final patch; (2) the regression test examples in the final patch, which give a good sense of what it actually looks like to use this; and (3) the earlier patches, which show the minimum amount of core infrastructure that I think we need in order to make something like this workable (ideas on how to further reduce that footprint are very welcome). Thanks, -- Robert Haas EDB: http://www.enterprisedb.com [1] All of the earlier patches have been posted previously in some form, but the commit messages have been rewritten for clarity, and the "Allow for plugin control over path generation strategies" patch has been heavily rewritten since it was last posted; the earlier versions turned out to have substantial inadequacies. [2] This is not to say that proposal to modify or improve the syntax are unwelcome, but the bigger obstacle to getting something committed here is probably reaching some agreement on the internal details. Any changes to src/include/optimizer or src/backend/optimizer need careful scrutiny from a design perspective. Also, keep in mind that the syntax needs to fit what we can actually do: a proposal to change the syntax to something that implies semantics we can't implement is a dead letter. [3] Note, however, that a proposal to achieve the same or similar goals by different means is more welcome than a proposal that I should have done some other project entirely. I've already put a lot of work into these goals and hope to achieve them, at least to some degree, before I start working toward something else.
Вложения
- v1-0005-Allow-for-plugin-control-over-path-generation-str.patch
- v1-0003-Store-information-about-Append-node-consolidation.patch
- v1-0002-Store-information-about-elided-nodes-in-the-final.patch
- v1-0001-Store-information-about-range-table-flattening-in.patch
- v1-0004-Temporary-hack-to-unbreak-partitionwise-join-cont.patch
- v1-0006-WIP-Add-pg_plan_advice-contrib-module.patch
On Thu, Oct 30, 2025 at 3:00 PM Robert Haas <robertmhaas@gmail.com> wrote:
[..over 400kB of attachments, yay]
Thank You for working on this!
My gcc-13 was nitpicking a little bit (see
compilation_warnings_v1.txt), so attached is just a tiny diff to fix
some of those issues. After that, clang-20 run was clean too.
> First, any form of user control over the planner tends to be a lightning rod for criticism around here.
I do not know where this is coming from, but everybody I've talked to
was saying this is needed to handle real enterprise databases and
applications. I just really love it, how one could precisely adjust
the plan with this even with the presence of heavy aliasing:
postgres=# explain (plan_advice, costs off) SELECT * FROM (select *
from t1 a join t2 b using (id)) a, t2 b, t3 c WHERE a.id = b.id and
b.id = c.id;
QUERY PLAN
-----------------------------------------------------
Merge Join
Merge Cond: (a.id = c.id)
-> Merge Join
Merge Cond: (a.id = b.id)
-> Index Scan using t1_pkey on t1 a
-> Index Scan using t2_pkey on t2 b
-> Sort
Sort Key: c.id
-> Seq Scan on t3 c
Supplied Plan Advice:
SEQ_SCAN(ble5) /* not matched */
Generated Plan Advice:
JOIN_ORDER(a#2 b#2 c)
MERGE_JOIN_PLAIN(b#2 c)
SEQ_SCAN(c)
INDEX_SCAN(a#2 public.t1_pkey )
NO_GATHER(c a#2 b#2)
(17 rows)
postgres=# set pg_plan_advice.advice = 'SEQ_SCAN(b#2)';
SET
postgres=# explain (plan_advice, costs off) SELECT * FROM (select *
from t1 a join t2 b using (id)) a, t2 b, t3 c WHERE a.id = b.id and
b.id = c.id;
QUERY PLAN
----------------------------------------------------
Hash Join
Hash Cond: (b.id = a.id)
-> Seq Scan on t2 b
-> Hash
-> Merge Join
Merge Cond: (a.id = c.id)
-> Index Scan using t1_pkey on t1 a
-> Sort
Sort Key: c.id
-> Seq Scan on t3 c
Supplied Plan Advice:
SEQ_SCAN(b#2) /* matched */
Generated Plan Advice:
JOIN_ORDER(b#2 (a#2 c))
MERGE_JOIN_PLAIN(c)
HASH_JOIN(c)
SEQ_SCAN(b#2 c)
INDEX_SCAN(a#2 public.t1_pkey)
NO_GATHER(c a#2 b#2)
To attract a little attention to the $thread, the only bigger design
(usability) question that keeps ringing in my head is how we are going
to bind it to specific queries without even issuing any SETs(or ALTER
USER) in the far future in the grand scheme of things. The discussed
query id (hash), full query text comparison, maybe even strstr(query ,
"partial hit") or regex all seem to be kind too limited in terms of
what crazy ORMs can come up with (each query will be potentially
slightly different, but if optimizer reference points are stable that
should nail it good enough, but just enabling it for the very specific
set of queries and not the others [with same aliases] is some major
challenge).
Due to this, at some point I was even thinking about some hashes for
every plan node (including hashes of subplans), e.g.:
Merge Join // hash(MERGE_JOIN_PLAIN(b#2) + ';' somehashval1 + ';'+
somehahsval2 ) => somehashval3
Merge Cond: (a.id = c.id)
-> Merge Join
Merge Cond: (a.id = b.id)
-> Index Scan using t1_pkey on t1 a // hash(INDEX_SCAN(a#2
public.t1_pkey)) => somehashval1
-> Index Scan using t2_pkey on t2 b // hash(INDEX_SCAN(b#2
public.t2_pkey)) => somehashval2
and then having a way to use `somehashval3` (let's say it's SHA1) as a
way to activate the necessary advice. Something like having a way to
express it using plan_advice.on_subplanhashes_plan_advice =
'somehashval3: SEQ_SCAN(b#2)'. This would have the benefit of being
able to override multiple similiar SQL queries in one go rather than
collecting all possible query_ids, but probably it's stupid,
heavyweight, but that would be my dream ;)
-J.
Вложения
On Fri, Oct 31, 2025 at 5:59 AM Jakub Wartak <jakub.wartak@enterprisedb.com> wrote: > > First, any form of user control over the planner tends to be a lightning rod for criticism around here. > > I do not know where this is coming from, but everybody I've talked to > was saying this is needed to handle real enterprise databases and > applications. I just really love it, how one could precisely adjust > the plan with this even with the presence of heavy aliasing: Thanks for the kind words. I'll respond to the points about compiler warnings later. > To attract a little attention to the $thread, the only bigger design > (usability) question that keeps ringing in my head is how we are going > to bind it to specific queries without even issuing any SETs(or ALTER > USER) in the far future in the grand scheme of things. The discussed > query id (hash), full query text comparison, maybe even strstr(query , > "partial hit") or regex all seem to be kind too limited in terms of > what crazy ORMs can come up with (each query will be potentially > slightly different, but if optimizer reference points are stable that > should nail it good enough, but just enabling it for the very specific > set of queries and not the others [with same aliases] is some major > challenge). Yeah, I haven't really dealt with this problem yet. > Due to this, at some point I was even thinking about some hashes for > every plan node (including hashes of subplans), [...] > > and then having a way to use `somehashval3` (let's say it's SHA1) as a > way to activate the necessary advice. Something like having a way to This doesn't make sense to me, because it seems circular. We can't use anything in the plan to choose which advice string to use, because the purpose of the advice string is to influence the choice of plan. In other words, our choice of what advice string to use has to be based on the properties of the query, not the plan. We can implement anything we want to do in terms of exactly how that works: we can use the query ID, or the query text, or the query node tree. Hypothetically, we could call out to a user-defined function and pass the query text or the query node tree as an argument and let it do whatever it wants to decide on an advice string. The practical problem here is computational cost -- any computation that gets performed for every single query is going to have to be pretty cheap to avoid creating a performance problem. That's why I thought matching on query ID or exact matching on query text would likely be the most practical approaches, aside from the obvious alternative of setting and resetting pg_plan_advice.advice manually. But I haven't really explored this area too much yet, because I need to get all the basics working first. -- Robert Haas EDB: http://www.enterprisedb.com
On Fri, 31 Oct 2025, 12:51 Robert Haas, <robertmhaas@gmail.com> wrote:
On Fri, Oct 31, 2025 at 5:59 AM Jakub Wartak
<jakub.wartak@enterprisedb.com> wrote:
> > First, any form of user control over the planner tends to be a lightning rod for criticism around here.
>
> I do not know where this is coming from, but everybody I've talked to
> was saying this is needed to handle real enterprise databases and
> applications. I just really love it, how one could precisely adjust
> the plan with this even with the presence of heavy aliasing:
I really like the functionality of the current patch as well, even though I am suspicious of user control over the planner. By giving concise, precise control over a plan, this allows people who believe they can out-plan the planner to test their alternative, and possibly fail.
Whatever other UIs and integrations you build as you develop this towards you goal, please keep what's currently there user accessible. Not only for testing code, but also for testing users' belief that they know better.
Alastair
This weas recently shared in LinkedIn https://www.vldb.org/pvldb/vol18/p5126-bress.pdf For example it says that 31% of all queries are metadata queries, 78% have LIMIT, 20% of queries have 10+ joins, with 0.52% exceeding 100 joins. , 12% of expressions have depths between 11-100 levels, some exceeding 100. These deeply nested conditions create optimization challenges benchmarks don't capture.etc. This reinforces my belief thet we either should have some kind of two-level optimization, where most queries are handled quickly but with something to trigger a more elaborate optimisation and investigation workflow. Or alternatively we could just have an extra layer before the query is sent to the database which deals with unwinding the product of excessively stupid query generators (usually, but not always, some BI tools :) ) On Fri, Oct 31, 2025 at 10:18 PM Alastair Turner <minion@decodable.me> wrote: > > > On Fri, 31 Oct 2025, 12:51 Robert Haas, <robertmhaas@gmail.com> wrote: >> >> On Fri, Oct 31, 2025 at 5:59 AM Jakub Wartak >> <jakub.wartak@enterprisedb.com> wrote: >> > > First, any form of user control over the planner tends to be a lightning rod for criticism around here. >> > >> > I do not know where this is coming from, but everybody I've talked to >> > was saying this is needed to handle real enterprise databases and >> > applications. I just really love it, how one could precisely adjust >> > the plan with this even with the presence of heavy aliasing: > > > I really like the functionality of the current patch as well, even though I am suspicious of user control over the planner.By giving concise, precise control over a plan, this allows people who believe they can out-plan the planner to testtheir alternative, and possibly fail. > > Whatever other UIs and integrations you build as you develop this towards you goal, please keep what's currently thereuser accessible. Not only for testing code, but also for testing users' belief that they know better. > > Alastair
On Fri, Oct 31, 2025 at 5:17 PM Alastair Turner <minion@decodable.me> wrote: > I really like the functionality of the current patch as well, even though I am suspicious of user control over the planner.By giving concise, precise control over a plan, this allows people who believe they can out-plan the planner to testtheir alternative, and possibly fail. Indeed. The downside of letting people control anything is that they may leverage that control to do something bad. However, I think it is unlikely that very many people would prefer to write an entire query plan by hand. If you wanted to do that, why would you being using PostgreSQL in the first place? Furthermore, if somebody does try to do that, I expect that they will find it frustrating and difficult: the planner considers a large number of options even for simple queries and an absolutely vast number of options for more difficult queries, and a human being trying possibilities one by one is only ever going to consider a tiny fraction of those possibilities. The ideal possibility often won't be in that small subset of the search space, and the user will be wasting their time. If that were the design goal of this feature, I don't think it would be worth having. But it isn't. As I say in the README, what I consider the principal use case is reproducing plans that you know to have worked well in the past. Sometimes, the planner is correct for a while and then it's wrong later. We don't need to accept the proposition that users can out-plan the planner. We only need to accept that they can tell good plans from bad plans better than the planner. That is a low bar to clear. The planner never finds out what happens when the plans that it generates are actually executed, but users do. If they are sufficiently experienced, they can make reasonable judgements about whether the plan they're currently getting is one they'd like to continue getting. Of course, they may make wrong judgements even then, because they lack knowledge or experience or just make a mistake, but it's not a farcically unreasonable thing to do. I've basically never wanted to write my own query plan from scratch, but I've certainly looked at many plans over the years and judged them to be great, or terrible, or good for now but risky in the long-term; and I'm probably not the only human being on the planet capable of making such judgements with some degree of competence. > Whatever other UIs and integrations you build as you develop this towards you goal, please keep what's currently thereuser accessible. Not only for testing code, but also for testing users' belief that they know better. And this is also a good point. Knowledgeable and experienced users can look at a plan that the planner generated, feel like it's bad, and wonder why the planner picked it. You can try to figure that out by, for example, setting enable_SOMETHING = false and re-running EXPLAIN, but since there aren't that many such knobs relevant to any given query, and since changing any of those knobs can affect large swathes of the query and not just the part you're trying to understand better, it can actually be really difficult to understand why the planner thought that something was the best option. Sometimes you can't even tell whether the planner thinks that the plan you expected to be chosen is *impossible* or just *more expensive*, which is always one of the things that I'm keen to find out when something weird is happening. This can make answering that question a great deal easier. If some important index is not getting used, you can say "no, really, I want to see what happens with this query when you plan it with that index" -- and then it either gives you a plan that does use that index, and you can see how much more expensive it is and why, or it still doesn't give you a plan using that index, and you know that the index is inapplicable to the query or unusable in general for some reason. You don't necessarily have it as a goal to coerce the planner in production; your goal may very well be to find out why your belief that you know better is incorrect. -- Robert Haas EDB: http://www.enterprisedb.com
On Sat, Nov 1, 2025 at 12:10 PM Hannu Krosing <hannuk@google.com> wrote: > This reinforces my belief thet we either should have some kind of > two-level optimization, where most queries are handled quickly but > with something to trigger a more elaborate optimisation and > investigation workflow. > > Or alternatively we could just have an extra layer before the query is > sent to the database which deals with unwinding the product of > excessively stupid query generators (usually, but not always, some BI > tools :) ) I'd like to keep the focus of this thread on the patches that I'm proposing, rather than other ideas for improving the planner. I actually agree with you that at least the first of these things might be a very good idea, but that would be an entirely separate project from these patches, and I feel a lot more qualified to do this project than that one. -- Robert Haas EDB: http://www.enterprisedb.com
On Thu, Oct 30, 2025 at 9:00 PM Robert Haas <robertmhaas@gmail.com> wrote: > First, any form of user control over the > planner tends to be a lightning rod for criticism around here. I've > come to believe that's the wrong way of thinking about it: we can want > to improve the planner over the long term and *also* want to have > tools available to work around problems with it in the short term. The most frustrating real-world incidents I've had were in the course of customers planning a major version upgrade, or worse, after upgrading and finding that a 5 minute query now takes 5 hours. I mention this to emphasize that workarounds will be needed also to deal with rare unintended effects that arise from our very attempts to improve the planner. > Further, we should not imagine that we're going to solve problems that > have stumped other successful database projects any time in the > foreseeable future; no product will ever get 100% of cases right, and > you don't need to get to very obscure cases before other products > throw up their hands just as we do. Right. > it seems to be super-useful for testing. We have > a lot of regression test cases that try to coerce the planner to do a > particular thing by manipulating enable_* GUCs, and I've spent a lot > of time trying to do similar things by hand, either for regression > test coverage or just private testing. This facility, even with all of > the bugs and limitations that it currently has, is exponentially more > powerful than frobbing enable_* GUCs. Once you get the hang of the > advice mini-language, you can very quickly experiment with all sorts > of plan shapes in ways that are currently very hard to do, and thereby > find out how expensive the planner thinks those things are and which > ones it thinks are even legal. So I see this as not only something > that people might find useful for in production deployments, but also > something that can potentially be really useful to advance PostgreSQL > development. That sounds very useful as well. -- John Naylor Amazon Web Services
On Fri, Oct 31, 2025 at 5:59 AM Jakub Wartak <jakub.wartak@enterprisedb.com> wrote: > My gcc-13 was nitpicking a little bit (see > compilation_warnings_v1.txt), so attached is just a tiny diff to fix > some of those issues. After that, clang-20 run was clean too. Here's v2. Change log: - Attempted to fix the compiler warnings. I didn't add elog() before pg_unreachable() as you suggested; instead, I added a dummy return afterwards. Let's see if that works. Also, I decided after reading the comment for list_truncate() that what I'd done there was not going to be acceptable, so I rewrote the code slightly. It now copies the list when adding to it, instead of relying on the ability to use list_truncate() to recreate the prior tstate. - Deleted the SQL-callable pg_parse_advice function and related code. That was useful to me early in development but I don't think anyone will need it at this point; if you want to test whether an advice string can be parsed, just try setting pg_plan_advice.advice. - Fixed a couple of dumb bugs in pgpa_trove.c. - Added a few more regression test scenarios. - Fixed a couple of typos/thinkos. -- Robert Haas EDB: http://www.enterprisedb.com
Вложения
- v2-0005-Allow-for-plugin-control-over-path-generation-str.patch
- v2-0003-Store-information-about-Append-node-consolidation.patch
- v2-0002-Store-information-about-elided-nodes-in-the-final.patch
- v2-0001-Store-information-about-range-table-flattening-in.patch
- v2-0004-Temporary-hack-to-unbreak-partitionwise-join-cont.patch
- v2-0006-WIP-Add-pg_plan_advice-contrib-module.patch
Here's v3. I've attempted to fix some more things that cfbot didn't like, one of which was an actual bug in 0005, and I also fixed a stupid few bugs in pgpa_collector.c and added a few more tests. -- Robert Haas EDB: http://www.enterprisedb.com
Вложения
- v3-0005-Allow-for-plugin-control-over-path-generation-str.patch
- v3-0002-Store-information-about-elided-nodes-in-the-final.patch
- v3-0001-Store-information-about-range-table-flattening-in.patch
- v3-0004-Temporary-hack-to-unbreak-partitionwise-join-cont.patch
- v3-0003-Store-information-about-Append-node-consolidation.patch
- v3-0006-WIP-Add-pg_plan_advice-contrib-module.patch
Hi
On Thu Nov 6, 2025 at 1:45 PM -03, Robert Haas wrote:
> Here's v3. I've attempted to fix some more things that cfbot didn't
> like, one of which was an actual bug in 0005, and I also fixed a
> stupid few bugs in pgpa_collector.c and added a few more tests.
>
I've spent some time playing with these patches. I still don't have to
much comments on the syntax yet but I've noticed a small bug or perhaps
I'm missing something?
When I run CREATE EXTENSION pg_plan_advice I'm able to use the
EXPLAIN(plan_advice) but if try to open another connection, with the
extension already previously created, I'm unable to use once I drop and
re-create the extension.
tpch=# create extension pg_plan_advice;
ERROR: extension "pg_plan_advice" already exists
tpch=# explain(plan_advice) select 1;
ERROR: unrecognized EXPLAIN option "plan_advice"
LINE 1: explain(plan_advice) select 1;
^
tpch=# drop extension pg_plan_advice ;
DROP EXTENSION
tpch=# create extension pg_plan_advice;
CREATE EXTENSION
tpch=# explain(plan_advice) select 1;
QUERY PLAN
------------------------------------------
Result (cost=0.00..0.01 rows=1 width=4)
Generated Plan Advice:
NO_GATHER("*RESULT*")
And thanks for working on this. I think that this can be a very useful
feature for both users and for postgres hackers, +1 for the idea.
--
Matheus Alcantara
EDB: http://www.enterprisedb.com
On Mon, Nov 17, 2025 at 9:42 AM Matheus Alcantara <matheusssilv97@gmail.com> wrote: > I've spent some time playing with these patches. I still don't have to > much comments on the syntax yet but I've noticed a small bug or perhaps > I'm missing something? Cool, thanks for looking. I am guessing that the paucity of feedback thus far is partly because there's a lot of stuff to absorb -- though the main point at this stage is really to get some opinions on the planner infrastructure/hooks, which don't necessarily require full understanding of (never mind agreement with) the design of pg_plan_advice itself. > When I run CREATE EXTENSION pg_plan_advice I'm able to use the > EXPLAIN(plan_advice) but if try to open another connection, with the > extension already previously created, I'm unable to use once I drop and > re-create the extension. This is just an idiosyncrasy of PostgreSQL's extension framework. Whether or not EXPLAIN (PLAN_ADVICE) works depends on whether the shared module has been loaded, not whether the extension has been created. The purpose of CREATE EXTENSION is to put SQL objects, such as function definitions, into the database, but there's no SQL required to enable EXPLAIN (PLAN_ADVICE) -- or for setting the pg_plan_advice.advice GUC. However, running CREATE EXTENSION to establish the function definitions will incidentally load the shared module into that particular session. Therefore, the best way to use this module is to add pg_plan_advice to shared_preload_libraries. Alternatively, you can use session_preload_libraries or run LOAD in an individual session. If you don't care about the collector interface, that's really all you need. If you do care about the collector interface, then in addition you will need to run CREATE EXTENSION, so that the SQL functions needed to access it are available. -- Robert Haas EDB: http://www.enterprisedb.com
Here's v4. This version has some bug fixes and test case changes to 0005 and 0006, with the goal of getting CI to pass cleanly (which it now does for me, but let's see if cfbot agrees). -- Robert Haas EDB: http://www.enterprisedb.com
Вложения
- v4-0001-Store-information-about-range-table-flattening-in.patch
- v4-0003-Store-information-about-Append-node-consolidation.patch
- v4-0002-Store-information-about-elided-nodes-in-the-final.patch
- v4-0004-Temporary-hack-to-unbreak-partitionwise-join-cont.patch
- v4-0005-Allow-for-plugin-control-over-path-generation-str.patch
- v4-0006-WIP-Add-pg_plan_advice-contrib-module.patch
On Tue Nov 18, 2025 at 11:19 AM EST, Robert Haas wrote:
> Here's v4. This version has some bug fixes and test case changes to
> 0005 and 0006, with the goal of getting CI to pass cleanly (which it
> now does for me, but let's see if cfbot agrees).
Thanks for working on this, Robert! I think the design seems solid (and
very powerful) from a user perspective. I was curious what would happen
with row-level security interactions so I tried it out on a toy example
I put together a while back. I found one case where scan advice fails on
an intentionally naive/bad policy implementation, but I'm not sure why
and it seems like the kind of weird corner case that might be useful to
reason about. See attached for the setup script, then:
set pg_plan_advice.advice = 'BITMAP_HEAP_SCAN(item public.item_tags_idx)';
set item_reader.allowed_tags = '{alpha,beta}';
set role item_reader;
explain (plan_advice, analyze, verbose, costs, timing)
select * from item
where value ilike 'a%' and tags && array[1];
Seq Scan on public.item (cost=0.00..41777312.00 rows=54961 width=67) (actual time=2.947..8603.333 rows=6762.00
loops=1)
Disabled: true
Output: item.id, item.value, item.tags
Filter: (EXISTS(SubPlan exists_1) AND (item.value ~~* 'a%'::text) AND (item.tags && '{1}'::integer[]))
Rows Removed by Filter: 993238
Buffers: shared hit=1012312
SubPlan exists_1
-> Seq Scan on public.tag (cost=0.00..41.75 rows=1 width=0) (actual time=0.008..0.008 rows=0.21 loops=1000000)
Filter: ((current_setting('item_reader.allowed_tags'::text) IS NOT NULL) AND
((current_setting('item_reader.allowed_tags'::text))::text[]@> ARRAY[tag.name]) AND (item.tags @> ARRAY[tag.id]))
Rows Removed by Filter: 18
Buffers: shared hit=1000000
Planning Time: 1.168 ms
Supplied Plan Advice:
BITMAP_HEAP_SCAN(item public.item_tags_idx) /* matched, failed */
Generated Plan Advice:
SEQ_SCAN(item tag@exists_1)
NO_GATHER(item tag@exists_1)
Execution Time: 8603.615 ms
Since the policies don't contain any execution boundaries, all the quals
should be going into a single bucket for planning if I understand the
process correctly. The bitmap heap scan should be a candidate given the
`tags &&` predicate (and indeed if I switch to a privileged role, the
advice matches successfully without any policies in the mix), but gdb
shows the walker bouncing out of pgpa_walker_contains_scan without any
candidate scans for the BITMAP_HEAP_SCAN strategy.
I do want to avoid getting bikesheddy about the advice language so I'll
forbear from syntax discussion, but one design thought with lower-level
implications did occur to me as I was playing with this: it might be
useful in some situations to influence the planner _away_ from known
worse paths while leaving it room to decide on the best other option. I
think the work you did in path management should make this pretty
straightforward for join and scan strategies, since it looks like you've
basically made the enable_* gucs a runtime-configurable bitmask (which
seems like a perfectly reasonable approach to my "have done some source
diving but not an internals hacker" eyes), and could disable one as
easily as forcing one.
"Don't use this one index" sounds more fiddly to implement, but also
less valuable since in that case you probably already know which other
index it should be using.
Вложения
On Sat, Nov 22, 2025 at 7:43 PM Dian Fay <di@nmfay.com> wrote: > Thanks for working on this, Robert! Thanks for looking at it! I was hoping for a bit more in the way of responses by now, honestly. > Since the policies don't contain any execution boundaries, all the quals > should be going into a single bucket for planning if I understand the > process correctly. The bitmap heap scan should be a candidate given the > `tags &&` predicate (and indeed if I switch to a privileged role, the > advice matches successfully without any policies in the mix), but gdb > shows the walker bouncing out of pgpa_walker_contains_scan without any > candidate scans for the BITMAP_HEAP_SCAN strategy. I can understand why it seems that way, but when I try setting enable_seqscan=false instead of using pg_plan_advice, I get exactly the same result. I think this is actually a great example both of why this is actually a very powerful tool and also why it has the potential to be really confusing. The power comes from the fact that you can find out whether the planner thinks that the thing you want to do is even possible. In this case, that's easy anyway because the example is simple enough, but sometimes you can't set enable_seqscan=false or similar because it would change too many other things in the plan at that same time and you wouldn't be able to compare. In those situations, this figures to be useful. However, all this can do is tell you that the answer to the question "is this a possible plan shape?" is "no". It cannot tell you why, and you may easily find the result counterintuitive. And honestly, this is one of the things I'm worried about if we go forward with this, that we'll get a ton of people who think it doesn't work because it doesn't force the planner to do things which the planner rejects on non-cost considerations. We're going to need really good documentation to explain to people that if you use this to try to force a plan and you can't, that's not a bug, that's the planner telling you that that plan shape is not able to be considered for some reason. That won't keep people from complaining about things that aren't really bugs, but at least it will mean that there's a link we can give them to explain why the way they're thinking about it is incorrect. However, that will just beg the next question of WHY the planner doesn't think a certain plan can be considered, and honestly, I've found over the years that I often need to resort to the source code to answer those kinds of questions. People who are not good at reading C source code are not going to like that answer very much, but I still think it's better if they know THAT the planner thinks the plan shape is impossible even if we can't tell them WHY the planner thinks that the plan shape is impossible. We probably will want to document at least some of the common reasons why this happens, to cut down on getting the same questions over and over again. In this particular case, I think the problem is that the user-supplied qual item.tags @> ARRAY[id] is not leakproof and therefore must be tested after the security qual. There's no way to use a Bitmap Heap Scan without reversing the order of those tests. > I do want to avoid getting bikesheddy about the advice language so I'll > forbear from syntax discussion, but one design thought with lower-level > implications did occur to me as I was playing with this: it might be > useful in some situations to influence the planner _away_ from known > worse paths while leaving it room to decide on the best other option. I > think the work you did in path management should make this pretty > straightforward for join and scan strategies, since it looks like you've > basically made the enable_* gucs a runtime-configurable bitmask (which > seems like a perfectly reasonable approach to my "have done some source > diving but not an internals hacker" eyes), and could disable one as > easily as forcing one. I mostly agree. Saying not to use a sequential scan on a certain table, or not to use a particular index, or not to use a particular join method seem like things that would be potentially useful, and they would be straightforward generalizations of what the code already does. For me, that would principally be a way to understand better why the planner chose what it did. I often wonder what the planner's second choice would have been, but I don't just want the plan with the second-cheapest overall cost, because that will be something just trivially different. I want the cheapest plan that excludes some key element of the current plan, so I can see a meaningfully different alternative. That said, I don't see this being a general thing that would make sense across all of the tags that pg_plan_advice supports. For example, NO_JOIN_ORDER() sounds hard to implement and largely useless. The main reason I haven't done this is that I want to keep the focus on plan stability, or said differently, on things that can properly round-trip. You should be able to run a query with EXPLAIN (PLAN_ADVICE), then set pg_plan_advice.advice to the resulting string, rerun the query, and get the same plan with all of the advice successfully matching. Since EXPLAIN (PLAN_ADVICE) would never emit these proposed negative tags, we'd need to think a little bit harder about how that stuff should be tested. That's not necessarily a big deal or anything, but I didn't think it was an essential element of the initial scope, so I left it out. I'm happy to add it in at some point, or for someone else to do so, but not until this much is working well. -- Robert Haas EDB: http://www.enterprisedb.com