The connection pool sucks
See original GitHub issueHello -
I think the client object interface is pretty good. What is not a good interface is the current incarnation of the pool. Namely pg.connect
. Here are some reasons why:
First of all, as a quick aside. The pooling was added to the module before the there was a lot of wisdom around “tiny modules that just do one thing.” Sooo really it would be better to have the pool in a completely separate module. I would like, probably, to start developing the pool off in it’s own module and maybe eventually rip the built in stuff out…but I don’t like breaking APIs. Anyways…
I think a good API makes the right thing easy and the wrong thing hard. Currently the pool is just about opposite of that.
Here are some examples of the pool sucking:
dead clients
Because the pool waits for a client’s query queue to drain, and once the client query queue is drained the client is automatically returned, if you never send a query to the client, the client is never returned. Eventually your pool is all checked out and idle and your app hangs. Here is an example of this total bullshit.
app.get('/', function(req, res, next) {
pg.connect(app.constring, function(err, client) {
if(!req.session) {
return next();
//way to go partner. you just leaked a connection
}
client.query('SELECT * FROM customers WHERE id = $1', [req.session.id], function(err, result) {
var html = app.render('home', {user: JSON.stringify(result.rows[0])});
res.send(html);
})
});
});
transactions
On the flip side of dead connections, greedy pool release mechanics of ‘the first drain event’ cause the client to get pushed back into the pool of available clients way too early.
pg.connect(/*constring*/, function(err, con) {
con.query('begin', function(err) { //start our transaction
con.query('update bla', function(err) {
setTimeout(function() {
//way to go partner, you returned this connection to the pool with an open transaction
//better hope no one else wants to run a query for 2 seconds or some weird stuff is gonna happen
con.query('commit');
}, 2000);
});
});
});
That right there is an example of the ‘auto release’ being too greedy and not greedy enough.
I am going to fix this
I’d like to open up discussion around a different API for the pool. After 24/48 hours I’ll get started on a new implementation. So here’s what I’m thinking…this would be backwards compatible by adding a 3rd parameter to the pg.connect
function. A callback you call when you want to release the client back to the pool. The callback itself will take a single error
argument in the style of node. If the argument is non-null, the client will be closed and removed from the pool instead of returned to the pool. This is how it would look:
pg.connect(/*constring*/, function(err, client, done) {
//hmmm....turns out I actually don't need this client
done();
});
or what about doing a transaction? currently you have to do some nasy stuff with pauseDrain()
and resumeDrain()
which are themselves somewhat cludgey hacks I put on because I was mentally exhausted and planning a home remodel + my wedding. Well, I’m back now, refreshed, and want to make things better and all of our lives easier…
pg.connect(/*constring*/, function(err, client, done) {
client.query('begin', function(err) {
if(err) return callback();
client.query('update something', function(err) {
client.query('commit', function() { done() });
})
});
});
Also, the 1st parameter (connection info) is optional. I think node-postgres should use the same environment variables as the psql
command to get default connection info, and then supplement with pg.defaults
and then override with custom stuff passed in. So if you use defaults or the environment variables, you can do this:
pg.connect(function(client, done() {
done();
});
The pool needs a cleaner way to shut itself down too without being destructive. I’m not sure how that should work currently.
feedback
I needs your feedback on this. Let’s hear it!
Issue Analytics
- State:
- Created 11 years ago
- Reactions:1
- Comments:37 (16 by maintainers)
Top GitHub Comments
@vanuan use any-db, and ConnectionPool.query. Like so:
This will never use more than 10 simultaneous connections, and you don’t have to worry about freeing them.
This is the correct approach 99% of the time, the only exception is if you need to perform multiple queries in a single transaction, in which case you need to hang on to a single connection for the duration of the transaction, which is what ConnectionPool.begin and Transaction objects are for.
There’s more detailed API descriptions for all of the any-db interfaces (they’re the same as what
pq
exposes) at the above links. If you run into stuff you don’t understand open issues requesting clarification on any-db..