-
Notifications
You must be signed in to change notification settings - Fork 185
Limit how many threads can prepare a statement #422
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
base: main
Are you sure you want to change the base?
Conversation
|
Great addition to the library! <3 I'm currently busy with another PR and will merge this once the workspace removal is complete: |
|
Cool, thank you for taking a look. Please let me know if there's anything I can do to improve this change or make it easier to merge, such as rebasing after #429 merges. |
7a9da3d to
f713c85
Compare
bikeshedder
left a comment
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.
The PR needs to be rebased on the latest main branch. Apart from that and a small change request (Either is too generic for my taste) it's a very cool addition.
Adding tests to test for possible race conditions would be the cherry on top.
`StatementCache` aims to prevent redundant SQL statement preparation with a simple cache. However in cases where many, many writers are racing to prepare a statement, lots of redundant and expensive work can be done because `tokio_postgres::client::Client::prepare_typed` is called before `StatementCache::insert`, and only the latter takes the lock on the cache. This commit allows entries in the `StatementCache` map to be either empty, a Semaphore, or a prepared Statement. The first call to `prepare_typed()` will insert a `Semaphore`. Then, tasks will wait for a semaphore permit before preparing the query, to ensure that only one task sends a `PREPARE` statement at once. At both steps in the process, the map will be read via a read lock first, then again via a write lock, before updating the entry.
965d66f to
c9699fe
Compare
|
I rebased and addressed your comment in I'm also unsure what to do about the MSRV and "Check re-exported features" check failures. I don't believe my PR changes anything about the Rust version in use or crate features, so perhaps these checks are also failing on |
I noticed that the existing test suite assumes a Postgres database is available, and discovers connection parameters through environment variables. One straightforward approach would be to start many tasks, synchronize them with Alternately, a heavyweight approach would be to write a mock Postgres server that speaks the backend's side of the wire protocol, build a pool that connects to it, and try to prepare a statement twice. We could delay the response to the first "Parse" command, and confirm no second "Parse" command arrives on another connection before some timeout. This would take more effort to write the infrastructure for (note that |
tbh. I don't really have a great idea for that either. The project comes with a Dev Container with a ready-to-use PostgreSQL database. That's quite similar to how the CI runs the checks. I do think however that it's quite difficult to design a test for this with an actual PostgreSQL database being used. I was wondering if the logic behind this statement cache optimization could be extracted into a testable synchronization primitive. This primitive could be tested without requiring a running PostgreSQL at all.
You can ignore those. This always happens when the I will add the missing features in another PR and fix the MSRV check. The checks were not broken by your changes. |
|
The only place where the statement cache actually interacts with PostgreSQL are the |
|
I will take a swing at the testable abstraction you describe. It might take me a little while to come back around to this PR, though. |
StatementCacheaims to prevent redundant SQL statement preparation with a simple cache. However in cases where many, many writers are racing to prepare a statement, lots of redundant and expensive work can be done becausetokio_postgres::client::Client::prepare_typedis called beforeStatementCache::insert, and only the latter takes the lock on the cache.This commit allows entries in the
StatementCachemap to be either empty, a Semaphore, or a prepared Statement. The first call toprepare_typed()will insert aSemaphore. Then, tasks will wait for a semaphore permit before preparing the query, to ensure that only one task sends aPREPAREstatement at once. At both steps in the process, the map will be read via a read lock first, then again via a write lock, before updating the entry.