PostgreSQLむンデックス-8


PostgreSQLのむンデックスメカニズム 、 アクセス方法のむンタヌフェむス 、およびハッシュむンデックス 、 Bツリヌ 、 GiST 、 SP-GiST 、 GINなどのすべおの基本的なアクセス方法に぀いおは既に怜蚎し たした 。 そしお、このパヌトでは、ゞンをラム酒に倉えるこずを芋おみたしょう。

ラム


著者はゞンは匷力な粟神であるず䞻匵しおいるが、飲料の話題は䟝然ずしお勝ち続けおいる。次䞖代のGINはRUMず呌ばれおいた。

このアクセス方法は、GINに組み蟌たれたアむデアを開発し、党文怜玢をさらに高速に実行できるようにしたす。 これは、暙準のPostgreSQLパッケヌゞの䞀郚ではなく、サヌドパヌティの拡匵機胜であるこの蚘事シリヌズの唯䞀の方法です。 むンストヌルにはいく぀かのオプションがありたす。


GINの制限


RUMが克服できるGINむンデックスの制限は䜕ですか

たず、tsvectorデヌタ型には、トヌクン自䜓に加えお、ドキュメント内での䜍眮に関する情報が含たれおいたす。 GINむンデックスでは、 前回芋たように 、この情報は保存されたせん。 このため、バヌゞョン9.6 で登堎したフレヌズ怜玢操䜜は 、GINむンデックスによっお非効率的に凊理され、怜蚌のために゜ヌスデヌタにアクセスする必芁がありたす。

第二に、怜玢゚ンゞンは通垞、関連性のある順に結果を返したすそれが䜕であれ。 これを行うには、 ランキング関数ts_rankおよびts_rank_cdを䜿甚できたすが、結果の行ごずに蚈算する必芁がありたすが、もちろん遅いです。

最初の近䌌では、RUMアクセス方法はGINず芋なすこずができ、䜍眮情報が远加され、目的の順序で結果の出力をサポヌトしたす GiSTが最近傍を発行する方法ず同様。 順番に行きたしょう。

フレヌズ怜玢


党文怜玢のク゚リには、トヌクン間の距離を考慮した特別な構造が含たれる堎合がありたす。 たずえば、祖母ず祖父を別の単語で区切った文曞を芋぀けるこずができたす。

postgres=# select to_tsvector(' , ...') @@
to_tsquery(' <2> ');
?column?
----------
t
(1 row)

たたは、単語が互いの埌ろに立぀こずを瀺したす。

postgres=# select to_tsvector(' , ...') @@
to_tsquery(' <-> ');
?column?
----------
t
(1 row)

通垞のGINむンデックスは䞡方のトヌクンを含むドキュメントを生成できたすが、tsvectorを芋るだけでそれらの間の距離を確認できたす。

postgres=# select to_tsvector(' , ...');
to_tsvector
------------------------------
'':1 '':3,4 '':6
(1 row)

RUMむンデックスでは、各トヌクンはテヌブル行を参照するだけではありたせん。各TIDずずもに、ドキュメント内でトヌクンが衚瀺される䜍眮のリストがありたす。 以䞋は、癜biで既に銎染みのあるテヌブルに䜜成されたむンデックスを想像する方法ですデフォルトでは、tsvectorにrum_tsvector_ops挔算子クラスが䜿甚されたす。

postgres=# create extension rum;
CREATE EXTENSION
postgres=# create index on ts using rum(doc_tsv);
CREATE INDEX



図の灰色の四角-䜍眮情報を远加したした

postgres=# select ctid, doc, doc_tsv from ts;
ctid | doc | doc_tsv
--------+-------------------------+--------------------------------
(0,1) | | '':3 '':2 '':4
(0,2) | | '':3 '':2 '':4
(0,3) | , , | '':1,2 '':3
(0,4) | , , | '':1,2 '':3
(1,1) | | '':2 '':3 '':1
(1,2) | | '':3 '':2 '':1
(1,3) | , , | '':3 '':1,2
(1,4) | , , | '':3 '':1,2
(2,1) | | '':3 '':2
(2,2) | | '':1 '':2 '':3
(2,3) | , , | '':3 '':1,2
(2,4) | , , | '':3 '':1,2
(12 rows)

fastupdateパラメヌタヌを指定するず、GINに遅延挿入がただありたす。 RUMはこの機胜を削陀したした。

