3

我有一个 node.js 程序调用 Postgres(Amazon RDS 微实例)函数,get_jobs在事务中,每秒 18 次使用node-postgresbrianc 的包。

节点代码只是briinc 的基本客户端池示例的增强版,大致类似于...

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;

我使用了临时表模式,因为我知道这jobs将始终是从 中提取的一小部分行really_big_table_1,希望这将比具有多个连接和多个 where 条件的单个查询更好地扩展。(我在 SQL Server 上使用这个效果很好,我现在不信任任何查询优化器,但请告诉我这是否是 Postgres 的错误方法!)

查询在小表上运行 8 毫秒(从节点测量),有足够的时间在下一个开始之前完成一个作业“轮询”。

问题:以这种速度进行大约 3 小时的轮询后,Postgres 服务器内存不足并崩溃。

我已经尝试过的...

  • 如果我在没有临时表的情况下重新编写函数,Postgres 不会耗尽内存,但我经常使用临时表模式,所以这不是解决方案。

  • 如果我停止节点程序(它会终止用于运行查询的 10 个连接),内存就会释放。仅仅让节点在轮询会话之间等待一分钟不会产生相同的效果,因此显然 Postgres 后端与池连接相关联的资源正在保留。

  • 如果我运行一段VACUUM时间轮询正在进行,它对内存消耗没有影响,并且服务器继续死亡。

  • 降低轮询频率只会改变服务器死机之前的时间量。

  • DISCARD ALL;在每个之后添加COMMIT;都没有效果。

  • 在s 上显式调用DROP TABLE jobs; DROP TABLE jobs_extra;afterRETURN query ()而不是s。服务器仍然崩溃。ON COMMIT DROPCREATE TABLE

  • 根据 CFrei 的建议,添加pg.defaults.poolSize = 0到节点代码中以尝试禁用池。服务器仍然崩溃,但与之前所有看起来像下面的第一个峰值的测试相比,花费的时间和交换时间要长得多(第二个峰值)。后来我发现pg.defaults.poolSize = 0 可能无法按预期禁用池

在 Postgres 服务器上交换内存使用情况

  • 基于:“临时表无法通过 autovacuum 访问。因此,应通过会话 SQL 命令执行适当的清理和分析操作。”,我尝试从VACUUM节点服务器运行VACUUM会话”命令)。我实际上无法让这个测试工作。我的数据库中有许多对象,并且VACUUM对所有对象进行操作时,执行每个作业迭代所花费的时间太长。只限于VACUUM临时表是不可能的 - (a) 你不能运行VACUUM在事务中和(b)在事务之外,临时表不存在。:P 编辑:稍后在 Postgres IRC 论坛上,一个有用的小伙子解释说 VACUUM 与临时表本身无关,但对于清理从pg_attributes该 TEMP TABLES 原因创建和删除的行很有用。无论如何,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不是释放所有相关资源吗?还有什么可以保存记忆?我该如何释放它?

4

2 回答 2

0

我在 SQL Server 上使用它效果很好,现在我不信任任何查询优化器

然后不要使用它们。您仍然可以直接执行查询,如下所示。

但请告诉我这是否是 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);
        });
}
于 2016-04-20T16:22:04.623 回答
0

使用 CTE 创建部分结果集而不是临时表。

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;  

  RETURN query (

    --  1. get the jobs that are due
    WITH jobs AS (

      select ...
      from really_big_table_1 
      where job_time < _nowstamp;

    --  2. get other stuff attached to those jobs
    ), jobs_extra AS (

      select ...
      from really_big_table_2 r
        inner join jobs j on r.id = j.some_id

    ) 

    -- 3. return the final result with a join to a third big table
    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;

规划器将按照我希望使用临时表实现的方式依次评估每个块。

我知道这并不能直接解决内存泄漏问题(我很确定 Postgres 的实现有问题,至少它们在 RDS 配置上的表现方式)。

但是,查询有效,它是按照我的预期方式计划的查询,并且在运行作业 3 天后内存使用量现在稳定,并且我的服务器没有崩溃。

我根本没有更改节点代码。

于 2016-04-24T07:16:01.117 回答