Re: pg_plan_advice (now with transparent SQL plan performance overrides - pg_stash_advice)
Jakub Wartak <jakub.wartak@enterprisedb.com>
On Tue, Mar 3, 2026 at 7:56 PM Robert Haas <robertmhaas@gmail.com> wrote: Hi Robert, > On Tue, Mar 3, 2026 at 6:42 AM Jakub Wartak > <jakub.wartak@enterprisedb.com> wrote: > > 1. First thought is that I found it quite surprising, we now have 3 modules > > and it's might cause confusion and lack of consistency: > > - 3 can to be in shared_preload_libraries (pg_stash_advice, > > pg_plan_advice, pg_collecti_advice) > > - 2 others can use CREATE EXTENSION (pg_stash_advice, pg_collect_advice), but > > "create extension pg_plan_advice;" fails > > so maybe they all should behave the same as people (including me) won't read > > the docs and just blindly add it here and there and issue CREATE EXTENSION, > > but it's going to be hard to remember for which ones (?) So we need more > > consistency? > > That's a possible point of view, but the flip side is that then it's > all-or-nothing. I think pg_plan_advice is a toolkit that bridge as a > rather large gulf between the theoretical power to walk plan trees and > use pgs_mask to modify behavior and the practicalities of actually > doing so. I think we would be well-advised to think of pg_plan_advice > as a core of functionality that, some day, we might even think of > moving into core, and pg_collect_advice and pg_stash_advice as things > that can be built around that core. I think it's important to have > demonstrations available that show that pg_plan_advice is not a > "sealed hood" that you must accept exactly as it is, but rather a > toolkit that you can do a variety of things with. pg_collect_advice > and pg_stash_advice are examples of what you can do, but many > variations on those same themes are possible. This is micro-thing, feel free to ignore, but well I was after something much more easy: `CREATE EXTENSION pg_plan_advice`to be no-op without any failure even if it doesnt provide any views or fucntions right now (so empty share/extension/pg_plan_advice.control and -1.0.sql) or at least some dummy function, just so that CREATE EXTENSION pg_plan_advice wouldn't fail. This is nothing technical, it's just sharp rough edge for user (but technically sound), that 2 are deployed with CREATE EXTENSION but 3rd one is not. I just think that if we put 3 into shared_preload_libraries then I won't have to think for which ones to exec CREATE EXTENSION (I would just blindly do it for all and the error wouldn't make somebody unhappy that something is potentially not working because CREATE EXTENSION is not for it) - it's pure user-focused usability feedback. > > 2. Should pgca always duplicate entries like that? (each call gets new entry?) > > Shouldn't it just update collection_time for identical calls of > > userid/dbid/queryid? > > To make it do that, we would need a completely different way of > storing entries, in order to avoid a linear scan of all collected data > for each new query. It would be a completely different module. I think > we probably want to have something like that, but it isn't this. See > previous point. > [..] > The functionality has not changed since I first posted this. It's only > had bug fixes and now some refactoring. OK, I misremembered it then. > > 3c. However for pgbench -M prepared, such online plan alterations are > > strangley not effective. > [....] > > 3d. ... however restarting restarting pgbench helps and the session > > changes plan (and thus > > performance characteristics). > > 3e. As this was pretty concerning, I've repeated the pgbench -M prepared > > excercise and I've figured out that I could achieve effect that I wanted ( > > boucing between fast <-> slow immediatley) by injecting call via gdb on that > > backend to InvalidateSystemCaches(). Kind of brute-force, but only > > then it worked > > One thing to keep in mind is that the whole point of -M prepared is to > avoid replanning. My guess is that what you're seeing here is that > only replan when something invalidates the plan, which seems more like > a feature than a bug, although possibly somebody is going to want a > way to force an invalidation when the stashed advice changes. That's a > pretty big hammer, though. Does this behavior go away if you use -M > extended? Yes with -M extended it is instant. I have found also that with -M prepared I can do simple one-time `analyze pgbench_accounts` (when changing SEQ_SCAN <-> INDEX_SCAN for that table) and that is also enough for the backend to immediatley see (and react to) to what's in the active configured stash even for future changes without further ANALYZEs. Not sure if pg_stash_advice needs a function to flush-force all backends, so the plans are 100% effecitve, as apparently we seem to have ANALYZE already, but it is not that obvious that one might want to use it. If there would be such function to gurantee, we probably wouldn't see complaints like 'I have done this and session still is using old plan'. > > > 5. QQ: will pg_stash_advice persist the stashes one day? > > I have no current plan to implement that, but who knows? OK, so perhaps docs for pg_create_advice_stash() and pg_set_stashed_advice() should mention those 'stashes' are not persistent across restarts. Without this I can already hear scream of some users from the future that they applied advice, it fixed problem and after some time it disappeared (In other RDBMS it is persistent, so users coming from there might have such expectations). Also related, we could also mention that this information is not replicated to standbys as this is in-memory only. > > 6. Any idea for better name than 'stash' ? :) It's some new term that is for > > sure not wildly recognized. Some other used name across industry: plan > > stability, > > advice freeze, plan freeze, plan force, baselines, plan management, force plan, > > force paths, SQL plan optimization profiles, query store (MSSQL). > > I'm up to change this if there's a consensus on what it should be > called, but that will require more than 1 vote. I picked stash because > I wanted to capture the idea of sticking something in a place where > you could grab it easily. Personally, my main critique of that name is > that this particular module matches by query ID and you could instead > match by $WHATEVER, so what would you call the next module that also > stashes advice strings but keyed in some slightly different way? > (Likewise, what do you call the next alternative to pg_collect_advice > that uses different collection criteria or storage?) > > Honestly, I was hoping and sort of expecting to get a lot more naming > feedback back when I first posted this. Don't call it advice, call it > hints or guidance or tweaks! Don't write HASH_JOIN(foo), write > JOIN_METHOD(foo, HASH_JOIN)! And so forth. On the one hand, the fact > that I didn't get that back then has made this less work, and I'm > still pretty happy with my choices. Furthermore, it's still not too > late to do some renaming if we agree on what that should look like. At > the same time, I don't particularly want to get into a fun and > exciting game of design-by-committee-under-time-pressure. As a > Demotivators poster said many years ago, "none of us is as dumb as all > of us." I freely admit that some of my naming and some of my design > decisions are almost certainly suboptimal, but at the same time, it > would be easy to underestimate the amount of thought that I've put > into some of the naming, so I'd only like to change it if we have a > pretty clear consensus that any given counter-proposal is better than > what I did. I especially do not want to go change it and then have to > change it all again because somebody else shows up with a new opinion. Well IMHO all the rest naming in pretty great shape and I think that pg_[collect|plan]_advice are great names too. It's just that `stash` keyword doesn't ring a bell to me at all that `pg_stash_advice` is related in any way to online/transparent/runtime plan modification and can be used to alter plans for other backends. Something like `pg_deploy_advice` or `pg_apply_advice` would be more in line with the other two, but perhaps it's just me.. > > 7. I saw you have written that in docs to be careful about memory use, but > > wouldn't be it safer if that maximum memory for pgca (when collecting in shared > > with almost infinite limite) would be still subject to like let's say 5% of s_b > > (or any other number here)? > > It depends on what you mean by safer. One thing that is unsafe is > running the computer out of memory. However, another thing that is > unsafe is getting the wrong answer. I felt that limiting the number of > queries was a good compromise. If you limit it to a certain amount of > memory usage, then (1) it's much harder to actually figure out whether > we're over budget and how much we need to free to be under budget and > (2) I suspect it's also harder to be sure whether you've set the limit > high enough to not lose any of the data you intended to retain. I > think that a fixed limit like 5% of shared_buffers would be, bluntly, > completely nuts. There is no reason to suppose that the amount of > memory someone is willing to use to store advice has anything to do > with the size of shared_buffers. If you have 100 sessions running > pgbench and you enabled the local collector with that limit in all of > them, you would be using 500% of shared_buffers in advice-storage > memory and that would probably take down the server. On the other > hand, if you're using the shared collector with shared_buffers=2GB, > that is only 10MB, which might easily be too small to store all the > data you care about even on a test system. > > The reason I set the limits in terms of number of queries was that (1) > it's easy for the code to figure out whether it's over the limit and > easy for it to reduce utilization to the limit and (2) I thought that > people using this were likely to have a better idea how many queries > they wanted to collect than how many MB/GB they wanted to collect. Of > course, (1) could be solved with enough effort and (2) could be > wrong-headed on my part, but if somebody wanted this changed, it would > have been nice to know that a few months ago when I first posted this > rather than now. I havent reported it earlier, well because there was little sense playing with collection implementation much earlier if we didn't have pinning of the plans itself (and the whole debate query_id also seemed pointless back then). Well anyway, it is much like work_mem danger, but work_mem is about well specific amount of memory (* N plan nodes) so it's just easier to put safer value there. How about if we would just measure (estimate) with some small table number of entries vs memory used and put that into the docs?, so users are wary that they shouldn't just blidnly set it to high value? E.g. with 1000 local limit I get ~280kB for pg_collect_advice context and with 1000000 local limit I've got it to ~50MB and stopped looking further (it was still growing). Itself that's not terrible but higher values with lots of backends might cause huge memory pressure (OOMs). Or maybe other idea: is there is possibility of making GUCs like local_collection_limits/local_collector settable only using SET/SET LOCAL, but not global? I mean what's the point of having being able to collect locally system-wide when realistically I cannot pull it back from backend-local memory? (and this removes the danger of multple backends goind wild with memory together). Below maybe useful for others - if they trying this, if testing performance please ensure you do not have Casserts as with Casserts enabling higher local_collection_limits are barley usable: progress: 137.0 s, 1516.9 tps, lat 0.659 ms stddev 0.174, 0 failed progress: 138.0 s, 1469.1 tps, lat 0.680 ms stddev 0.174, 0 failed progress: 139.0 s, 1050.0 tps, lat 0.952 ms stddev 0.206, 0 failed progress: 140.0 s, 922.0 tps, lat 1.085 ms stddev 0.194, 0 failed [..] progress: 143.0 s, 664.0 tps, lat 1.504 ms stddev 0.377, 0 failed progress: 144.0 s, 595.0 tps, lat 1.681 ms stddev 0.386, 0 failed progress: 145.0 s, 531.0 tps, lat 1.883 ms stddev 0.402, 0 failed [..] progress: 159.0 s, 260.0 tps, lat 3.840 ms stddev 0.607, 0 failed progress: 160.0 s, 271.0 tps, lat 3.702 ms stddev 0.447, 0 failed resetting it back to 0 or disabling local collector and reloading won't fix it, backend needs to re-establish connection. Even with just 10000 local collection limit it just gets down from ~1500 tps to 900 tps. It's seems the impact on CPU coming to be from exec_simple_query() -> finish_xact_command() -> MemoryContextCheck() -> AllocSetCheck(), so memory context validation that have literally nothing to do with with this patch (other than it using a lot of memory in those scenarios) > > 9. If IsQueryIdEnabled() is false (even after trying out to use 'auto') > > shouldn't this module raise warning when pg_stash_advice.stash_name != NULL? > > I think the bar for printing a warning on every single query is > extremely high. Doing that just because of a questionable > configuration setting doesn't seem like a good idea. Why on every single query? I'm thinking that this should bail out in pgca&pgsa during initialization of those modules. What's the point of those modules if queryid is always 0? (I'm assuming somebody has compue_query_id=off and still loaded those modules). > > 10. I'm was here mainly for v18-0007 (pg_stash_advice), but this still looks > > like some small bug to me (minmax matching in v18-0003): > > [..] > > I think this is just out of scope for now. It's documented that we > don't have a way of controlling aggregation behavior at present. [..] Oh ok, I've completley missed "pgplanadvice-limitations" SGML section about aggregates, so sure - fair limitation. BTW: the good news is that I haven't seen a single crash when throwing wild stuff on it or having strange ideas at pg_stash_advice usage. -J.
Commits
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
pg_plan_advice: Fix another unique-semijoin bug.
- 4321dcad475b 19 (unreleased) landed
-
pg_plan_advice: Export feedback-related definitions.
- c644aca24089 19 (unreleased) landed
-
pg_plan_advice: Fix a bug when a subquery is pruned away entirely.
- 0f93ebb3112d 19 (unreleased) landed
-
pg_plan_advice: Add alternatives test to Makefile.
- 1faf9dfa4796 19 (unreleased) landed
-
pg_plan_advice: Handle non-repeatable TABLESAMPLE scans.
- 3311ccc3d24b 19 (unreleased) landed
-
pg_stash_advice: Allow stashed advice to be persisted to disk.
- c10edb102ada 19 (unreleased) landed
-
Add pg_stash_advice contrib module.
- e8ec19aa321a 19 (unreleased) landed
-
pg_plan_advice: Avoid assertion failure with partitionwise aggregate.
- e2ee95233cab 19 (unreleased) landed
-
pg_plan_advice: Invent DO_NOT_SCAN(relation_identifier).
- 6455e55b0da4 19 (unreleased) landed
-
Add an alternative_plan_name field to PlannerInfo.
- 26255a320733 19 (unreleased) landed
-
pg_plan_advice: Refactor to invent pgpa_planner_info
- 5dcb15e89af2 19 (unreleased) landed
-
Respect disabled_nodes in fix_alternative_subplan.
- 47c110f77e75 19 (unreleased) landed
-
get_memoize_path: Don't exit quickly when PGS_NESTLOOP_PLAIN is unset.
- dc47beacaa0b 19 (unreleased) landed
-
test_plan_advice: Set TAP test priority 50 in meson.build.
- 12444183e401 19 (unreleased) landed
-
pg_plan_advice: Avoid a crash under GEQO.
- 01b02c0ecad1 19 (unreleased) landed
-
Test pg_plan_advice using a new test_plan_advice module.
- e0e4c132ef2b 19 (unreleased) landed
-
pg_plan_advice: Always install pg_plan_advice.h, and in the right place
- 59dcc19b397f 19 (unreleased) landed
-
pg_plan_advice: Fix failures to accept identifier keywords.
- 5e72ce2467c1 19 (unreleased) landed
-
Add pg_plan_advice contrib module.
- 5883ff30b02c 19 (unreleased) landed
-
Allow extensions to mark an individual index as disabled.
- 0fbfd37cefb7 19 (unreleased) landed
-
Replace get_relation_info_hook with build_simple_rel_hook.
- 91f33a2ae92a 19 (unreleased) landed
-
Store information about Append node consolidation in the final plan.
- 7358abcc6076 19 (unreleased) landed
-
Store information about elided nodes in the final plan.
- 0d4391b265f8 19 (unreleased) landed
-
Store information about range-table flattening in the final plan.
- adbad833f3d9 19 (unreleased) landed
-
Pass cursorOptions to planner_setup_hook.
- 0f4c8d33d49d 19 (unreleased) landed
-
Fix PGS_CONSIDER_NONPARTIAL interaction with Materialize nodes.
- cbdf93d47122 19 (unreleased) landed
-
Fix mistakes in commit 4020b370f214315b8c10430301898ac21658143f
- 71c1136989b3 19 (unreleased) landed
-
Allow for plugin control over path generation strategies.
- 4020b370f214 19 (unreleased) landed
-
Update some comments for fasthash
- 7892e2592471 19 (unreleased) landed
-
Allow passing a pointer to GetNamedDSMSegment()'s init callback.
- 48d4a1423d2e 19 (unreleased) cited
-
Don't reset the pathlist of partitioned joinrels.
- 014f9a831a32 19 (unreleased) cited
-
Treat number of disabled nodes in a path as a separate cost metric.
- e22253467942 18.0 cited