Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upr2d2 for tokio-postgres #233
Comments
This comment has been minimized.
This comment has been minimized.
|
I have spent some time thinking about asynchronous pooling, and it's a bit more challenging implementation-wise than synchronous pooling. In particular, async driver methods take fn get<F, U, T, E>(&self, f: F) -> BoxFuture<T, E>
where F: FnOnce(Connection) -> U,
U: Future<Item = (T, Connection), Error = (E, Option<Connection>)>,
E: From<GetError> |
This comment has been minimized.
This comment has been minimized.
theduke
commented
Feb 15, 2017
|
Yeah that is along the lines that I imagined. Though I would probably call in |
This comment has been minimized.
This comment has been minimized.
|
Apparently Hyper's tokio branch has support for keep-alive (at least on the client-side) which may be a good model for tokio-postgres--both the API paradigm and the implementation. For example, creating a client in hyper is done with something like Abstracting away connection management seems like a perfectly natural thing to do for a tokio-based postgres client, and it would certainly solve the issues with connection ownership. |
This comment has been minimized.
This comment has been minimized.
|
Things are a bit more complicated here since a database connection is inherently stateful. You often need to run a sequence of commands on specifically the same connection. |
This comment has been minimized.
This comment has been minimized.
theduke
commented
Feb 15, 2017
•
|
Thinking about it a bit more, and along the thinking of @jwilm, it might be nice to provide an abstraction that abstracts the need for a connection handle completely, with a tokio_service implementation and some convenience functions on top. The #[derive(Clone)]
struct Pool { ... }
enum Request {
Execute { query: String, params: Vec<Box<ToSql>> },
BatchExecute { query: String },
Query { query: String, params: Vec<Box<ToSql>> },
Chained ( Vec<Request> ),
}
enum Response {
Ok,
RowsAffected (u64),
Rows( Rows ),
Chained ( Vec<Result<Response, Error>> ),
}
impl Service for Pool {
type Request = Request;
type Response = Response;
type Error = PoolError;
type Future = BoxFuture<Self::Response, Self::Error>;
fn call ...
}
impl Pool {
fn execute<S: Into<String>>(&self, stmt: S, params: &[&ToSql]) -> BoxFuture<u64, Error> {
self.call(Request::Execute(...))
}
fn chain(&self) -> ChainBuilder {
ChainBuilder { pool: self.clone(), requests: Vec::new() }
}
}
struct ChainBuilder {
pool: Pool,
requests: Vec<Request>,
}
impl ChainBuilder {
fn and_execute<S: Into<String>>(self, stmt: S, params: &[&ToSql]) -> Self {
self.requests.push(Request::Execute(...));
self
}
...
fn run(self) -> BoxFuture<Vec<Result<Response, Error>>, Error> { ... }
} |
This comment has been minimized.
This comment has been minimized.
|
@sfackler understood, but I don't think it changes what an ideal API looks like. Projects like PgBouncer have managed to address such complexity of connection pooling. In the case of futures/tokio, I imagine that each transaction or series of statements would be wrapped up into a single future. Each connection could be a task which will drive 1 future to completion. |
This comment has been minimized.
This comment has been minimized.
theduke
commented
Feb 15, 2017
|
It probably makes sense to have a That would make chaining easy, and the connection can be released back to the pool either in I'll start hacking on an implementation and do a PR for feedback. |
This comment has been minimized.
This comment has been minimized.
|
PgBouncer is a pretty different story from my understanding - you're maintaining a persistent connection to the bouncer, which is bound to one or more Postgres connections in a configurable way: https://pgbouncer.github.io/features.html Keep in mind that buffering up a series of commands is not sufficient - you need to be able to run a query and then run some code that inspects the result of that before deciding what to do next. |
This comment has been minimized.
This comment has been minimized.
theduke
commented
Feb 16, 2017
|
You could always fall back on manually forwading the connection if you need that though. I think a majority of queries won't actually need this. |
This comment has been minimized.
This comment has been minimized.
|
@sfackler PgBouncer's transaction pooling mode seems analogous to what's needed here. PgBouncer will share one database connection between multiple client connections for the duration of a transaction in this mode.PgBouncer has the added challenge that it actually needs to parse some of the query data to detect transactions.
I think this is addressed with a transaction pooling approach; the connection is not returned to the pool until the Future that started a transaction either commits or rolls it back. A series of statements outside of a transaction wouldn't be guaranteed to execute on the same connection. I don't see any problems with that, but maybe you've got something in mind. |
This comment has been minimized.
This comment has been minimized.
|
I am pretty wildly opposed to anything that involves trying to parse query strings. Another reason that I want to have a separate pooling library is that there's a bunch of logic that goes into that that can be shared among all of the various database backends. |
This comment has been minimized.
This comment has been minimized.
|
Hmm, I didn't mean to suggest you actually parse query strings. Being the client library, you have a priori knowledge of when you'd be in a transaction or not. Having a separate pooling library seems fine, but as a user, I don't really want to be managing my own connections. |
This comment has been minimized.
This comment has been minimized.
|
@sfackler It's not done yet, but was interested in any comments you might have to give on my attempt to clone r2d2 into an asynchronous style. It's at maurer/r5d4. Notable questions I have before I try to document/robustify it:
|
This comment has been minimized.
This comment has been minimized.
|
Nice! I will take a look when I have some time, which unfortunately is probably not until tomorrow. |
This comment has been minimized.
This comment has been minimized.
rrichardson
commented
Dec 11, 2017
|
Is this still being worked-on? Is there a new strategy/direction that obviates this effort? |
This comment has been minimized.
This comment has been minimized.
|
I am not working on it - the work I was doing that wanted it no longer uses postgres. The repo is still there as a starting point if anyone wants to complete it. |
This comment has been minimized.
This comment has been minimized.
|
I wrote a crate that's basically r2d2 but async, with a tokio_postgres adapter. |
This comment has been minimized.
This comment has been minimized.
nicoburns
commented
Jul 29, 2018
|
Adding this to the list of async pool implementations https://github.com/tailhook/tk-pool |
This comment has been minimized.
This comment has been minimized.
tyranron
commented
Jan 22, 2019
|
Again, to the list of async pools.
|
theduke commentedFeb 14, 2017
•
edited
Since Postgres does not support multiplexing / concurrent queries on a single connection, do you have ideas about how to handle multiple connections?
Would r2d2 be a good fit? I don't know how r2d2 manages the connection internally. Does it use an extra thread or just does maintainance whenever a new connection is acquired?
Does r2d2 fit into this scenario?
Would it make sense to come up with a generic pool manager that is designed to be run on an event loop?
Other thougthts?