web-dev-qa-db-ja.com

読み取りパフォーマンスのためのPostgreSQLの構成

私たちのシステムは大量のデータを書き込みます(一種のビッグデータシステム)。書き込みパフォーマンスは私たちのニーズには十分ですが、読み取りパフォーマンスは本当に遅すぎます。

主キー(制約)構造は、すべてのテーブルで同様です。

timestamp(Timestamp) ; index(smallint) ; key(integer).

テーブルには数百万行、場合によっては数十億行を含めることができ、読み取りリクエストは通常​​、特定の期間(タイムスタンプ/インデックス)とタグに対するものです。約200k行を返すクエリがあるのが一般的です。現在、1秒あたり約15,000行を読み取ることができますが、10倍高速にする必要があります。これは可能ですか?可能な場合、どのように?

注: PostgreSQLは当社のソフトウェアにパッケージ化されているため、ハードウェアはクライアントごとに異なります。

これは、VMテストに使用されます。VMのホストは、24.0 GBのRAMを搭載したWindows Server 2008 R2 x64です。

サーバー仕様(仮想マシンVMWare)

Server 2008 R2 x64
2.00 GB of memory
Intel Xeon W3520 @ 2.67GHz (2 cores)

postgresql.conf最適化

shared_buffers = 512MB (default: 32MB)
effective_cache_size = 1024MB (default: 128MB)
checkpoint_segment = 32 (default: 3)
checkpoint_completion_target = 0.9 (default: 0.5)
default_statistics_target = 1000 (default: 100)
work_mem = 100MB (default: 1MB)
maintainance_work_mem = 256MB (default: 16MB)

テーブル定義

CREATE TABLE "AnalogTransition"
(
  "KeyTag" integer NOT NULL,
  "Timestamp" timestamp with time zone NOT NULL,
  "TimestampQuality" smallint,
  "TimestampIndex" smallint NOT NULL,
  "Value" numeric,
  "Quality" boolean,
  "QualityFlags" smallint,
  "UpdateTimestamp" timestamp without time zone, -- (UTC)
  CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag" ),
  CONSTRAINT "FK_AnalogTransition_Tag" FOREIGN KEY ("KeyTag")
      REFERENCES "Tag" ("Key") MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION
)
WITH (
  OIDS=FALSE,
  autovacuum_enabled=true
);

クエリ

PgAdmin3でのクエリの実行には約30秒かかりますが、可能であれば5秒未満で同じ結果を得たいと考えています。

SELECT 
    "AnalogTransition"."KeyTag", 
    "AnalogTransition"."Timestamp" AT TIME ZONE 'UTC', 
    "AnalogTransition"."TimestampQuality", 
    "AnalogTransition"."TimestampIndex", 
    "AnalogTransition"."Value", 
    "AnalogTransition"."Quality", 
    "AnalogTransition"."QualityFlags", 
    "AnalogTransition"."UpdateTimestamp"
FROM "AnalogTransition"
WHERE "AnalogTransition"."Timestamp" >= '2013-05-16 00:00:00.000' AND "AnalogTransition"."Timestamp" <= '2013-05-17 00:00:00.00' AND ("AnalogTransition"."KeyTag" = 56 OR "AnalogTransition"."KeyTag" = 57 OR "AnalogTransition"."KeyTag" = 58 OR "AnalogTransition"."KeyTag" = 59 OR "AnalogTransition"."KeyTag" = 60)
ORDER BY "AnalogTransition"."Timestamp" DESC, "AnalogTransition"."TimestampIndex" DESC
LIMIT 500000;

説明1

"Limit  (cost=0.00..125668.31 rows=500000 width=33) (actual time=2.193..3241.319 rows=500000 loops=1)"
"  Buffers: shared hit=190147"
"  ->  Index Scan Backward using "PK_AnalogTransition" on "AnalogTransition"  (cost=0.00..389244.53 rows=1548698 width=33) (actual time=2.187..1893.283 rows=500000 loops=1)"
"        Index Cond: (("Timestamp" >= '2013-05-16 01:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-16 15:00:00-04'::timestamp with time zone))"
"        Filter: (("KeyTag" = 56) OR ("KeyTag" = 57) OR ("KeyTag" = 58) OR ("KeyTag" = 59) OR ("KeyTag" = 60))"
"        Buffers: shared hit=190147"
"Total runtime: 3863.028 ms"

説明2

私の最新のテストでは、データを選択するのに7分かかりました。下記参照:

"Limit  (cost=0.00..313554.08 rows=250001 width=35) (actual time=0.040..410721.033 rows=250001 loops=1)"
"  ->  Index Scan using "PK_AnalogTransition" on "AnalogTransition"  (cost=0.00..971400.46 rows=774511 width=35) (actual time=0.037..410088.960 rows=250001 loops=1)"
"        Index Cond: (("Timestamp" >= '2013-05-22 20:00:00-04'::timestamp with time zone) AND ("Timestamp" <= '2013-05-24 20:00:00-04'::timestamp with time zone) AND ("KeyTag" = 16))"
"Total runtime: 411044.175 ms"
43
JPelletier

データの配置とストレージサイズ

実際、インデックスTupleごとのオーバーヘッドは、Tupleヘッダーの8バイトとアイテムポインターの4バイトです。

関連:

主キーには3つの列があります。

PRIMARY KEY ("Timestamp" , "TimestampIndex" , "KeyTag")

