17

C# デーモン アプリケーション (dotConnect を ADO.NET プロバイダーとして使用) を SQL Server 2008 R2 から PostgreSQL 9.0.4 x64 (Windows Server 2008 R2 上) に移動することにしました。したがって、PostgreSQL 構文に一致するようにすべてのクエリをわずかに変更しましたが、SQL Server の同じクエリでは発生しなかった動作に行き詰まりました (低レベルの Express エディションでも)。

データベースに、相互に関係のない 2 つの非常に単純なテーブルが含まれているとします。ID、Name、Model、ScanDate、Notes のようになります。TCP/IP 経由でデータを読み取り、処理し、トランザクションを開始し、通常の INSERT を使用して結果を前述の 2 つのテーブルに入れる変換プロセスがあります。テーブルは最初は空です。BLOB 列はありません。悪い日には約 500.000 回の INSERT があり、すべてが 1 つのトランザクションにまとめられています (複数のトランザクションに分割することはできません)。SELECT、UPDATE、または DELETE は行われません。INSERT の例 (ID は bigserial - 自動的に自動インクリメントされます):

INSERT INTO logs."Incoming" ("Name", "Model", "ScanDate", "Notes")
VALUES('Ford', 'Focus', '2011-06-01 14:12:32', NULL)

SQL Server は、約 200 MB の妥当なワーキング セットを維持しながら、負荷を冷静に受け入れます。ただし、PostgreSQL は、トランザクションが実行されるたびに (!) 追加で 30 MB を消費し、システム RAM をすぐに使い果たします。

RTFM を実行し、postgresql.conf をいじってみました: 「work_mem」を最小 64 kB に設定し (これにより、RAM の占有がわずかに遅くなりました)、「shared_buffers」/「temp_buffers」を最小に減らしました (違いはありません)。無駄に。トランザクション分離レベルを Read Uncommitted に下げても効果がありませんでした。ID BIGSERIAL (PK) のインデックス以外にインデックスはありません。SqlCommand.Prepare() は違いはありません。同時接続は確立されません。デーモンはデータベースを排他的に使用します。

PostgreSQL は気が遠くなるほど単純な INSERT フェストに対応できないように見えるかもしれませんが、SQL Server はそれを行うことができます。多分それは PostgreSQL スナップショットと SQL Server のロックの分離の違いでしょうか? これは私にとって事実です。標準の SQL Server は動作しますが、標準の PostgreSQL も微調整した PostgreSQL も動作しません。

INSERT ベースのトランザクションが実行されている間、PostgreSQL のメモリ消費量を (SQL Server の場合と同様に) 一定に保つにはどうすればよいですか?

編集:人工的なテストケースを作成しました:

DDL :

CREATE TABLE sometable
(
  "ID" bigserial NOT NULL,
  "Name" character varying(255) NOT NULL,
  "Model" character varying(255) NOT NULL,
  "ScanDate" date NOT NULL,
  CONSTRAINT "PK" PRIMARY KEY ("ID")
)
WITH (
  OIDS=FALSE
);

C# (Devart.Data.dll と Devart.Data.PostgreSql.dll が必要)

PgSqlConnection conn = new PgSqlConnection("Host=localhost; Port=5432; Database=testdb; UserId=postgres; Password=###########");
conn.Open();
PgSqlTransaction tx = conn.BeginTransaction(IsolationLevel.ReadCommitted);

for (int ii = 0; ii < 300000; ii++)
{
    PgSqlCommand cmd = conn.CreateCommand();
    cmd.Transaction = tx;
    cmd.CommandType = CommandType.Text;
    cmd.CommandText = "INSERT INTO public.\"sometable\" (\"Name\", \"Model\", \"ScanDate\") VALUES(@name, @model, @scanDate) RETURNING \"ID\"";
    PgSqlParameter parm = cmd.CreateParameter();
    parm.ParameterName = "@name";
    parm.Value = "SomeName";
    cmd.Parameters.Add(parm);

    parm = cmd.CreateParameter();
    parm.ParameterName = "@model";
    parm.Value = "SomeModel";
    cmd.Parameters.Add(parm);

    parm = cmd.CreateParameter();
    parm.ParameterName = "@scanDate";
    parm.PgSqlType = PgSqlType.Date;
    parm.Value = new DateTime(2011, 6, 1, 14, 12, 13);
    cmd.Parameters.Add(parm);

    cmd.Prepare();

    long newID = (long)cmd.ExecuteScalar();
}

