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
RUST-954 Pin cursor connections in load balancer mode #446
RUST-954 Pin cursor connections in load balancer mode #446
Conversation
src/client/executor.rs
Outdated
self.execute_operation_pinned(op, session, None).await | ||
} | ||
|
||
pub(crate) async fn execute_operation_pinned<T: Operation>( |
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 not thrilled with _pinned
(use passed-in connection) vs _pinnable
(return checked-out connection), but this at least avoids the churn of updating all unrelated execute_operation
calls.
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 think a slight improvement would be to move the _pinned
portion to the Operation
trait by adding a new fn pinned_connection(&mut self) -> Option<&mut Connection>
, similar to how we currently handle selection criteria on the operation. Then we can actually eliminate the _pinned
method altogether and use just execute_operation
most of the time. This will work nicely with transactions that have connections pinned to them as well, since we'll probably want the logic for that in the executor somewhere rather than outside of it.
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.
That cleans things up but runs into lifetime issues - with that signature it mutably borrows the Operation
for the span that the returned connection is used, which conflicts with the rest of the execution. Operation
can be parameterized to give the return a &'conn mut Connection
lifetime to disconnect the borrows, but that has substantial ripple effects on the rest of the driver code. Sadly, default lifetimes never made it in, because Operation<'conn = 'static>
would be ideal here.
src/cursor/common.rs
Outdated
|
||
#[derive(Clone, Debug)] | ||
pub(crate) struct PinnedConnection { | ||
connection: Option<Arc<Mutex<Connection>>>, |
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.
It's hypothetically possible that this doesn't need to be an Arc<Mutex<_>>
, but could instead use a similar trick to the Session
associated type in GetMoreProviderResult
. However, couldn't get that to line up well after a day or so of work - particular challenges were the manual polling and state management in the cursor lifecycle, the drop impls for the cursors, and not having lifetimes leak out into the public cursor api - so I went with this solution.
There is a runtime cost incurred by the reference counting and locking, but it's unlikely to be a significant issue in practice because the cursors buffer values returned, so there's not going to be a hot loop involving those operations.
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.
Yeah I agree this is probably fine, and it's simple which is really nice considering the complexity of the existing session stuff. The runtime cost from the locking at least will be insignificant I think since cursors themselves can't be used concurrently.
} | ||
} | ||
|
||
pub(super) fn kill_cursor( |
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 opted to make this a free function rather than a method on PinnedConnection
because it doesn't feel like it belongs to the connection as such.
19075c9
to
5354a2f
Compare
src/client/executor.rs
Outdated
self.execute_operation_pinned(op, session, None).await | ||
} | ||
|
||
pub(crate) async fn execute_operation_pinned<T: Operation>( |
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 think a slight improvement would be to move the _pinned
portion to the Operation
trait by adding a new fn pinned_connection(&mut self) -> Option<&mut Connection>
, similar to how we currently handle selection criteria on the operation. Then we can actually eliminate the _pinned
method altogether and use just execute_operation
most of the time. This will work nicely with transactions that have connections pinned to them as well, since we'll probably want the logic for that in the executor somewhere rather than outside of it.
src/cursor/common.rs
Outdated
|
||
#[derive(Clone, Debug)] | ||
pub(crate) struct PinnedConnection { | ||
connection: Option<Arc<Mutex<Connection>>>, |
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.
Yeah I agree this is probably fine, and it's simple which is really nice considering the complexity of the existing session stuff. The runtime cost from the locking at least will be insignificant I think since cursors themselves can't be used concurrently.
I've updated this to use the discussed pin-by-id setup; the key point this enables is that a connection can be pinned while mutably borrowed rather than just owned, which will be crucial for transactions. As a side benefit, it also enables passing the pin via the This comes at the cost of losing the potential performance improvements of directly owned connections - pinned will now have to incur the same channel round-trip as checked out - and some increased potential contention on the connection pool worker from handling pin takes / unpins. I think the latter is unlikely to be substantial (or even really measurable above normal noise), but if it is it could probably be mitigated by moving the pinned connections to a different structure with its own worker task and channel. |
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.
Looks great! I mostly have comments about naming/bikeshedding and documentation.
src/client/executor.rs
Outdated
}) | ||
.await | ||
} | ||
|
||
/// Execute the given operation, returning the implicit session created for it if one was. | ||
/// Execute the given operation, returning cursor created by the operation. |
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.
/// Execute the given operation, returning cursor created by the operation. | |
/// Execute the given operation, returning the cursor created by the operation. |
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.
Applied.
src/cmap/manager.rs
Outdated
Ok(()) | ||
} | ||
|
||
pub(super) async fn take_pinned(&self, id: u32) -> Result<Connection> { |
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.
(For my own understanding) what is the difference between taking a pinned connection and unpinning?
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.
Pinning a connection removes it from the normal connection pool and makes it available to retrieve by id (wrapped by PinnedConnectionHandle
). Taking a pinned connection is that retrieval; it enforces ownership mechanics at runtime by only allowing a connection to be taken by id if that connection had previously been pinned and dropped. Unpinning returns the connection to the normal connection pool, either immediately (if nothing's using it) or when it gets dropped.
Hopefully the docstrings added will help make this clearer :)
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.
LGTM, great job with this! it was a really tricky one
53eab21
to
429a396
Compare
RUST-954
This implements the cursor portion of connection pinning; transactions and checkout type tracking will come in future PRs.