get_jobs
トランザクション内で、node-postgres
brianc のパッケージを使用して 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 DROP
CREATE TABLE
CFrei の提案に従って、
pg.defaults.poolSize = 0
プーリングを無効にするためにノード コードに追加されました。サーバーはまだクラッシュしましたが、以下の最初のスパイクのように見えた以前のすべてのテストよりもはるかに時間がかかり、スワップがはるかに高くなりました (2 番目のスパイク)。後で、pg.defaults.poolSize = 0
プーリングが期待どおりに無効にならない可能性があることがわかりました。
これに基づいて、「自動バキュームでは一時テーブルにアクセスできません。したがって、適切なバキューム操作と分析操作は、セッション SQL コマンドを介して実行する必要があります。」、
VACUUM
ノード サーバーからa を実行しようとしましVACUUM
た ("inセッション」コマンド)。私は実際にこのテストを機能させることができませんでした。データベースに多くのオブジェクトがありVACUUM
、すべてのオブジェクトを操作しているため、各ジョブの繰り返しの実行に時間がかかりすぎていました。一時テーブルのみに制限VACUUM
することは不可能でした - (a) 実行できませんVACUUM
トランザクション内および (b) トランザクション外では、一時テーブルは存在しません。pg_attributes
:P 編集: Postgres IRC フォーラムの後半で、VACUUM は一時テーブル自体には関係ないが、その TEMP TABLES の原因で作成および削除された行をクリーンアップするのに役立つ可能性があると有益なチャップが説明しました。いずれにせよ、「セッション中」の VACUUM は答えではありませんでした。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
、関連するすべてのリソースを解放していませんか? 他に何が記憶を保持している可能性がありますか? どうすれば解放できますか?