3

get_jobsトランザクション内で、node-postgresbrianc のパッケージを使用して 1 秒間に 18 回、Postgres (Amazon RDS マイクロ インスタンス) 関数を呼び出す node.js プログラムがあります。

ノード コードは、brianc の基本的なクライアント プーリングの例の拡張バージョンにすぎません。

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;

... 55msごとに繰り返されます。

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です。(私はこれを SQL Server で非常に効果的に使用しており、現在はクエリ オプティマイザーを信頼していませんが、これが Postgres の間違ったアプローチであるかどうか教えてください!)

クエリは小さなテーブルで 8 ミリ秒 (ノードから測定) で実行され、次のジョブが開始される前に 1 つのジョブ「ポーリング」を完了するのに十分な時間です。

問題: この速度で約 3 時間ポーリングした後、Postgres サーバーがメモリ不足になり、クラッシュします。

すでに試したこと...

  • 一時テーブルなしで関数を書き直せば、Postgres はメモリ不足にはなりませんが、一時テーブル パターンを多用するので、これは解決策ではありません。

  • ノード プログラムを停止すると (クエリの実行に使用する 10 個の接続が強制終了されます)、メモリが解放されます。ポーリング セッション間でノードを 1 分間待機させるだけでは同じ効果が得られないため、プールされた接続に関連付けられた Postgres バックエンドが保持しているリソースが明らかに存在します。

  • ポーリングの実行VACUUM中に a を実行しても、メモリ消費には影響せず、サーバーは停止し続けます。

  • ポーリング頻度を減らしても、サーバーが停止するまでの時間だけが変わります。

  • DISCARD ALL;それぞれの後に追加しCOMMIT;ても効果はありません。

  • s の s の代わりにDROP TABLE jobs; DROP TABLE jobs_extra;afterを明示的に呼び出します。サーバーはまだクラッシュします。RETURN query ()ON COMMIT DROPCREATE TABLE

  • CFrei の提案に従って、pg.defaults.poolSize = 0プーリングを無効にするためにノード コードに追加されました。サーバーはまだクラッシュしましたが、以下の最初のスパイクのように見えた以前のすべてのテストよりもはるかに時間がかかり、スワップがはるかに高くなりました (2 番目のスパイク)。後で、pg.defaults.poolSize = 0 プーリングが期待どおりに無効にならない可能性があることがわかりました。

Postgres サーバーのスワップ メモリ使用量

  • これに基づいて、自動バキュームでは一時テーブルにアクセスできません。したがって、適切なバキューム操作と分析操作は、セッション SQL コマンドを介して実行する必要があります。」、VACUUMノード サーバーからa を実行しようとしましVACUUMた ("inセッション」コマンド)。私は実際にこのテストを機能させることができませんでした。データベースに多くのオブジェクトがありVACUUM、すべてのオブジェクトを操作しているため、各ジョブの繰り返しの実行に時間がかかりすぎていました。一時テーブルのみに制限VACUUMすることは不可能でした - (a) 実行できませんVACUUMトランザクション内および (b) トランザクション外では、一時テーブルは存在しません。pg_attributes:P 編集: Postgres IRC フォーラムの後半で、VACUUM は一時テーブル自体には関係ないが、その TEMP TABLES の原因で作成および削除された行をクリーンアップするのに役立つ可能性があると有益なチャップが説明しました。いずれにせよ、「セッション中」の VACUUM は答えではありませんでした。

  • DROP TABLE ... IF EXISTSCREATE TABLEの代わりに、の前にON COMMIT DROP。サーバーはまだ死んでいます。

  • CREATE TEMP TABLE (...)insert into ... (select...)代わりにCREATE TEMP TABLE ... AS、の代わりにON COMMIT DROP。サーバーが死ぬ。

ではON COMMIT DROP、関連するすべてのリソースを解放していませんか? 他に何が記憶を保持している可能性がありますか? どうすれば解放できますか?

4

2 に答える 2

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;

プランナーは、一時テーブルで達成したかった方法で、各ブロックを順番に評価します。

これでメモリ リークの問題が直接解決されないことはわかっています (少なくとも RDS 構成でのマニフェスト方法に関しては、Postgres の実装に問題があると確信しています)。

ただし、クエリは機能し、意図したとおりにクエリが計画されており、ジョブを実行してから 3 日経ってもメモリ使用量は安定しており、サーバーはクラッシュしません。

ノードコードはまったく変更していません。

于 2016-04-24T07:16:01.117 に答える