3

最適化する必要があるデータベース テーブルが大きくなりすぎて (数億行) あるのですが、それをパーティション分割する前に、提案について尋ねてみようと思いました。

使用法は次のとおりです。

0 . テーブルには、それぞれ約 20 バイトの長さの約 10 列が含まれます。

  1. INSERTS は、毎秒数百回の速度で実行されます。

  2. SELECT ステートメントは、列 'a' (ここで a='xxxx' ) に基づいて 1 時間に数回実行されます。

  3. DELETE ステートメントは、DATE 列に基づいて実行されます。(日付が 1 年以上古い場合は削除) 通常は 1 日 1 回。

重要な要件は、INSERT および SELECT ステートメントを高速化し、削除中にテーブル全体をロックダウンすることなく、1 年前の履歴データを保持できるようにすることです。

列 'a' 用と日付フィールド用の 2 つのインデックスが必要だと思います。または両方を最適化することは可能ですか?

選択の速度と削除の速度の間に必要なトレードオフはありますか?

パーティショニングが唯一の解決策ですか? そのようなテーブルを分割するための良い戦略は何ですか?

PostgreSQL 8.4 データベースを使用しています。

4

5 に答える 5

4

単一の物理テーブルを保持するのではなく、PostgreSQL のパーティショニングを調べましたか? バージョン 8.1 以降でサポートされています。

パーティショニングは、高速 INSERT パフォーマンスと高速 DELETE パフォーマンスのどちらを選択するかという問題を回避するのに役立ちます。年/月ごとにテーブルをいつでも分割でき、不要になったパーティションを削除するだけです。パーティションの削除は非常に高速で、小さなパーティションへの挿入も非常に高速です。

マニュアルから:

パーティショニングとは、論理的に 1 つの大きなテーブルを、より小さな物理的な部分に分割することを指します。パーティショニングにはいくつかの利点があります。

  • 特定の種類のクエリでは、クエリのパフォーマンスを大幅に向上させることができます。
  • テーブルの各部分には、データセット全体のインデックスよりも小さいインデックスがあるため、更新のパフォーマンスも向上する可能性があります。インデックスがメモリに簡単に収まらなくなると、インデックスに対する読み取り操作と書き込み操作の両方で、徐々に多くのディスク アクセスが必要になります。
  • 一括削除は、その要件がパーティショニング設計に組み込まれている場合、パーティションの 1 つを削除するだけで実行できます。DROP TABLE は、その後の VACUUM オーバーヘッドは言うまでもなく、バルク DELETE よりもはるかに高速です。
  • めったに使用されないデータは、安価で低速なストレージ メディアに移行できます。

この利点は通常、テーブルが非常に大きくなる場合にのみ価値があります。どの時点でテーブルがパーティショニングの恩恵を受けるかは、アプリケーションによって異なりますが、経験則では、テーブルのサイズはデータベース サーバーの物理メモリを超える必要があります。

現在、PostgreSQL はテーブル継承によるパーティショニングをサポートしています。各パーティションは、単一の親テーブルの子テーブルとして作成する必要があります。通常、親テーブル自体は空です。データセット全体を表すためだけに存在します。パーティショニングの実装を試みる前に、継承 (セクション 5.8 を参照) に精通している必要があります。

于 2010-03-04T19:28:15.497 に答える
3

他の人が述べたように、パーティショニングがあなたの答えですが、

私はいくつかに分割しますhash(a)aが整数の場合はa%256良いでしょう。それがテキストの場合は、次のようなものsubstring(md5(a) for 2)です。

挿入と選択を高速化します。

削除の場合は、より頻繁に実行しますが、小さくし、パーティション化します。私は毎時間(XX:30に)それらを実行し、次のようにします:

delete from table_name
where date<(current_date - interval '1 year')
and
  hash(a)
  =
  (extract(doy from current_timestamp) * 24
    + extract(hour from current_timestamp))::int % 256;

編集:私はこれをテストしました:

create function hash(a text) returns text as $$ select substring(md5($1) for 1) $$ language sql immutable strict;
CREATE TABLE tablename (id text, mdate date);
CREATE TABLE tablename_partition_0 ( CHECK ( hash(id) = '0' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_1 ( CHECK ( hash(id) = '1' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_2 ( CHECK ( hash(id) = '2' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_3 ( CHECK ( hash(id) = '3' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_4 ( CHECK ( hash(id) = '4' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_5 ( CHECK ( hash(id) = '5' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_6 ( CHECK ( hash(id) = '6' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_7 ( CHECK ( hash(id) = '7' ) ) INHERITS (tablename); 
CREATE TABLE tablename_partition_8 ( CHECK ( hash(id) = '8' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_9 ( CHECK ( hash(id) = '9' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_a ( CHECK ( hash(id) = 'a' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_b ( CHECK ( hash(id) = 'b' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_c ( CHECK ( hash(id) = 'c' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_d ( CHECK ( hash(id) = 'd' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_e ( CHECK ( hash(id) = 'e' ) ) INHERITS (tablename);
CREATE TABLE tablename_partition_f ( CHECK ( hash(id) = 'f' ) ) INHERITS (tablename);
analyze;
explain select * from tablename where id='bar' and hash(id)=hash('bar');
                                         クエリプラン                                          
-------------------------------------------------- -------------------------------------------
 結果 (コスト=0.00..69.20 行=2 幅=36)
   -> 追加 (cost=0.00..69.20 rows=2 width=36)
         -> tablename の Seq Scan (cost=0.00..34.60 rows=1 width=36)
               フィルタ: ((id = 'bar'::text) AND ("substring"(md5(id), 1, 1) = '3'::text))
         -> tablename_partition_3 tablename の Seq Scan (cost=0.00..34.60 rows=1 width=36)
               フィルタ: ((id = 'bar'::text) AND ("substring"(md5(id), 1, 1) = '3'::text))
(6行)

クエリに追加する必要があります。そうしないとhash(id)=hash('searched_value')、Postgres がすべてのテーブルを検索します。


編集:テーブルを修正するための自動挿入にルールシステムを使用することもできます:

create rule tablename_rule_0 as
  on insert to tablename where hash(NEW.id)='0'
  do instead insert into tablename_partition_0 values (NEW.*);
create rule tablename_rule_1 as
  on insert to tablename where hash(NEW.id)='1'
  do instead insert into tablename_partition_1 values (NEW.*);
-- and so on
insert into tablename (id) values ('a');
select * from tablename_partition_0;
 id | mdate 
----+-------
 a  | 
(1 row)
于 2010-03-05T15:51:38.410 に答える
0

私は専門家ではありませんが、列「a」でパーティション化すると選択が高速化されるようですが、日付でパーティション化すると(他のすべての回答が示唆しているように)削除が高速化されます(テーブルをドロップします)が、あなたにとっては役に立たないでしょう選択する。

どちらの場合も、挿入のパフォーマンスが向上するようです。

この問題に重きを置く専門家のケアはありますか? 両方のフィールドで分割することは可能/有用ですか?

于 2010-03-05T12:08:20.240 に答える
0

1 つの解決策は、挿入日に基づいて分割することです。

つまり、アプリケーション(またはDAO)は、現在の日付(または最後のパーティションスライスが開始されてからの時間)および/または「最後の」パーティションのサイズを組み合わせたロジックに基づいて、挿入するテーブルを決定します。または、そのようなロジックを毎日のスクリプトにオフロードし、DAO で使用するために、スクリプトに「これは使用するパーティションです」と入力させます。

これにより、「古い」行を削除する必要がすぐになくなります (古いパーティションを削除するだけです)。また、挿入が定期的に小さなテーブルの作成を開始することを保証します。これにより、とりわけ、「平均的な」INSERT/SELECT 速度が向上します (もちろん、最悪のシナリオでも同様に遅くなります)。

于 2010-03-04T19:28:28.930 に答える