tx.Commit();

これにより、メモリの浪費が再現されます。ただし、「cmd」変数が作成され、FOR ループの外で .Prepare()d が実行された場合、メモリは増加しません。どうやら、IDENTICAL SQL を使用して複数の PgSqlCommands を準備しても、パラメーター値が異なると、SQL Server のように PostgreSQL 内で単一のクエリ プランにはなりません。

問題は残ります: Fowler の Active Record dp を使用して複数の新しいオブジェクトを挿入する場合、準備された PgSqlCommand インスタンスの共有はエレガントではありません。

同一の構造で異なる引数値を持つ複数のクエリでクエリプランの再利用を容易にする方法/オプションはありますか?

アップデート

ADO.NET を使用せずに、SQL バッチが DBMS で直接実行される (Jorani が提案) という、可能な限り単純なケースを検討することにしました。驚いたことに、PostgreSQL は受信した SQL クエリを比較せず、内部でコンパイルされたプランを再利用しません。受信したクエリが同じ引数を持っている場合でも! たとえば、次のバッチ:

PostgreSQL (pgAdmin -> クエリの実行経由) -- メモリを大量に消費する

BEGIN TRANSACTION;

INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
-- the same INSERT is repeated 100.000 times

COMMIT;

SQL Server (Management Studio -> Execute 経由) -- メモリ使用量を一定に保ちます

BEGIN TRANSACTION;

INSERT INTO [dbo].sometable ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
INSERT INTO [dbo].sometable ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
-- the same INSERT is repeated 100.000 times

COMMIT;

PostgreSQL ログ ファイル (ありがとう、Sayap!) には次のものが含まれます。

2011-06-05 16:06:29 EEST LOG:  duration: 0.000 ms  statement: set client_encoding to 'UNICODE'
2011-06-05 16:06:43 EEST LOG:  duration: 15039.000 ms  statement: BEGIN TRANSACTION;

INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES('somename', 'somemodel', '2011-06-01 14:12:19');
-- 99998 lines of the same as above
COMMIT;

どうやら、クエリ全体をそのままサーバーに送信した後でも、サーバーはそれを最適化できません。

ADO.NET ドライバーの代替

Jordani が示唆したように、dotConnect の代わりに NpgSql ドライバーを試してみましたが、結果は同じ (不足) でした。ただし、 .Prepare() メソッドの Npgsql ソースには、次のようなわかりやすい行が含まれています。

planName = m_Connector.NextPlanName();
String portalName = m_Connector.NextPortalName();
parse = new NpgsqlParse(planName, GetParseCommandText(), new Int32[] { });
m_Connector.Parse(parse);

ログ ファイルの新しい内容:

2011-06-05 15:25:26 EEST LOG:  duration: 0.000 ms  statement: BEGIN; SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
2011-06-05 15:25:26 EEST LOG:  duration: 1.000 ms  parse npgsqlplan1: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST LOG:  duration: 0.000 ms  bind npgsqlplan1: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST DETAIL:  parameters: $1 = 'SomeName', $2 = 'SomeModel', $3 = '2011-06-01'
2011-06-05 15:25:26 EEST LOG:  duration: 1.000 ms  execute npgsqlplan1: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST DETAIL:  parameters: $1 = 'SomeName', $2 = 'SomeModel', $3 = '2011-06-01'
2011-06-05 15:25:26 EEST LOG:  duration: 0.000 ms  parse npgsqlplan2: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST LOG:  duration: 0.000 ms  bind npgsqlplan2: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST DETAIL:  parameters: $1 = 'SomeName', $2 = 'SomeModel', $3 = '2011-06-01'
2011-06-05 15:25:26 EEST LOG:  duration: 0.000 ms  execute npgsqlplan2: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"
2011-06-05 15:25:26 EEST DETAIL:  parameters: $1 = 'SomeName', $2 = 'SomeModel', $3 = '2011-06-01'
2011-06-05 15:25:26 EEST LOG:  duration: 0.000 ms  parse npgsqlplan3: INSERT INTO public."sometable" ("Name", "Model", "ScanDate") VALUES($1::varchar(255), $2::varchar(255), $3::date) RETURNING "ID"

