node.js - Node 调用带有临时表的 postgres 函数导致 "memory leak"

标签 node.js postgresql node-postgres

我有一个 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 服务器耗尽内存并崩溃。

我已经尝试过的...
  • 如果我在没有临时表的情况下重写函数,Postgres 不会耗尽内存,但我经常使用临时表模式,所以这不是解决方案。
  • 如果我停止 Node 程序(它会杀死它用来运行查询的 10 个连接),内存就会释放。仅仅让 Node 在轮询 session 之间等待一分钟不会产生相同的效果,因此显然与池连接关联的 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到 Node 代码以尝试禁用池。服务器仍然崩溃,但用了更长的时间,并且交换比所有以前的测试(看起来像下面的第一个峰值)高得多(第二个峰值)。后来才知道pg.defaults.poolSize = 0 may not disable pooling as expected .

  • Swap memory usage on Postgres server
  • this的基础上: “临时表不能被 autovacuum 访问。因此,应该通过 session SQL 命令执行适当的真空和分析操作。”,我试图运行一个 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/

    相关文章:

    node.js - process.env.SOME_KEY 未定义

    javascript - 如何在nodejs中先实现mysql查询? (或者先实现条件查询)

    json - 如何在sqlalchemy中从postgresql查询json数据类型的数据?

    sql - 在 SELECT 列表中两次使用 jsonb_array_elements_text() 的结果不一致

    node.js - 如何在 node-postgres 的客户端或池之间进行选择

    Javascript node.js get不会给出json请求代码

    javascript - babel-node 不被识别为内部或外部命令 - Babel 7

    r - 使用 RPostgresql 打印查询时间

    node.js - PG(Node-Postgres)池在 Connect 上挂起……但仅在 Gatsby 内部?