我有一个node.js程序,它在一个事务中调用postgres(amazon rds micro instance)函数,get_jobs
,使用brianc的node-postgres
包每秒18次。
节点代码只是brianc's basic client pooling example的增强版本,大致类似于…
var pg = require('pg');
var conString = "postgres://username:password@server/database";
function getJobs(cb) {
pg.connect(conString, function(err, client, done) {
if (err) return console.error('error fetching client from pool', err);
client.query("BEGIN;");
client.query('select * from get_jobs()', [], function(err, result) {
client.query("COMMIT;");
done(); //call `done()` to release the client back to the pool
if (err) console.error('error running query', err);
cb(err, result);
});
});
}
function poll() {
getJobs(function(jobs) {
// process the jobs
});
setTimeout(poll, 55);
}
poll(); // start polling
所以Postgres得到了:
2016-04-20 12:04:33 UTC:172.31.9.180(38446):XXX@XXX:[5778]:LOG: statement: BEGIN;
2016-04-20 12:04:33 UTC:172.31.9.180(38446):XXX@XXX:[5778]:LOG: execute <unnamed>: select * from get_jobs();
2016-04-20 12:04:33 UTC:172.31.9.180(38446):XXX@XXX:[5778]:LOG: statement: COMMIT;
…每55毫秒重复一次。
get_jobs
是用临时表编写的,如下所示CREATE OR REPLACE FUNCTION get_jobs (
) RETURNS TABLE (
...
) AS
$BODY$
DECLARE
_nowstamp bigint;
BEGIN
-- take the current unix server time in ms
_nowstamp := (select extract(epoch from now()) * 1000)::bigint;
-- 1. get the jobs that are due
CREATE TEMP TABLE jobs ON COMMIT DROP AS
select ...
from really_big_table_1
where job_time < _nowstamp;
-- 2. get other stuff attached to those jobs
CREATE TEMP TABLE jobs_extra ON COMMIT DROP AS
select ...
from really_big_table_2 r
inner join jobs j on r.id = j.some_id
ALTER TABLE jobs_extra ADD PRIMARY KEY (id);
-- 3. return the final result with a join to a third big table
RETURN query (
select je.id, ...
from jobs_extra je
left join really_big_table_3 r on je.id = r.id
group by je.id
);
END
$BODY$ LANGUAGE plpgsql VOLATILE;
我使用the temp table pattern是因为我知道
jobs
总是从really_big_table_1
中提取行,希望这比使用多个连接和多个where条件的单个查询具有更好的伸缩性。(我在使用sql server时使用了这个方法,现在我不信任任何查询优化程序,但请告诉我这是否是postgres的错误方法!)该查询在小表上以8ms的速度运行(从节点测量),在下一个作业开始之前有足够的时间完成一个作业“poll”。
问题:按此速率轮询大约3小时后,Postgres服务器内存不足并崩溃。
我已经试过了…
如果我在没有临时表的情况下重新编写函数,postgres不会耗尽内存,但是我经常使用临时表模式,所以这不是一个解决方案。
如果我停止node程序(这会杀死它用来运行查询的10个连接),内存就会释放出来。仅仅让节点在两个轮询会话之间等待一分钟并不会产生相同的效果,因此与池连接相关联的postgres后端显然保留了一些资源。
如果在进行轮询时运行
VACUUM
,则不会影响内存消耗,服务器将继续走向死亡。降低轮询频率只会更改服务器死亡前的时间量。
每次
DISCARD ALL;
后添加COMMIT;
没有效果。在
DROP TABLE jobs; DROP TABLE jobs_extra;
之后显式调用RETURN query ()
而不是在ON COMMIT DROP
s服务器上调用CREATE TABLE
s。服务器仍会崩溃。根据cfrei的建议,在节点代码中添加了
pg.defaults.poolSize = 0
,试图禁用池。服务器仍然崩溃,但是花费了更长的时间,swap比之前所有看起来像下面第一个峰值的测试都要高得多(第二个峰值)。后来我才发现。may not disable pooling as expected
根据:“autovacuum无法访问临时表。因此,应该通过session sql命令执行适当的真空和分析操作。”,我尝试从节点服务器运行
pg.defaults.poolSize = 0
(有人试图使VACUUM
成为“in session”命令)。我真的不能让这个测试正常工作。我的数据库中有许多对象,VACUUM
在所有对象上操作,执行每个作业迭代花费的时间太长。将VACUUM
限制为临时表是不可能的(a)不能在事务中运行VACUUM
,并且(b)在事务外部不存在临时表。:p编辑:稍后在postgres irc论坛上,一个很有帮助的人解释说,真空与临时表本身无关,但可以用来清理临时表导致的VACUUM
中创建和删除的行。无论如何,抽真空“在会议”不是答案。pg_attributes
在DROP TABLE ... IF EXISTS
之前,而不是CREATE TABLE
。服务器仍然死机。ON COMMIT DROP
和CREATE TEMP TABLE (...)
而不是insert into ... (select...)
,而不是CREATE TEMP TABLE ... AS
。服务器死机。那么
ON COMMIT DROP
是否没有释放所有相关的资源?还有什么能保存记忆?如何释放它? 最佳答案
我在sql server中使用了这个功能,现在我不信任任何查询优化程序
那就别用了。您仍然可以直接执行查询,如下所示。
但请告诉我,如果这是错误的做法,为博士后!
这不是一个完全错误的方法,这只是一个非常尴尬的方法,因为你正在尝试创建一些已经被其他人实现的东西,以便更容易使用。因此,您正在犯许多错误,可能会导致许多问题,包括内存泄漏。
与使用pg-promise的同一示例的简单性相比:
var pgp = require('pg-promise')();
var conString = "postgres://username:password@server/database";
var db = pgp(conString);
function getJobs() {
return db.tx(function (t) {
return t.func('get_jobs');
});
}
function poll() {
getJobs()
.then(function (jobs) {
// process the jobs
})
.catch(function (error) {
// error
});
setTimeout(poll, 55);
}
poll(); // start polling
使用ES6语法时变得更简单:
var pgp = require('pg-promise')();
var conString = "postgres://username:password@server/database";
var db = pgp(conString);
function poll() {
db.tx(t=>t.func('get_jobs'))
.then(jobs=> {
// process the jobs
})
.catch(error=> {
// error
});
setTimeout(poll, 55);
}
poll(); // start polling
在您的示例中,我唯一不太理解的是使用事务执行单个
SELECT
。这不是事务的一般用途,因为您不会更改任何数据。我想你是想缩小一段真正的代码,你有改变一些数据也。如果不需要事务,则可以进一步将代码缩减为:
var pgp = require('pg-promise')();
var conString = "postgres://username:password@server/database";
var db = pgp(conString);
function poll() {
db.func('get_jobs')
.then(jobs=> {
// process the jobs
})
.catch(error=> {
// error
});
setTimeout(poll, 55);
}
poll(); // start polling
更新
然而,不控制前一个请求的结束是一种危险的方法,这也可能造成内存/连接问题。
安全的方法应该是:
function poll() {
db.tx(t=>t.func('get_jobs'))
.then(jobs=> {
// process the jobs
setTimeout(poll, 55);
})
.catch(error=> {
// error
setTimeout(poll, 55);
});
}
关于node.js - Node 使用临时表调用postgres函数会导致“内存泄漏”,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/36739289/