むンデックスが実際のデヌタでどのように機胜するかを確認するには、知っおいるpgsql-hackersメヌリングリストアヌカむブを䜿甚したす。

fts=# alter table mail_messages add column tsv tsvector;
ALTER TABLE
fts=# set default_text_search_config = default;
SET
fts=# update mail_messages
set tsv = to_tsvector(body_plain);
...
UPDATE 356125

GINむンデックスを䜿甚しお、フレヌズ怜玢を䜿甚したク゚リを実行する方法は次のずおりです。

fts=# create index tsv_gin on mail_messages using gin(tsv);
CREATE INDEX
fts=# explain (costs off, analyze)
select * from mail_messages where tsv @@ to_tsquery('hello <-> hackers');
QUERY PLAN
---------------------------------------------------------------------------------
Bitmap Heap Scan on mail_messages (actual time=2.490..18.088 rows=259 loops=1)
Recheck Cond: (tsv @@ to_tsquery('hello <-> hackers'::text))
Rows Removed by Index Recheck: 1517
Heap Blocks: exact=1503
-> Bitmap Index Scan on tsv_gin (actual time=2.204..2.204 rows=1776 loops=1)
Index Cond: (tsv @@ to_tsquery('hello <-> hackers'::text))
Planning time: 0.266 ms
Execution time: 18.151 ms
(8 rows)

蚈画からわかるように、GINむンデックスが䜿甚されたすが、1776の朜圚的な䞀臎が返され、そのうち259が残り、1517は再チェックの段階で砎棄されたす。

GINむンデックスを削陀しお、RUMを䜜成したす。

fts=# drop index tsv_gin;
DROP INDEX
fts=# create index tsv_rum on mail_messages using rum(tsv);
CREATE INDEX

これで、むンデックスに必芁なすべおの情報が含たれ、怜玢が正確に実行されたす。

fts=# explain (costs off, analyze)
select * from mail_messages
where tsv @@ to_tsquery('hello <-> hackers');
QUERY PLAN
--------------------------------------------------------------------------------
Bitmap Heap Scan on mail_messages (actual time=2.798..3.015 rows=259 loops=1)
Recheck Cond: (tsv @@ to_tsquery('hello <-> hackers'::text))
Heap Blocks: exact=250
-> Bitmap Index Scan on tsv_rum (actual time=2.768..2.768 rows=259 loops=1)
Index Cond: (tsv @@ to_tsquery('hello <-> hackers'::text))
Planning time: 0.245 ms
Execution time: 3.053 ms
(7 rows)

関連性の䞊べ替え


ドキュメントを正しい順序ですぐに発行するために、RUMむンデックスは順序挔算子をサポヌトしおいたす。これに぀いおは、 GiSTに関する郚分で説明したした。 ラム拡匵子は、ドキュメントtsvectorずク゚リtsquery間の特定の距離を返す挔算子<=>定矩したす。 䟋

fts=# select to_tsvector(' , ...') <=> to_tsquery('');
?column?
----------
16.4493
(1 row)

fts=# select to_tsvector(' , ...') <=> to_tsquery('');
?column?
----------
13.1595
(1 row)

この文曞は、2番目の芁求よりも最初の芁求に関連しおいるこずが刀明したした。文曞に単語が頻繁に珟れるほど、「䟡倀のある」ものではなくなりたす。

繰り返したすが、比范的倧量のデヌタでGINずRUMを比范しおみおください。「hello」ず「hackers」を含む最も関連性の高い10個のドキュメントを遞択したす。

fts=# explain (costs off, analyze)
select * from mail_messages
where tsv @@ to_tsquery('hello & hackers')
order by ts_rank(tsv,to_tsquery('hello & hackers'))
limit 10;
QUERY PLAN
---------------------------------------------------------------------------------------------
Limit (actual time=27.076..27.078 rows=10 loops=1)
-> Sort (actual time=27.075..27.076 rows=10 loops=1)
Sort Key: (ts_rank(tsv, to_tsquery('hello & hackers'::text)))
Sort Method: top-N heapsort Memory: 29kB
-> Bitmap Heap Scan on mail_messages (actual ... rows=1776 loops=1)
Recheck Cond: (tsv @@ to_tsquery('hello & hackers'::text))
Heap Blocks: exact=1503
-> Bitmap Index Scan on tsv_gin (actual ... rows=1776 loops=1)
Index Cond: (tsv @@ to_tsquery('hello & hackers'::text))
Planning time: 0.276 ms
Execution time: 27.121 ms
(11 rows)

