1

大量 (~500 万) のインデックス付きデータ ポイントを含む一連のオブジェクト (~1000 行) を効率的に順序付けする方法を見つけようとすると、難しい問題があります。私の場合、特定のデータポイントでテーブルを並べ替えることができるクエリが必要です。各データポイントは 16 ビットの符号なし整数です。

私は現在、大きな配列を使用してこの問題を解決しています:

オブジェクト テーブル:

  id serial NOT NULL,
  category_id integer,
  description text,
  name character varying(255),
  created_at timestamp without time zone NOT NULL,
  updated_at timestamp without time zone NOT NULL,
  data integer[],

GIST インデックス:

  CREATE INDEX object_rdtree_idx
  ON object
  USING gist
  (data gist__intbig_ops)  

このインデックスは現在、選択クエリを実行するときに使用されていませんが、とにかく役立つかどうかはわかりません。

配列フィールドは毎日、約 500 万個の値の新しいセットで更新されます

特定のデータ ポイントの値で並べ替えられたすべてのオブジェクトを一覧表示する必要がある Web サーバーがあります。

クエリの例:

SELECT name, data[3916863] as weight FROM object ORDER BY weight DESC

現在、このクエリの実行には約 2.5 秒かかります。

質問: より良いアプローチはありますか? 挿入側がバックグラウンドで発生するので遅くなるのはうれしいですが、選択クエリはできるだけ速くする必要があります。とは言っても挿入にかかる時間には限界があります。

すべての値に独自の行があるルックアップ テーブルを作成することを検討しましたが、挿入/ルックアップ時間がこのアプローチによってどのように影響を受けるかはわかりません。個々の行として最大 500 万のデータ ポイントを持つ 1000 以上のレコードを入力すると思われます。遅すぎる。

現在、行の挿入には約 30 秒かかりますが、現時点では許容範囲です。

最終的には、基本的な問題に対するスケーラブルなソリューションを探していますが、今のところ、このソリューションが機能する必要があるため、このソリューションをさらにスケールアップする必要はありません。

更新: 配列の代わりに巨大なテーブルを使用することを却下したのは間違いでした。挿入時間は大幅に増加しましたが、クエリ時間はわずか数ミリ秒に短縮されました。

データムがゼロ以外で前回の更新から変更された場合にのみデータムを保存するように生成アルゴリズムを変更しています。これにより、数秒しかかからない数十万の値に挿入が削減されました。

新しいテーブル:

CREATE TABLE data
(
  object_id integer,
  data_index integer,
  value integer,
)

CREATE INDEX index_data_on_data_index
  ON data
  USING btree
  ("data_index");

新しいクエリ:

SELECT name, coalesce(value,0) as weight FROM objects LEFT OUTER JOIN data on data.object_id = objects.id AND data_index = 7731363 ORDER BY weight DESC

挿入時間: 15,000 レコード/秒

クエリ時間: 17ms

4

1 に答える 1

3

まず第一に、これには本当にリレーショナル データベースが必要ですか? 一部のデータを他のデータに関連付けていないようです。フラットファイル形式の方がずっと良いかもしれません。

第二に、あなたのインデックスdataは、あなたが示したクエリには役に立ちません。インデックスが配列内の値に基づいて構築されている間に、データム(配列内の位置) を照会しています。インデックスを削除すると、挿入が大幅に高速化されます。

他の理由 (より大きなデータ モデル、MVCC、セキュリティ) で PostgreSQL を使い続ける必要がある場合は、データ モデルとALTER COLUMN data SET TYPE bytea STORAGE external. 列は約 4 x 500 万 = 20MB であるためdata、いずれにしても行外に格納されますが、明示的に設定すると、何を持っているかが正確にわかります。

次に、マクロを使用してデータ値を「直接」取得するカスタム関数を C で作成します。PG_GETARG_BYTEA_P_SLICE()これは次のようになります (私は非常に熟練した PG C プログラマーではないので、エラーを許してください。ただし、これは途中で役立つはずです)。 :

// Function get_data_value() -- Get a 4-byte value from a bytea
// Arg 0: bytea* The data
// Arg 1: int32  The position of the element in the data, 1-based

PG_FUNCTION_INFO_V1(get_data_value);
Datum
get_data_value(PG_FUNCTION_ARGS)
{
    int32 element = PG_GETARG_INT32_P(1) - 1;     // second argument, make 0-based
    bytea *data = PG_GETARG_BYTEA_P_SLICE(0,      // first argument
                     element * sizeof(int32),     // offset into data
                     sizeof(int32));              // get just the required 4 bytes
    PG_RETURN_INT32_P((int32*)data);
}

PG_GETARG_BYTEA_P_SLICE()マクロは、ディスクからデータのスライスのみを取得するため、非常に効率的です。

docsには、カスタム C 関数を作成するサンプルがいくつかあります。

クエリは次のようになります。

SELECT name, get_data_value(data, 3916863) AS weight FROM object ORDER BY weight DESC;
于 2015-10-03T03:09:58.553 に答える