我有一个 node.js 程序调用 Postgres(Amazon RDS 微型实例)函数,get_jobs
在一个事务中,每秒 18 次使用 node-postgres
由 brinc 包装。
Node 代码只是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 的错误方法!)查询在小表上运行 8 毫秒(从 Node 开始测量),有足够的时间在下一个作业开始之前完成一项作业“轮询”。
问题:以这种速度轮询大约 3 小时后,Postgres 服务器耗尽内存并崩溃。
我已经尝试过的...
VACUUM
在轮询进行时,它对内存消耗没有影响,服务器继续走向死亡。 DISCARD ALL;
每个之后 COMMIT;
没有效果。 DROP TABLE jobs; DROP TABLE jobs_extra;
后 RETURN query ()
而不是 ON COMMIT DROP
s 上 CREATE TABLE
s。服务器仍然崩溃。 pg.defaults.poolSize = 0
到 Node 代码以尝试禁用池。服务器仍然崩溃,但用了更长的时间,并且交换比所有以前的测试(看起来像下面的第一个峰值)高得多(第二个峰值)。后来才知道pg.defaults.poolSize = 0
may not disable pooling as expected . VACUUM
来自 Node 服务器(因为有人试图使 VACUUM
成为“ session 中”命令)。我实际上无法让这个测试工作。我的数据库中有很多对象和 VACUUM
,对所有对象进行操作,执行每个作业迭代花费的时间太长。限制VACUUM
只是到临时表是不可能的 - (a) 你不能运行 VACUUM
在事务中和 (b) 在事务之外,临时表不存在。 :P 编辑:后来在 Postgres IRC 论坛上,一个有用的人解释说 VACUUM 与临时表本身无关,但对于清理从 pg_attributes
创建和删除的行很有用那是 TEMP TABLES 导致的。无论如何,“ session 中”的 VACUUMing 不是答案。 DROP TABLE ... IF EXISTS
之前CREATE TABLE
, 而不是 ON COMMIT DROP
.服务器仍然死亡。 CREATE TEMP TABLE (...)
和 insert into ... (select...)
而不是 CREATE TEMP TABLE ... AS
, 而不是 ON COMMIT DROP
.服务器死了。 ON COMMIT DROP
也是如此不释放所有相关资源?还有什么可以保持内存?我该如何释放它?
最佳答案
I used this to great effect with SQL Server and I don't trust any query optimiser now
然后不要使用它们。您仍然可以直接执行查询,如下所示。
but please tell me if this is the wrong approach for Postgres!
这并不是一种完全错误的方法,它只是一种非常尴尬的方法,因为您正在尝试创建一些其他人已经实现的东西,以便更容易使用。结果,您犯了许多错误,这些错误可能导致许多问题,包括内存泄漏。
与使用 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 函数导致 "memory leak",我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36739289/