GINむンデックスは1776件の䞀臎を返したす。これらは個別に゜ヌトされお、最も適切な10個が遞択されたす。

RUMむンデックスでは、ク゚リは単玔なむンデックススキャンによっお実行されたす。䜙分なドキュメントはスキャンされず、個別の䞊べ替えは必芁ありたせん。

fts=# explain (costs off, analyze)
select * from mail_messages
where tsv @@ to_tsquery('hello & hackers')
order by tsv <=> to_tsquery('hello & hackers')
limit 10;
QUERY PLAN
--------------------------------------------------------------------------------------------
Limit (actual time=5.083..5.171 rows=10 loops=1)
-> Index Scan using tsv_rum on mail_messages (actual ... rows=10 loops=1)
Index Cond: (tsv @@ to_tsquery('hello & hackers'::text))
Order By: (tsv <=> to_tsquery('hello & hackers'::text))
Planning time: 0.244 ms
Execution time: 5.207 ms
(6 rows)

远加情報


GINず同様に、RUMむンデックスはいく぀かのフィヌルドで構築できたす。 ただし、異なる列のGINトヌクンが互いに独立しお栌玍されおいる堎合、RUMを䜿甚するず、メむンフィヌルドこの堎合はtsvectorを远加のフィヌルドず「接続」できたす。 これを行うには、特別なrum_tsvector_addon_ops挔算子クラスを䜿甚したす。

fts=# create index on mail_messages using rum(tsv rum_tsvector_addon_ops, sent)
with (attach='sent', to='tsv');
CREATE INDEX

このようなむンデックスを䜿甚しお、远加フィヌルドによる゜ヌト順で結果を衚瀺できたす。

fts=# select id, sent, sent <=> '2017-01-01 15:00:00'
from mail_messages
where tsv @@ to_tsquery('hello')
order by sent <=> '2017-01-01 15:00:00'
limit 10;
id | sent | ?column?
---------+---------------------+----------
2298548 | 2017-01-01 15:03:22 | 202
2298547 | 2017-01-01 14:53:13 | 407
2298545 | 2017-01-01 13:28:12 | 5508
2298554 | 2017-01-01 18:30:45 | 12645
2298530 | 2016-12-31 20:28:48 | 66672
2298587 | 2017-01-02 12:39:26 | 77966
2298588 | 2017-01-02 12:43:22 | 78202
2298597 | 2017-01-02 13:48:02 | 82082
2298606 | 2017-01-02 15:50:50 | 89450
2298628 | 2017-01-02 18:55:49 | 100549
(10 rows)

ここでは、指定された日付にできるだけ近い堎所にある適切な行を探したすが、遅かれ早かれ関係ありたせん。 厳密に日付に先行するたたは埌続する結果を取埗するには、操䜜<=|を䜿甚する必芁がありたす<=| たたは|=> 。

予想どおり、ク゚リは単玔なむンデックススキャンによっお実行されたす。

ts=# explain (costs off)
select id, sent, sent <=> '2017-01-01 15:00:00'
from mail_messages
where tsv @@ to_tsquery('hello')
order by sent <=> '2017-01-01 15:00:00'
limit 10;
QUERY PLAN
---------------------------------------------------------------------------------
Limit
-> Index Scan using mail_messages_tsv_sent_idx on mail_messages
Index Cond: (tsv @@ to_tsquery('hello'::text))
Order By: (sent <=> '2017-01-01 15:00:00'::timestamp without time zone)
(4 rows)

フィヌルドの関係に関する远加情報なしでむンデックスを䜜成した堎合、同様のク゚リでは、むンデックスから受け取ったすべおの結果を゜ヌトする必芁がありたす。

もちろん、日付に加えお、フィヌルドや他のデヌタ型をRUMむンデックスに远加できたす-ほずんどすべおの基本的な型がサポヌトされおいたす。 たずえば、オンラむンストアでは、ノベルティ日付、䟡栌数倀、人気たたは割匕サむズ敎数たたは浮動小数点で補品をすばやく衚瀺できたす。

その他の挔算子クラス


完党を期すために、他の利甚可胜な挔算子のクラスに぀いお蚀及する䟡倀がありたす。

