web-dev-qa-db-ja.com

文字列のJSONB配列(GINインデックス付き)と行の分割(Bツリーインデックス)

データに関連するアカウントを示すreceiverを格納するデータベースがあります。 1セットのデータがreceiver列を除いてすべての列データが同じである3つの個別の行を作成する可能性があるため、これはデータの大量の複製につながりました。データベースの再設計中に、レシーバーの現在のBツリーインデックスではなく、GINインデックスを持つ配列を使用することを検討しました。

現在のテーブル定義:

CREATE TABLE public.actions (
    global_sequence bigint NOT NULL DEFAULT nextval('actions_global_sequence_seq'::regclass),
    time timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
    receiver text NOT NULL,
    tx_id text NOT NULL,
    block_num integer NOT NULL,
    contract text NOT NULL,
    action text NOT NULL,
    data jsonb NOT NULL
);

インデックス:

  • "actions_pkey" PRIMARY KEY、btree(global_sequence、time)
  • "actions_time_idx" btree(時間DESC)
  • "receiver_idx" btree(レシーバー)

フィールドの詳細:

  • グローバルシーケンスは、連続的に増加するIDです。
  • ブロック番号と時間は一意ではなく、インクリメントされます
  • データは時間によって内部的に分割されるため、グローバルシーケンスと時間が主キーです。
    • 10億を超えるアクションが関連付けられているレシーバーがあります(それぞれが固有のglobal_sequenceを備えています)。
  • 平均テキスト長:
    • レシーバー:12
    • tx_id:52
    • 契約:12
    • アクション:6
    • データ:アクションメタデータを含む中小規模のJSONB

つのスキーマオプションのカーディナリティ:

  • 現在:この表の42億行に位置
  • 配列としてのレシーバー:約18億行になります
  • 正規化: 3つのテーブルがあります:
    • アクション:18億行
    • Actions_Accounts:42億行
    • アカウント:500 000行

一般的なクエリ:

  • SELECT * FROM actions WHERE receiver = 'Alpha' ORDER BY time DESC LIMIT 100

クエリにはすべての列が必要です。 NULL値は表示されません。正規化されたスキーマでの結合は遅くなり、クエリの速度が最優先です)

1
Syed Jafri

最適なDB設計は常に全体像に依存します。

一般に、単純なクエリでは、プレーンなbtreeインデックスよりも高速なものはほとんどありません。 jsonまたはjsonbを導入するか、またはGINインデックスと組み合わせてプレーン配列タイプを導入しても、おそらく遅くなります

元のテーブルでこれマルチカラムインデックス正しいソート順で、一般的なクエリのgame changerである必要があります。

CREATE INDEX game_changer ON actions (receiver, time DESC);

このように、Postgresはインデックスから直接上位100行を選択できます。超早い。

関連:

現在のインデックスreceiver_idxおよびactions_time_idxは目的を失う可能性があります。

完全なインデックスの隣に、ストレージサイズは大きなテーブルの重要な要素であるため、重複を避けることは正しい考えかもしれません。しかし、それはさまざまな方法で実現できます。古き良き正規化はもう考えましたか?

CREATE TABLE receiver (
   receiver_id serial PRIMARY KEY
 , receiver    text NOT NULL -- UNIQUE?
);

CREATE TABLE action (  -- I shortened the name to "action"
   action_id   bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
   -- global_sequence bigint NOT NULL DEFAULT nextval('actions_global_sequence_seq'::regclass),  -- ??
   time       timestamptz NOT NULL DEFAULT now(),
   block_num  int NOT NULL,
   tx_id      text NOT NULL,
   contract   text NOT NULL,
   action     text NOT NULL,
   data       jsonb NOT NULL
)

CREATE TABLE receiver_action (
   receiver_id int    REFERENCES receiver
 , action_id   bigint REFERENCES action
 , PRIMARY KEY (receiver_id, action_id)
);

また、テーブルactionの列の順序が変更されているため、行ごとに数バイトが節約され、数十億行の場合は数GBになります。

見る:

あなたの共通のクエリは少し変更されます:

SELECT a.*
FROM   receiver_action ra
JOIN   action a USING (action_id)
WHERE  ra. receiver_id = (SELECT receiver_id FROM receiver WHERE receiver = 'Alpha')
ORDER  BY a.time DESC
LIMIT  100;

欠点:現在、一般的なクエリを高速にすることははるかに困難です。関連:

迅速な(そして少し汚れた)修正:テーブルreceiver_actiontime列を冗長に含める(またはそこに移動する) 。

CREATE TABLE receiver_action (
   receiver_id int    REFERENCES receiver
 , action_id   bigint REFERENCES action
 , time        timestamptz NOT NULL DEFAULT now()  -- !
 , PRIMARY KEY (receiver_id, action_id)
);

インデックスを作成します。

CREATE INDEX game_changer ON receiver_action (receiver_id, time DESC) INCLUDE (action_id);

INCLUDEにはPostgres 11以降が必要です。見る:

そして、このクエリを使用します:

SELECT a.*
FROM  (
   SELECT action_id
   FROM   receiver_action
   WHERE  receiver_id = (SELECT receiver_id FROM receiver WHERE receiver = 'Alpha')
   ORDER  BY time DESC
   LIMIT  100
   )
JOIN   action a USING (action_id);

one set of data may create 3 separate rowsの背後にある正確なストーリーによっては、n:m実装と式GINインデックスの代わりに、テーブルアクションの3つの個別の列でさえ、さらに多くのことが可能になる場合があります...しかし、それは深すぎます。ここをタップします。

4