-
Notifications
You must be signed in to change notification settings - Fork 435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support of prepared statements #845
Conversation
@knizhnik I think you are missing the header file messages.h for the messages.c file. |
Sorry and thank you - fixed. |
The file "common/uthash.h" also seems to be missing in the PR @knizhnik. It is referenced in the file "include/ps.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for poking #757 again and creating this follow up one with a few fixes. That made me finally take the time to look closer at this solutinon. Because of the large amount of changes I hadn't taken such a closer look before.
I really quite like the overall design of this solution. Thank you @dashorst!
@knizhnik I do agree with the comments you left on #757
I have not tested the PR yet, partially because it's missing common/uthash.h
as @asdm90 already mentioned.
I did leave a bunch of comments though (some large, some small). Once you add common/uthash.h
and add a at least 1 test, I'll give it a spin and try to add some more tests while doing so.
src/client.c
Outdated
if (client->packet_cb_state.flag == CB_HANDLE_COMPLETE_PACKET) { | ||
/* pkt already read from sbuf with cb */ | ||
client->packet_cb_state.flag = CB_NONE; | ||
} else { | ||
sbuf_prepare_skip(sbuf, pkt->len); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really understand what this is supposed to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lines 1071-1076 are new to me, reading from sbuf should already be handled at this point, so not sure either.
About lines 1064 and the switch block on line 1065:
The original code of handle_client_work
begins with a switch (pkt->type)
to handle different packet types from the client. At this point there is not yet a server connection bound to the client connection. Inside this first switch block some initial processing/checking is done with regards to prepared statement support.
After this first switch block a server connection is acquired to forward the client packet to the server.
Line 1064 and the switch block starting on line 1065 are basically doing the actual packet rewriting to transform the packet received from the client to match the specific server connection acquired (e.g. client -> bind pkt for named ps S_1 -> pgbouncer -> sent bind pkt for named ps B_7 to server).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear I specifically meant 1071-1076. The part above made sense and it took me a bit to figure out why the code was split in two pieces exactly, but in the end it made sense. Your explanation is nice though and would have helped me understand it easier. Let's add something similar as a comment.
Sidenote regarding |
I believe the PR by @dashorst was crafted from this patch repo. Which has had some updates since then. Fix: Feat: |
Yes. This PR was crafted on the request of the pgbouncer team to make it easier to see the changes. So I cloned the pgbouncer repo and applied the patch in the patch repo, and crafted this PR. |
This sounds like 2 useful changes. |
I tried creating a basic test. Just to play with this a bit: import psycopg
import pytest
def test_prepared_statement(bouncer):
bouncer.admin(f"set pool_mode=transaction")
prepared_query = "SELECT 1"
with bouncer.cur() as cur1:
with bouncer.cur() as cur2:
cur1.execute(prepared_query, prepare=True)
cur1.execute(prepared_query)
with cur2.connection.transaction():
cur2.execute("SELECT 2")
cur1.execute(prepared_query) But there's an issue in the response we return (probably what we return to the Parse). Because the
|
Missing check (server.c, Running your test with the ps code enabled works, at least with my current 'patch', which is not the code in this pr. Changes to
|
Thank you for creating test - there was really my bug in handle_bind_command. Looks like in prepared statement name generated by pgbench has the same length as one generated by pgbouncer. This is why pgbench with -M prepared worked. Fixed. By the way, pgbouncer python tests are not working with the latest version of pytest:
Any plans to fix it? |
The |
Have you considered the implications of using LRU vs LFU when replacing the eviction policy? The eviction policy was very crude and might just work together with a certain workload and tuned pgbouncer and application settings. But LRU might not work for everyone... A good read on cache admission and eviction policies is this paper. With regard to LRU it states:
Increasing the server side cache size also places a burden on the postgresql server itself, not only on the pgbouncer process. Hence, we are a bit reluctant to run pgbouncer with huge server side prepared statement caches per server connection. All though it is hard to measure the exact effects as so many variables are involved. One option would be to make the eviction policy configurable, so people can test what works best for their workload. The other option, which is more work, would be to implement e.g. a (minimal) W-TinyLFU policy and not burden users with figuring out what their workload needs. Or at least implement some sort of doorkeeper to minimise the risk of high frequency items being flushed out of the cache by bursts of less frequent items. Keep in mind that e.g. the pgjdbc driver also uses prepared statements to optimise a batch, and after the batch completes the prepared statement is never reused. |
Yes you are right I forgot to enable the prepared statement support in the test. |
Sorry, but it still doesn't help.
|
@knizhnik it seems that error is coming from pytest-benchmark. Which isn't needed for pgbouncer tests and is apparently installed globally for you (based on the path in the stacktrace). Some references to this issue:
You'll probably want to use a virtualenv to isolate the python environment for running pgbouncer tests from your system setup:
|
I am quite sure that it is some problem with my python installation. |
Both LRU and LFU have their pros and cons. Usually clock is prefered to LRU because it doesn't require global lock. I think that for prepared statement LRU is really better choice. |
src/prepared_statement.c
Outdated
} | ||
else if (HASH_COUNT(server->server_prepared_statements) != 1) /* do nothing if there is just one prepared statement */ | ||
{ | ||
/* Maintain LRU double-linke list: move element to the end of the list */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll create a thread here to keep the cache eviction mechanism discussion focussed in one place.
@sboschman Regarding the cache algorithm, I agree there's many trade-offs here and supporting different eviction mechanisms would be nice. But I believe for the initial version of this prepared statement feature LRU is definitely good enough. The main case where LRU caches are problematic are when the full size of the cache is scanned, thus completely evicting the current set. This is especially problematic if this happens often.
I think it's pretty unlikely for that to happen for a decently sized (>=100 items) prepared statement cache.
I agree with @knizhnik that LRU is at least better than least-often-used one, because in practice that will never evict existing items.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear. I think it would be beneficial to support multiple cache evection algorithms, but I couldn't quickly find a C implementation of W-TinyLFU. Having an implementation of that in PgBouncer/libusual might be desirable if LRU really has some problems for people in real world scenarios. But requiring that for this initial PR seems unnecessary and will only make this PR harder to review.
@dashorst First of all thank you for your work - I started implementation of prepared statements for pgbouncer myself, found ti to be more complicated than I originally estimated and then found your repository. So there is now your repo: https://github.com/topicusonderwijs/pgbouncer-ps-patch, I think it will be nice if we can somehow coordinate our efforts to add prepared statements support to pgbouncer. |
Currently tests in CI are all failing for a obvious reason. Could you fix that one, to see if it the current tests still pass with these changes. The current error is:
|
Sorry, fixed |
Use the same pattern as prepare.c.
Profiling showed that this was causing CPU and memory overhead when using large bind arguments were used.
This reverts commit b396682.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm planning to merge and release this tomorrow morning. @petere If you want to take a final look, please do so before then.
Fix a few typos. Rewrite some sentences. Replace the future tense with the present tense in a few sentences. Remove a reference to an in-progress patch for PostgreSQL that will avoid an issue with this feature. We should add a reference to it when such feature is merged in Postgres.
Congratulations on landing the PR! Looking forward to using it in our systems! |
This is big – thanks everyone involved (@knizhnik particularly) |
Yes, thanks everyone who helped getting this PR in. Not just the people that wrote code, but also the ones that did reviews and tried it out with various clients. I released v1.21.0 today, which includes this feature! 🎉 |
In #845 full tracking of expected responses was implemented. This turned out to have bugs when sending `COPY FROM STDIN` queries using the extended protocol. [The protocol explicitly allows sending `Sync` messages after sending an `Execute` message for a `COPY FROM STDIN` command][1]. Such Sync messages are ignored by the server until a CopyDone or CopyFail message is received. The new command queue tracking did not take this into account, and was incorrectly expecting more ReadyForQuery messages from the server. This could lead to two problems for the server connection on which the `COPY FROM STDIN` was sent: 1. The server connection would never be unassigned from the client. 2. Memory usage for this server connection would continue to increase until the client disconnects. 3. If `max_prepared_statements` was non-zero, then clients might receive ParseComplete and CloseComplete responses in the wrong order. This wouldn't result in wrong behaviour by the server, but the client might get confused and close the connection. This PR fixes these issues. It also removes the rfq_count tracking, since it's now only keeping track of a strict subset of what the outstanding request queue was tracking. [1]: https://www.postgresql.org/docs/current/protocol-flow.html#PROTOCOL-COPY Fixes #1023 Fixes #1024
This PR is actually successor of #757 which seems to be stuck.
It contains the following fixes/improvements:
Co-Author: @dashorst