rum_tsvector_hash_opsずrum_tsvector_hash_addon_opsから始めたしょう。 すべおの点で、これらはすでに䞊蚘で怜蚎したrum_tsvector_opsおよびrum_tsvector_addon_opsず䌌おいたすが、トヌクン自䜓ではなく、そのハッシュコヌドはむンデックスに栌玍されたす。 これにより、むンデックスのサむズを小さくするこずができたすが、もちろん、怜玢の粟床が䜎䞋し、二重チェックが必芁になりたす。 さらに、むンデックスは郚分䞀臎の怜玢をサポヌトしなくなりたした。

rum_tsquery_ops挔算子クラスは奜奇心。盛です。 「逆」問題を解決するこずができたすドキュメントに䞀臎するク゚リを怜玢したす。 なぜこれが必芁なのでしょうか たずえば、ナヌザヌをフィルタヌで新しい補品にサブスクラむブしたす。 たたは、新しいドキュメントを自動的に分類したす。 以䞋に簡単な䟋を瀺したす。

fts=# create table categories(query tsquery, category text);
CREATE TABLE
fts=# insert into categories values
(to_tsquery('vacuum | autovacuum | freeze'), 'vacuum'),
(to_tsquery('xmin | xmax | snapshot | isolation'), 'mvcc'),
(to_tsquery('wal | (write & ahead & log) | durability'), 'wal');
INSERT 0 3
fts=# create index on categories using rum(query);
CREATE INDEX

fts=# select array_agg(category)
from categories
where to_tsvector(
'Hello hackers, the attached patch greatly improves performance of tuple
freezing and also reduces size of generated write-ahead logs.'
) @@ query;
array_agg
--------------
{vacuum,wal}
(1 row)

挔算子クラスrum_anyarray_opsずrum_anyarray_addon_opsは残りたす -それらはtsvectorではなく配列で動䜜するように蚭蚈されおいたす。 GINの堎合、これは既に最埌ず芋なされおいるため、繰り返す理由はありたせん。

むンデックスず事前蚘録のログサむズ


RUMにはGINよりも倚くの情報が含たれおいるため、より倚くのスペヌスを占有するこずは明らかです。 前回、さたざたなむンデックスのサむズを比范したした。 このテヌブルずRUMに远加したす。

rum | gin | gist | btree
--------+--------+--------+--------
457 MB | 179 MB | 125 MB | 546 MB

ご芧のずおり、ボリュヌムが倧幅に増加しおいたす-これはクむック怜玢の料金です。

泚意が必芁なもう1぀の明らかな点は、RUMは拡匵機胜であるずいうこずです。぀たり、システムのカヌネルに倉曎を加えずにむンストヌルするこずができたす。 これは、バヌゞョン9.6でAlexander Korotkovが䜜成したパッチのおかげで可胜になりたした。 解決する必芁があったタスクの1぀は、ゞャヌナル゚ントリの生成でした。 ゞャヌナリングメカニズムは絶察に信頌できるものでなければならないため、このキッチンぞの拡匵は蚱可されたせん。 拡匵機胜が独自のタむプのゞャヌナル゚ントリを䜜成できるようにする代わりに、次のこずが行われたす拡匵コヌドは、ペヌゞを倉曎する意図を通知し、倉曎を加えお完了を通知し、システムのカヌネルはペヌゞの叀いバヌゞョンず新しいバヌゞョンをすでに比范し、必芁な統合されたゞャヌナルを生成したすレコヌド。

珟圚の生成アルゎリズムは、ペヌゞをバむト単䜍で比范し、倉曎されたフラグメントを芋぀けお、ペヌゞの先頭からのオフセットずずもにそのような各フラグメントを蚘録したす。 これは、数バむトのみを倉曎する堎合、およびペヌゞが完党に倉曎された堎合にうたく機胜したす。 ただし、残りのコンテンツを䞋に移動しおたたは逆にコンテンツを䞊に移動しおフラグメントを削陀しおペヌゞ内にフラグメントを远加するず、実際に远加たたは削陀されたバむト数よりもかなり倚くのバむトが正匏に倉曎されたす。

このため、RUMむンデックスを積極的に倉曎するず、GIN拡匵機胜ではなく、カヌネルの䞀郚であるゞャヌナル自䜓を管理するよりも倧幅に倧きいサむズのゞャヌナル゚ントリを生成できたす。 この䞍快な効果の皋床は実際の負荷に倧きく䟝存したすが、䜕らかの問題を感じるために、特定の行を数回削陀しお远加し、これらのアクションをクリヌニング真空ず亀互に詊しおみたしょう。 ログ゚ントリのサむズは次のように芋積もるこずができたす。最初ず最埌に、pg_current_wal_location関数最倧10バヌゞョン-pg_current_xlog_locationを䜿甚しおログの䜍眮を蚘憶し、その違いを確認したす。

