-
Notifications
You must be signed in to change notification settings - Fork 2
/
DBConnection.js
178 lines (167 loc) · 5.62 KB
/
DBConnection.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/*
* Copyright 2015 Shyp, Inc.
*
* Database connection interface modeled heavily on Go's database/sql library.
*
* If you need to make a query, call:
*
* DBConnection.get (err, conn) ->
* conn.query 'SELECT foo FROM bar', (err, result) ->
* console.log result.rows[0]
* conn.release()
*
* You'll need to release the connection yourself. It's not recommended, but
* it's safe to call `conn.release()` twice.
*
* If you need a database transaction, call `DBConnection.begin`:
*
* DBConnection.begin (err, txn) ->
* txn.query 'UPDATE ... ', (err, result) ->
* txn.rollback() # or txn.commit()
*
* The DBTransaction object that's returned has the same interface, but instead
* of releasing the connection, call `rollback` or `commit`, which will release
* the connection for you.
*
* You should NOT continue to use the connection after calling `release`,
* `commit`, or `rollback`.
*
* This library is callback-based because the underlying library relies heavily
* on it. If you need a Promise based interface, wrap the calling functions in
* Promise.promisify calls.
*/
var DBConnection;
var pg = require('pg');
var DBTransaction = require('./DBTransaction.js');
var Metrics = require('./Metrics.js');
var DEFAULT_CONNECTION_POOL_SIZE = 100;
module.exports = DBConnection = function(client, releaseFn) {
this.client = client;
this.releaseFn = releaseFn;
this._released = false;
};
/*
* Get a new database connection. Will hit the callback with (err,
* DBConnection). Extensive documentation for the parameters can be found
* here: https://github.com/brianc/node-postgres/wiki/pg#method-connect
*
* The caller is responsible for releasing the connection by calling
* release(true) when they are finished with the query. Otherwise it will stay
* open for up to 30 seconds.
*
* NB: This will block if all connections in the pool are checked out! If
* latency is important, consider setting a timeout and canceling the query.
* More discussion here: https://github.com/brianc/node-postgres/issues/805
*/
DBConnection.get = function(config, cb) {
DBConnection._setPoolSize();
var start = Date.now();
pg.connect(config, function(err, client, release) {
if (err !== null && typeof err !== "undefined") {
return cb(err);
}
try {
var poolUtilization = DBConnection._getPoolUtilization();
Metrics.measure('db.txn_conn_pool.count', poolUtilization);
} catch (e) {}
Metrics.measure('db.txn_conn_pool.total', pg.defaults.poolSize);
Metrics.increment('db.txn_conn_pool.get');
Metrics.timing('db.txn_conn_pool.get.latency', start);
cb(err, new DBConnection(client, release));
});
};
/*
* Begin gets a new connection and begins a transaction. Hits the callback
* with an error or a DBTransaction object.
*
* The caller is responsible for calling commit() or rollback() to complete
* the transaction in every code path; open transactions crush database
* performance. If Postgres returns a syntax error, the client loses the
* connection, or there's a connection timeout, the connection should
* automatically be released.
*/
DBConnection.begin = function(config, cb) {
return DBConnection.get(config, function(err, conn) {
if (err) {
return cb(err);
}
return conn.query('BEGIN', function(err) {
if (err) {
Metrics.increment('db.txn.begin.error');
return cb(err);
}
Metrics.increment('db.txn.begin.success');
return cb(null, new DBTransaction(conn));
});
});
};
/*
* Query makes the given query to the database. If the query fails with a
* connection error or Postgres error, this will release the connection before
* hitting the callback with an error.
*
* Query accepts two different signatures:
*
* (sql, cb): A SQL command to execute and a callback to hit
* (sql, values, cb): A SQL command to execute, an array of values to
* interpolate into the query (parameters), and a callback to hit.
*/
DBConnection.prototype.query = function(sql, values, cb) {
if (cb === null || typeof cb === "undefined") {
cb = values;
values = [];
}
var that = this;
this.client.query(sql, values, function(err, res) {
if (err) {
that.release(true);
return cb(err);
}
return cb(null, res);
});
};
/*
* Release this connection back to the pool. `dispose=true` will destroy
* the underlying connection object - it's the safest mode, in case PG is
* still trying to send back data, but may result in unnecessary connection
* teardowns/latency.
*/
DBConnection.prototype.release = function(dispose) {
var poolUtilization;
if (this._released) {
return;
}
this.releaseFn(dispose);
this._released = true;
poolUtilization = DBConnection._getPoolUtilization();
return Metrics.measure('db.txn_conn_pool.release.count', poolUtilization);
};
DBConnection._setPoolSize = function() {
return pg.defaults.poolSize = DBConnection._getPoolSize();
};
DBConnection._getPoolSize = function() {
var defaultPoolSize;
defaultPoolSize = parseInt(process.env.PGBOUNCER_MAX_CLIENT_CONN, 10);
if (defaultPoolSize > 0) {
return defaultPoolSize;
} else {
return DEFAULT_CONNECTION_POOL_SIZE;
}
};
/*
* getPoolUtilization synchronously returns the current number of open
* connections as an integer, or throws an error if that number can't be
* determined.
*/
DBConnection._getPoolUtilization = function() {
var key, pool;
key = Object.keys(pg.pools.all)[0];
if (key != null) {
pool = pg.pools.all[key];
if (pool != null) {
size = pool.getPoolSize();
return size;
}
}
throw new Error("DBConnection: Couldn't get pool size. Make sure at least one connection's been made");
};