我有一个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 DROPs服务器上调用CREATE TABLEs。服务器仍会崩溃。
根据cfrei的建议,在节点代码中添加了pg.defaults.poolSize = 0,试图禁用池。服务器仍然崩溃,但是花费了更长的时间,swap比之前所有看起来像下面第一个峰值的测试都要高得多(第二个峰值)。后来我才发现。
may not disable pooling as expected
根据node.js -  Node 使用临时表调用postgres函数会导致“内存泄漏”-LMLPHP:“autovacuum无法访问临时表。因此,应该通过session sql命令执行适当的真空和分析操作。”,我尝试从节点服务器运行pg.defaults.poolSize = 0(有人试图使VACUUM成为“in session”命令)。我真的不能让这个测试正常工作。我的数据库中有许多对象,VACUUM在所有对象上操作,执行每个作业迭代花费的时间太长。将VACUUM限制为临时表是不可能的(a)不能在事务中运行VACUUM,并且(b)在事务外部不存在临时表。:p编辑:稍后在postgres irc论坛上,一个很有帮助的人解释说,真空与临时表本身无关,但可以用来清理临时表导致的VACUUM中创建和删除的行。无论如何,抽真空“在会议”不是答案。
pg_attributesDROP TABLE ... IF EXISTS之前,而不是CREATE TABLE。服务器仍然死机。
ON COMMIT DROPCREATE 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/

10-09 20:42
查看更多