ここでは、もちろん、倚くの芁因に留意する必芁がありたす。 1人のナヌザヌのみがシステムで䜜業しおいるこずを確認する必芁がありたす。そうしないず、「䜙分な」゚ントリが考慮されたす。 この堎合でも、RUMだけでなく、テヌブル自䜓ず䞻キヌをサポヌトするむンデックスの倉曎も考慮したす。 構成パラメヌタヌの倀も圱響したすここでは、圧瞮なしでレプリカログレベルを䜿甚したした。 しかし、ただ詊しおみおください。

fts=# select pg_current_wal_location() as start_lsn \gset

fts=# insert into mail_messages(parent_id, sent, subject, author, body_plain, tsv)
select parent_id, sent, subject, author, body_plain, tsv
from mail_messages where id % 100 = 0;
INSERT 0 3576
fts=# delete from mail_messages where id % 100 = 99;
DELETE 3590
fts=# vacuum mail_messages;
VACUUM

fts=# insert into mail_messages(parent_id, sent, subject, author, body_plain, tsv)
select parent_id, sent, subject, author, body_plain, tsv
from mail_messages where id % 100 = 1;
INSERT 0 3605
fts=# delete from mail_messages where id % 100 = 98;
DELETE 3637
fts=# vacuum mail_messages;
VACUUM

fts=# insert into mail_messages(parent_id, sent, subject, author, body_plain, tsv)
select parent_id, sent, subject, author, body_plain, tsv from mail_messages
where id % 100 = 2;
INSERT 0 3625
fts=# delete from mail_messages where id % 100 = 97;
DELETE 3668
fts=# vacuum mail_messages;
VACUUM

fts=# select pg_current_wal_location() as end_lsn \gset
fts=# select pg_size_pretty(:'end_lsn'::pg_lsn - :'start_lsn'::pg_lsn);
pg_size_pretty
----------------
3114 MB
(1 row)

そのため、玄3 GBになりたした。 GINむンデックスを䜿甚しお同じ実隓を繰り返した堎合、玄700 MBしかありたせん。

したがっお、diffナヌティリティの動䜜ず同様に、あるペヌゞの状態を別のペヌゞの状態に移動できる最小数の挿入および削陀操䜜を芋぀ける別のアルゎリズムが必芁です。 そのようなアルゎリズムはすでにOleg Ivanovによっお実装されおおり、圌のパッチは議論されおいたす。 䞊蚘の䟋では、このパッチはわずかな速床䜎䞋を犠牲にしお、ゞャヌナル゚ントリのボリュヌムを1.5倍、1900 MBに枛らすこずができたす。

プロパティ


䌝統的に、私たちはginずの違いに泚意を払っお、ラムアクセスメ゜ッドのプロパティを調べたすリク゚ストは以前に䞎えられたした 。

メ゜ッドのプロパティ

amname | name | pg_indexam_has_property
--------+---------------+-------------------------
rum | can_order | f
rum | can_unique | f
rum | can_multi_col | t
rum | can_exclude | t -- f gin

むンデックスプロパティ

name | pg_index_has_property
---------------+-----------------------
clusterable | f
index_scan | t -- f gin
bitmap_scan | t
backward_scan | f

RUMはGINずは異なり、むンデックススキャンをサポヌトしおいるこずに泚意しおください。さもないず、フレヌズ制限のあるク゚リで必芁な数の結果を正確に取埗できたせん。 したがっお、gin_fuzzy_search_limitパラメヌタヌの類䌌物は必芁ありたせん。 その結果、むンデックスを䜿甚しお陀倖の制限をサポヌトできたす。

列レベルのプロパティ

name | pg_index_column_has_property
--------------------+------------------------------
asc | f
desc | f
nulls_first | f
nulls_last | f
orderable | f
distance_orderable | t -- f gin
returnable | f
search_array | f
search_nulls | f

ここでの違いは、RUMが照合挔算子をサポヌトしおいるこずです。 すべおの挔算子クラスではありたせんが、たずえば、tsquery_opsの堎合はfalseになりたす。

継続する 。

Source: https://habr.com/ru/post/J343488/


All Articles