このログの抜粋では、非効率性が非常に明白です...

結論(そのようなもの)

Frank の WAL に関するメモは、もう 1 つの目覚めです。典型的な MS 開発者が SQL Server を隠していることを構成する別の何かです。

NHibernate は (最も単純な使用法であっても) 準備された SqlCommands を適切に再利用します...それが最初から使用されていれば...

SQL Server と PostgreSQL の間にアーキテクチャ上の違いが存在することは明らかであり、 SQL Server用に特別に構築されたコード(したがって、「同一の SQL を再利用できない」可能性を幸いなことに認識していない) は、PostgreSQL では大きな問題がなければ効率的に機能しません。リファクタリング。また、130 以上のレガシー ActiveRecord クラスをリファクタリングして、準備された SqlCommand オブジェクトを乱雑なマルチスレッド ミドルウェアで再利用することは、「dbo を public に置き換えるだけ」タイプの作業ではありません。

残念ながら私の残業には、Eevar の答えは正しいです :)

参加してくれたみんな、ありがとう!

4

3 に答える 3

9

work_memとshared_buffersを減らすのは良い考えではありません。データベース(PostgreSQLを含む)はRAMが大好きです。

しかし、これはあなたの最大の問題ではないかもしれません、WAL設定はどうですか?wal_buffersは、トランザクション全体、つまりすべての500kINSERTを保持するのに十分な大きさである必要があります。現在の設定は何ですか?そして、checkpoint_segmentsはどうですか?

500kINSERTは問題ではないはずです。PostgreSQLはメモリの問題なしにこれを処理できます。

http://www.postgresql.org/docs/current/interactive/runtime-config-wal.html

于 2011-06-04T18:59:55.603 に答える
8

あなたはそれを自分で理解したのではないかと思います。おそらく、500kの異なるプリペアドステートメント、クエリプラン、その他すべてを作成しているでしょう。実際、それはそれよりも悪いです。プリペアドステートメントはトランザクション境界の外側に存在し、接続が閉じられるまで存続します。このようにそれらを乱用すると、大量のメモリが消費されます。

クエリを複数回実行したいが、実行ごとの計画オーバーヘッドを回避したい場合は、単一のプリペアドステートメントを作成し、それを新しいパラメーターで再利用します。

クエリが一意でアドホックな場合は、postgresのバインド変数の通常のサポートを使用してください。プリペアドステートメントによる余分なオーバーヘッドは必要ありません。

于 2011-06-05T00:04:24.257 に答える
1
  1. 私はフランクに完全に同意します。

  2. 準備されたPgSqlCommandインスタンス共有はエレガントではありません。

なぜ??外部ループを持つことはできませんか?

    cmd = conn.CreateCommand(); 
    parm1 = cmd.CreateParameter();
    parm1.ParameterName = "@name";
    parm2 = cmd.CreateParameter();
    parm2.ParameterName = "@model";
    parm3 = cmd.CreateParameter(); 
    parm3.ParameterName = "@scanDate"; 

また、私はこれをmsdnで見つけました:

// NOTE:
// For optimal performance, make sure you always set the parameter
// type and the maximum size - this is especially important for non-fixed
// types such as NVARCHAR or NTEXT;

dotConnectがSQLサーバープロバイダーとして機能していない場合、それは良くありません(最新バージョン/バグ修正済みです)。他のプロバイダーを使用できますか?

誰がメモリを「食べている」かを確認する必要があります-dbサーバーまたはプロバイダー。SQLスクリプトと「psql.exe」を生成する場合は、PostgreSqlをテストすることもできます

于 2011-06-05T00:01:48.963 に答える