"Timestamp"      timestamp (8 bytes)
"TimestampIndex" smallint  (2 bytes)
"KeyTag"         integer   (4 bytes)

結果:

ページヘッダー内の4バイトのアイテムポインター(8バイトの倍数にはカウントされません)
 
インデックスタプルヘッダー用の8バイト
 8バイト "タイムスタンプ" [ .____。 ---- 
インデックスタプルごとに28バイト。プラス数バイトのオーバーヘッド。

この関連回答のオブジェクトサイズの測定の詳細:

複数列インデックスの列の順序

次の2つの質問と回答を読んで理解してください。

インデックス(主キー)を使用する方法では、並べ替え手順なしで行を取得できます。これは特にLIMITを使用すると魅力的です。しかしretrieving行は非常に高価に見えます。

一般に、複数列のインデックスでは、「等価」列が最初に、「範囲」列が最後に表示されます。

したがって、列の順序を逆にして追加のインデックスを試してください

CREATE INDEX analogransition_mult_idx1
   ON "AnalogTransition" ("KeyTag", "TimestampIndex", "Timestamp");

データの配布に依存します。しかしmillions of row, even billion of rowsを使用すると、これは大幅に速くなる可能性があります。

タプルのサイズは、データの配置とパディングのため、8バイト大きくなります。これをプレーンインデックスとして使用している場合は、3番目の列"Timestamp"を削除してみてください。 (ソートに役立つ可能性があるため)少し速いかもしれませんし、そうでないかもしれません。

両方のインデックスを保持したい場合があります。いくつかの要因によっては、元のインデックスが望ましい場合があります(特に、小さいLIMITの場合)。

自動バキュームとテーブル統計

テーブル統計は最新である必要があります。 autovacuum が実行されていると思います。

テーブルが巨大で、適切なクエリプランにとって統計が重要であるように見えるので、関連する列の statistics target を大幅に増やします。

ALTER TABLE "AnalogTransition" ALTER "Timestamp" SET STATISTICS 1000;

...または数十億行の場合はさらに高くなります。最大値は10000で、デフォルトは100です。

WHEREまたはORDER BY句に含まれるすべての列に対してこれを行います。次に、ANALYZEを実行します。

テーブルレイアウト

その間、データの配置とパディングについて学んだことを適用すると、この最適化されたテーブルレイアウトによってディスクスペースが節約され、パフォーマンスが少し向上します(pkとfkを無視)。

CREATE TABLE "AnalogTransition"(
  "Timestamp" timestamp with time zone NOT NULL,
  "KeyTag" integer NOT NULL,
  "TimestampIndex" smallint NOT NULL,
  "TimestampQuality" smallint,
  "UpdateTimestamp" timestamp without time zone, -- (UTC)
  "QualityFlags" smallint,
  "Quality" boolean,
  "Value" numeric
);

CLUSTER/pg_repack

特定のインデックスを使用するクエリの読み取りパフォーマンスを最適化するには(それが元のインデックスであろうと、私が提案した代替であろうと)、インデックスの物理的な順序でテーブルを書き換えることができます。 CLUSTER はそれを行いますが、かなり侵襲的であり、操作の間は排他ロックが必要です。
pg_repack は、テーブルを排他的にロックしなくても同じことができる、より洗練された代替手段です。
pg_squeeze は、最近の同様のツールです(まだ使用していません)。

読み取る必要があるテーブルのブロックがはるかに少ないため、これは巨大なテーブルで実質的に役立ちます。

RAM

一般に、2 GBの物理RAMは数十億行をすばやく処理するのに十分ではありません。さらにRAMは長い道のりになる可能性があります-適応設定を伴う:そもそも大きなeffective_cache_size

55

したがって、計画から私は1つのことを見る:あなたのインデックスは肥大化(その後、基になるテーブルと一緒に)するか、単にこの種のクエリにはあまり適していません(上記の私の最新のコメントでこれに対処しようとしました)。

インデックスの1つの行には、14バイトのデータ(およびヘッダーの一部)が含まれています。ここで、計画で与えられた数値から計算します。190147ページから500,000行を取得しました。つまり、平均して、ページあたり3未満の有効な行、つまり、8 kbページあたり約37バイトです。これは非常に悪い比率ですね。インデックスの最初の列はTimestampフィールドであり、クエリで範囲として使用されるため、プランナーは一致する行を見つけるためにインデックスを選択できます(実際に使用します)。ただし、TimestampIndex条件にWHEREが記載されていないため、KeyTagでのフィルタリングは、インデックスページにランダムに表示されると思われるため、あまり効果的ではありません。

したがって、1つの可能性は、インデックス定義を

CONSTRAINT "PK_AnalogTransition" PRIMARY KEY ("Timestamp", "KeyTag", "TimestampIndex")

(または、システムの負荷を考慮して、このインデックスを新しいインデックスとして作成します。

CREATE INDEX CONCURRENTLY "idx_AnalogTransition" 
    ON "AnalogTransition" ("Timestamp", "KeyTag", "TimestampIndex");
  • これには確かにしばらく時間がかかりますが、それまでの間は引き続き作業できます。)

インデックスページの大部分がデッドローで占められている可能性もあります。デッドローは、バキュームによって削除できます。 autovacuum_enabled=trueを設定してテーブルを作成しましたが、自動バキュームを開始したことがありますか?または、VACUUMを手動で実行しますか?

11
dezso