web-dev-qa-db-ja.com

GINを使用してビット文字列にインデックスを付ける

PostgreSQLを拡張して、最大1000ビットのビット文字列にインデックスを付けるようにしています。 (これらのビット文字列は高次元ベクトルの量子化によって作成されるため、各次元に対して最大4ビットが割り当てられます)。挿入はそれほど頻繁ではありませんが、検索は主に使用される操作です。検索で、ビット文字列と完全に一致するすべての行を取得したいと思います。

GIN(自分のデータ型と組み合わせて)にとっては完璧な仕事のようですが、どう思いますか?

7
navige

検索では、ビット文字列と完全に一致するすべての行を取得したいと思います。

デフォルトのタイプであるBツリーインデックスを使用します。ここにGINインデックスのケースはありません。

_bit varying_ type の場合、最大1000ビットは、ディスク上で最大133バイト(またはそれ以上)のストレージサイズになります。

_SELECT pg_column_size(repeat('1', 1000)::varbit)  -- 133
_

それほどではない。プレーンなBツリーインデックスで十分です。しかし、おそらくカラムが十分に大きいため、次のトリックによってパフォーマンスが向上します。

ビットストリング列のごく一部が、検索をいくつかのヒットに絞り込むのに十分なほど特徴的である場合、 式のインデックスmightによりパフォーマンスが向上します。小さいインデックスはRAM=に収まり、すべての処理を高速化するためです。小さなテーブルを気にしないでください。オーバーヘッドによってメリットが失われます。しかし、大きなテーブル

与えられたテーブル:

_CREATE TABLE tbl(id serial PRIMARY KEY, b_col varbit);
_

最初の10ビットで検索をいくつかのヒットに絞り込むのに十分な場合は、- 式のインデックスb_col::bit(10)を作成できます。 bin(n) にキャストすると、bitstringがnビットに切り捨てられます。

_CREATE INDEX tbl_b_col10_idx ON tbl ((b_col::bit(10)))
_

インデックス定義のキャスト演算子には、追加の括弧が必要です。見る:

次に、クエリの代わりに

_SELECT * FROM tbl WHERE b_col = '1111011110111101'::varbit; -- 16 bit
_

あなたは使うでしょう:

_SELECT *
FROM   tbl
WHERE  b_col::bit(10) = '1111011110111101'::bit(10) -- utilize index
AND    b_col = '1111011110111101'::varbit;  -- filter to exact match
_

bit(n)にキャストする場合、短い値にはright(最下位ビット)まで_0_が埋め込まれることに注意してください。

実際のアプリケーションでは、これは数百のビットで意味をなし始めます。転換点をテストします。

さらに最適化

ほとんどのインストールは8バイトのMAXALIGN(64ビットOS)( 詳細はこちら )で動作するため、8バイトを超えないデータのインデックスサイズは同じです。効果的には、行ごとに:

 4バイトのアイテムポインター
インデックスタプルヘッダー用に8バイト(またはヒープタプル用に23 + 1バイト)
?データの実際のスペース
?最も近い8バイトの倍数へのパディング

さらに、ページおよびインデックス/テーブルごとに若干のオーバーヘッドがあります。詳細 マニュアル内 または SOに関するこの関連回答内

したがって、上記のアプローチをさらに最適化できるはずです。最初の64ビット(または最後または最も特徴的で機能するもの)をbigintにキャストし、インデックスを作成しますこの表現について。

_CREATE INDEX tbl_b_col64_idx ON tbl ((b_col::bit(64)::bigint))
_

varbitbigintの間にキャストが定義されていないため、2回キャストします(b_col::bit(64)::bigint)。 SOに関するこの関連回答の詳細:

事実上、これは非常に高速で単純なハッシュ関数であり、ハッシュ値は値の範囲を検索することもできます。正確な要件に応じて、さらに一歩進んでanyIMMUTABLEハッシュ関数-md5()などを使用できます。 上記のリンクの回答の詳細。

それに伴うクエリ:

_SELECT *
FROM   tbl
WHERE  b_col::bit(64)::bigint = '1111011110111101'::bit(64)::bigint -- utilize index
AND    b_col = '1111011110111101'::varbit;  -- narrow down to exact match
_

結果のインデックスは最初の例と同じ大きさになるはずですが、次の3つの理由により、クエリはかなり高速になるはずです。

  • インデックスは通常、はるかに少ないヒットを返します(64ビットの情報と10ビットの情報)

  • Postgresは整数演算を使用できます。これは、単純な_=_操作の場合でも高速になります。 (それを確認するためのテストは行われませんでした。)

  • タイプintegerには、varbit- 5 or 8 bytes のようなオーバーヘッドはありません。 (私のインストールでは、960ビットまで5バイト、それ以上は8バイト)。
    事実上、インデックスを最小サイズに保つには、24ビットのみをvarbitインデックスにパックできます-bigintインデックスの64ビットの情報。

CLUSTER

このような場合、CLUSTERはパフォーマンスを向上させるはずです。

_CLUSTER TABLE tbl USING tbl_b_col10_idx;
_

これは1回限りの操作であり、設計の間隔で繰り返す必要があります。使用したい場合は、必ず CLUSTER のマニュアルをお読みください。または、代替 pg_repack を検討してください。詳細:

値の最初の64ビットがほとんどの場合一意である場合、インデックススキャンはほとんどの場合単一行を返すため、CLUSTERはほとんど役に立ちません。そうでない場合、CLUSTERたくさん助けます。その結果、最適化されたインデックスが少ない最初の例の効果ははるかに大きくなります。

17