web-dev-qa-db-ja.com

PostgreSQL-大規模なデータセットの頻繁な更新を回避するようにデータベースを設計する

これらの数値とクエリの必要条件を使用して、データベースのパフォーマンスを最適化しようとしています。

  1. 一意のIDで識別される200〜400kのネットワークセグメント
  2. 各ネットワークセグメントには、限られた数の動的属性(平均速度)を持つ状態があります。動的属性の単一の状態を8バイトに格納できます
  3. セグメントの状態は3分ごと、H24、7/7に変化します
  4. 特定の日付範囲のセグメントのグループ(場合によってはすべてのセグメント)の状態または実際の状況のみを照会することは可能ですか?.
  5. 特定の日付(通常は「現在」)の「自分の周り」のすべてのセグメントを検索する空間クエリを要求できます。

これらの必要条件により、このソリューションに到達しました(強い欠点、以下で説明)。

[A] TABLE main_segments_history( 
      id_segment integer NOT NULL,
      day date NOT NULL,    
      day_slices bigint[],
      CONSTRAINT main_segments_history_pk PRIMARY KEY (id_segment,day)
)

[B] TABLE current_segment_release_state(
      id_segment integer NOT NULL,
      release_date timestamptz,
      ... all other attributes ...
      CONSTRAINT currsegm_release_state_pk PRIMARY KEY (id_segment,release_date)
)

[A]テーブルの説明:

  1. これは、フィールド "day"で、partition_manager(pg_partman)。各パーティションは1か月です
  2. Day_slices配列は、480要素の1次元配列であり、1日の各3分のスライスの粒度を表します

[B]テーブルの説明:

  1. 各セグメントの現在のリリース状態です

ネットワークを詳しく説明するバックエンドプロセスがあります。
3分ごとに、各セグメントの状態を挿入または更新します。
つまり、このプロセスでは、1日の開始時に新しい行が挿入され、3分ごとに内部配列が更新されます。

このソリューションのadvantages

  1. 各月のパーティションテーブルの限られた数の行
  2. セグメントの静的データ(つまり、ジオメトリ)を結合するときの優れたパフォーマンス
  3. 現在のリリースのわずかな冗長性は、リアルタイムのリクエストに応答するのに非常に適しています
  4. スペースセーフ:〜12 GBのみ各パーティションに保存されたデータ(1か月)-

欠点

  1. constraint_exclusion。日付範囲でクエリを実行する場合、PostgreSQLのその機能/パラメータを使用する必要があります。これは、複数のパーティションにまたがるプリコンパイル済みクエリで定数値を使用しています。例:

    制約除外はOK(2017年2月と3月にのみ検索されます):

    SELECT * FROM main_segments_history 
             WHERE day BETWEEN '2017-01-01' AND '2017-02-03'
    

    制約除外KO(すべてのパーティションテーブルを検索します):

    SELECT * FROM main_segments_history m 
             JOIN sometable s ON s.id=m.id_segment 
             WHERE day BETWEEN s.day_from AND s.day_to
    
  2. アップデートはevilです。

パフォーマンスの問題のために自動バキュームをオフにし、毎晩バッチで実行しました。
このように毎日約90Mから190Mの更新があることを考慮してください。これは、postgreSQLによって完全に書き換えられた行の数でもあります(UPDATEが削除された行にフラグを付け、新しい行が挿入)毎日。

さらに、UPDATEは非常に時間のかかる操作であり、書き込みの遅延を引き起こすことがよくあります。

最初に、LINK-TO-DATAデザインを使用する可能性について調査します。たとえば、セグメントIDのコンテナーとしてテーブルを使用し、セグメント状態のBIG TABLEにdata_idを使用しますが、それぞれの行数を数えるだけで破棄しました処理する月:〜2.880.000.0は、1日あたり最大3GBの容量です。あまりよくない。

についてどう思いますか?このシステムを最適化するためのソリューションはありますか?

5
Flavio

テーブルへの更新を回避する唯一の方法は、しないだけですそれら。データをログに記録しているだけなので、1日分のデータを保存する日次テーブルをお勧めします。 1日に1億9000万回の更新を行う代わりに、1日に1億9000万回の挿入を行うことができます。セグメントの数と同じ数の更新。

 CREATE TABLE main_segments_history
 ( 
       id_segment integer NOT NULL,
       day date NOT NULL,    
       day_slices bigint[],
       CONSTRAINT main_segments_history_pk PRIMARY KEY (id_segment, day)
 ) ;

 CREATE TABLE dayly_segments
 (
      id_segment integer NOT NULL,
      day date NOT NULL,
      id_slice integer NOT NULL,
      slice bigint,
      PRIMARY KEY (id_segment, day, id_slice)
 ) ;

これは、1日のデータをシミュレートします(そして200セグメント。

 INSERT INTO 
     dayly_segments
     (id_segment, day, id_slice, slice)
 SELECT
     id_segment, '2017-01-01', id_slice, (random()*1e7)::bigint
 FROM
     generate_series (1, 200) AS s1(id_segment)
     CROSS JOIN generate_series (1, 20*24) AS s2(id_slice) ;

vacuumingプロセスを実行するときに、アクティビティが少ない時間があると思います。その時点で、dayly_segmentsテーブルからmain_segments_historyにデータを移動し、すべてのデータを配列として配置できます。これにより、main_segments_historyを更新することはありません。

これは、基本的に、次のことを行います。

 -- Move segments from dayly_segments to main_segment_history
 INSERT INTO
     main_segments_history
     (id_segment, day, day_slices)
 SELECT
     id_segment, day, 
     (SELECT array_agg(slice) 
      FROM (SELECT slice 
              FROM dayly_segments s1 
             WHERE s1.id_segment = s0.id_segment AND s1.day = s0.day 
          ORDER BY id_slice) AS s2)
 FROM
     (SELECT DISTINCT 
         id_segment, day 
     FROM
         dayly_segments s0
     WHERE 
         day = '2017-01-01'
     ) AS s0 ;

 -- Delete them from original
 DELETE FROM
     dayly_segments
 WHERE
     day = '2017-01-01' ;

 -- At this point, you should also...
 VACUUM dayly_segments ;

dbfiddle ここ


仮定:

  1. $current_valueを見逃すことはありません。つまり、配列にはholesがありません。
  2. id_sliceには、増加するシーケンスの結果を割り当てることができます。整数値の代わりに、time値だけを使用することもできます。
1
joanolo