- シーケンシャルサーチで検索が実行されているか確認
- ケース1「PGroongaのインデックスの設定し忘れ」
- ケース2「演算子がサポートしていない型のカラムを対象に検索している」
- ケース3「演算子クラスの指定が間違っている。あるいは指定していない」
- ケース4「PGroongaのインデックスで指定しているカラムの順序と検索クエリーで記載しているカラムの順序が異なる」
- まとめ
こんにちは、PGroongaの開発やサポートをしている堀本です。
この記事では、PGroongaのインデックスを設定しているにも関わらず、 シーケンシャルサーチで検索が実行されてしまう時に確認すべきポイントを紹介します。
PGroongaはPostgreSQLで高速に全文検索するための拡張ですが、 PGroongaのインデックスを設定しているのに、検索が速くならないことがあります。
色々な原因が考えられるのですが、よくあるケースとしては PostgreSQLがPGroongaのインデックスを使用せず、シーケンシャルサーチで検索が実行されていているケースです。
以下では、なぜシーケンシャルサーチが選択されるのか、PGroongaのインデックスを選択して 検索を実行するにはどうしたらいいかを解説します。
最初に、シーケンシャルサーチで検索が実行されているかどうかを確認します。 その後に、シーケンシャルサーチで検索が実行される原因とその解決方法を4つ記載します。
シーケンシャルサーチで検索が実行されているか確認
では、まず該当のクエリーがシーケンシャルサーチになっているかどうかを確認しましょう。
確認はEXPLAIN ANALYZE
を使用して行います。
ここでは、以下のようなテーブル構造を例にします。
シーケンシャルサーチで検索している時のEXPLAIN ANALYZE
の結果が欲しいので、
以下の例では、必ずシーケンシャルサーチになるように何もインデックスを設定していませんし、主キーも設定していません。
CREATE TABLE memos (
title text,
content text
);
INSERT INTO memos VALUES ('PostgreSQL', 'PostgreSQLはRDBMSです。');
INSERT INTO memos VALUES ('Groonga', 'Groongaは超高速な全文検索エンジンです。');
INSERT INTO memos VALUES ('PGroonga', 'PGroongaはPostgreSQLで超高速な全文検索を実現する拡張です。');
該当のクエリーは以下とします。
SELECT * FROM memos WHERE content &@~ 'PostgreSQL';
では、該当のクエリーがシーケンシャルサーチかどうかを確認しましょう。
確認には、前述の通りEXPLAIN ANALYZE
を使います。
EXPLAIN ANALYZE SELECT * FROM memos WHERE content &@~ 'PostgreSQL';
-- QUERY PLAN
-- -----------------------------------------------------------------------------------------------------
-- Seq Scan on memos (cost=0.00..678.80 rows=1 width=64) (actual time=2.803..4.664 rows=2 loops=1)
-- Filter: (content &@~ 'PostgreSQL'::text)
-- Rows Removed by Filter: 1
-- Planning Time: 0.113 ms
-- Execution Time: 4.731 ms
-- (5 rows)
結果は上記の通りです。
シーケンシャルサーチの場合は上記のようにSeq Scan
と出力されます。
ここのSeq Scan
を以下のようにIndex Scan using #{PGroongaのインデックス名}
とするのが今回のゴールです。
EXPLAIN ANALYZE SELECT * FROM memos WHERE content &@~ 'PostgreSQL';
-- QUERY PLAN
-- ------------------------------------------------------------------------------------------------------------------------------
-- Index Scan using pgrn_content_index on memos (cost=0.00..4.02 rows=1 width=64) (actual time=0.778..0.782 rows=2 loops=1)
-- Index Cond: (content &@~ 'PostgreSQL'::text)
-- Planning Time: 0.835 ms
-- Execution Time: 1.002 ms
-- (4 rows)
シーケンシャルサーチかどうかの確認方法がわかったので、具体的に問題のケースを解決していきましょう。
ケース1「PGroongaのインデックスの設定し忘れ」
まずは、一番簡単なケースから紹介します。
当然ですが、PGroongaのインデックスを設定していなければ PGroongaのインデックスを使った検索はできません。
そんな簡単な見落としをするかな?と思う方も多いと思いますが、非常に多くのテーブル、インデックスが存在するデータベースを考えてみてください。 数十、数百あるテーブル、カラム、インデックスがあるケースでは、うっかりインデックスを設定し忘れるということは起こり得ます。
このケースは、確認が非常に簡単ですぐ終わるので、最初に確認する点として適当かと思います。
チェックポイント:テーブルの構成情報を確認する
このケースのチェックポイントは、テーブルの構成情報を確認するということです。 具体的には、以下のように確認します。
- psqlで該当のDBへ接続
- テーブルの定義を確認
- 2.の中にPGroongaのインデックスが存在するか確認
1.の操作は特に難しいことは無いので詳細は省略します。
2.には、\d #{テーブル名}
が使えます。#{テーブル名}
の部分はPGroongaのインデックスを使って検索したいカラムを含むテーブルの名前に置き換えます。
3.で2.の内容を確認します。具体的にどこを確認するのかを以下に記載します。
まず、以下のmemos
テーブルのcontent
カラムにインデックスを設定しているとします。
CREATE TABLE memos (
title text PRIMARY KEY,
content text
);
CREATE INDEX pgrn_content_index ON memos USING pgroonga (content);
次に、memos
テーブルのcontent
カラムにインデックスが設定されているかどうかを\d
コマンドで確認します。
結果は以下のようになります。
\d memos
-- Table "public.memos"
-- Column | Type | Collation | Nullable | Default
-- ---------+------+-----------+----------+---------
-- title | text | | not null |
-- content | text | | |
-- Indexes:
-- "memos_pkey" PRIMARY KEY, btree (title)
-- "pgrn_content_index" pgroonga (content)
Indexes
の項目に注目してください。
ここには、memos
テーブルに設定されている全てのIndexが#{Index名} #{インデックスの種類} (#{インデックス対象のカラム名})
の形式で表示されます。
ここで、PGroongaのインデックスがcontent
カラムに設定されているかを確認します。
今回の例では、pgrn_content_index
をcontent
カラム設定したので、Indexes
に"pgrn_content_index" pgroonga (content)
が表示されていることを確認します。
前述の通り、Indexes
は#{Index名} #{インデックスの種類} (#{インデックス対象のカラム名})
という形式なので、
"pgrn_content_index" pgroonga (content)
という表示で、PGroongaのインデックスがcontent
カラムに設定されていることがわかります。
もし、pgrn_content_index
を設定し忘れてしまった場合は、以下のようにIndexes
にpgrn_content_index
が表示されません。
\d memos
-- Table "public.memos"
-- Column | Type | Collation | Nullable | Default
-- ---------+------+-----------+----------+---------
-- title | text | | not null |
-- content | text | | |
-- Indexes:
-- "memos_pkey" PRIMARY KEY, btree (title)
このように\d
コマンドを使ってインデックスの設定し忘れを確認します。
インデックスがちゃんと設定されていることが確認できました。
この段階で、インデックスを使用した検索ができていればチェックはここで完了です。 インデックスがちゃんと設定されていても、インデックスを使った検索にならない場合は次のケースに進んでください。
ケース2「演算子がサポートしていない型のカラムを対象に検索している」
次は検索対象のカラムの型と検索に使用している演算子がサポートしている型が一致していないケースです。
具体例を交えて説明します。 以下のようなテーブル、インデックス、クエリーを考えます。
CREATE TABLE memos (
title text PRIMARY KEY,
content text,
tags text[]
);
INSERT INTO memos VALUES ('PostgreSQL', 'PostgreSQLはRDBMSです。', ARRAY['PostgreSQL']);
INSERT INTO memos VALUES ('Groonga', 'Groongaは超高速な全文検索エンジンです。', ARRAY['Groonga']);
INSERT INTO memos VALUES ('PGroonga', 'PGroongaはPostgreSQLで超高速な全文検索を実現する拡張です。', ARRAY['PGroonga', 'PostgreSQL']);
CREATE INDEX pgrn_tags_index ON memos USING pgroonga (tags);
SET enable_seqscan = off;
EXPLAIN ANALYZE VERBOSE SELECT * FROM memos WHERE tags &> 'PostgreSQL';
-- QUERY PLAN
-- --------------------------------------------------------------------------------------------------------------------------------
-- Seq Scan on public.memos (cost=10000000000.00..10000000003.28 rows=1 width=96) (actual time=23.095..23.098 rows=2 loops=1)
-- Output: title, content, tags
-- Filter: ((memos.tags)::character varying[] &> 'PostgreSQL'::character varying)
-- Rows Removed by Filter: 1
-- Planning Time: 0.084 ms
-- JIT:
-- Functions: 2
-- Options: Inlining true, Optimization true, Expressions true, Deforming true
-- Timing: Generation 0.330 ms, Inlining 9.505 ms, Optimization 8.461 ms, Emission 5.106 ms, Total 23.403 ms
-- Execution Time: 23.473 ms
-- (10 rows)
上の例を実行するとインデックスを使った検索ではなく、シーケンシャルサーチになります。 これをインデックスを使った検索をするように変更していきます。
チェックポイント:演算子がサポートしているデータ型を確認する
まず、上の例の内容を確認しましょう。
この例の検索対象のカラムはtags
でtext[]
型です。
検索に使用している演算子は&>
で、これは検索対象の配列型のカラム内に指定したキーワードが含まれているかどうかをチェックします。
次に、text[]
型をサポートしている演算子に&>
があるかを確認します。
どの演算子がどの型をサポートしているかは、PGroongaの公式ドキュメントのリファレンスマニュアルに記載があります。
「PGroongaの公式ドキュメントのリファレンスマニュアル」で&>
を検索します。
すると、「varchar[]
用」というセクションに&>
が記載されていることがわかります。
つまり、&>
はvarchar[]
型のカラムをサポートしていますが、それ以外の型はサポートしていません。
再び上の例を見てみましょう。
検索対象のtags
カラムはtext[]
型なので、&>
はサポートしていない型です。
このように、検索対象のカラムの型を検索に使う演算子がサポートしていない場合、インデックスは使用されません。
このケースでは、インデックス作成時と検索時にtags
をvarchar[]
にキャストすることでインデックスが使われるようになります。
では、実際に見てみましょう。
DROP INDEX pgrn_tags_index;
CREATE INDEX pgrn_tags_index ON memos USING pgroonga ((tags::varchar[]));
EXPLAIN ANALYZE VERBOSE SELECT * FROM memos WHERE tags::varchar[] &> 'PostgreSQL';
-- QUERY PLAN
-- ----------------------------------------------------------------------------------------------------------------------------------
-- Index Scan using pgrn_tags_index on public.memos (cost=0.00..4.01 rows=1 width=96) (actual time=0.195..0.195 rows=0 loops=1)
-- Output: title, content, tags
-- Index Cond: ((memos.tags)::character varying[] &> 'PostgreSQL'::character varying)
-- Planning Time: 0.047 ms
-- Execution Time: 0.221 ms
-- (5 rows)
無事にインデックスを使った検索ができるようになりました。
この段階で、インデックスを使用した検索ができていればチェックはここで完了です。 演算子がサポートしている型のカラムを検索をしている場合でも、インデックスを使った検索にならない場合は次のケースに進んでください。
ケース3「演算子クラスの指定が間違っている。あるいは指定していない」
次は、検索対象のカラムの型に対応した演算子クラスを指定していない場合です。 演算子クラスについて説明すると長くなってしまうので、ここでは説明を割愛します。演算子クラスが何かわからなくても確認することはできます。
ここでも具体例を交えて説明します。 以下のようなテーブル、インデックス、クエリーを考えます。
CREATE TABLE memos (
title text PRIMARY KEY,
content text
);
INSERT INTO memos VALUES ('PostgreSQL', 'PostgreSQLはRDBMSです。');
INSERT INTO memos VALUES ('Groonga', 'Groongaは超高速な全文検索エンジンです。');
INSERT INTO memos VALUES ('PGroonga', 'PGroongaはPostgreSQLで超高速な全文検索を実現する拡張です。');
CREATE INDEX pgrn_tags_index ON memos USING pgroonga (content);
SET enable_seqscan = off;
EXPLAIN ANALYZE VERBOSE SELECT * FROM memos WHERE content &^ 'Postgre';
-- QUERY PLAN
-- --------------------------------------------------------------------------------------------------------------------------------
-- Seq Scan on public.memos (cost=10000000000.00..10000000003.28 rows=1 width=64) (actual time=17.357..17.361 rows=1 loops=1)
-- Output: title, content
-- Filter: (memos.content &^ 'Postgre'::text)
-- Rows Removed by Filter: 2
-- Planning Time: 0.198 ms
-- JIT:
-- Functions: 2
-- Options: Inlining true, Optimization true, Expressions true, Deforming true
-- Timing: Generation 0.290 ms, Inlining 7.746 ms, Optimization 5.999 ms, Emission 3.593 ms, Total 17.628 ms
-- Execution Time: 17.705 ms
-- (10 rows)
こちらの例も実行するとインデックスを使った検索ではなく、シーケンシャルサーチになります。 これをインデックスを使った検索をするように変更していきます。
チェックポイント:演算子クラスの指定を確認する
まず、上の例の内容を確認しましょう。
この例の検索対象のカラムはcontent
でtext
型です。
検索に使用している演算子は&^
で、これは前方一致検索を行う演算子です。
再びPGroongaの公式ドキュメントのリファレンスマニュアルを参照します。
「PGroongaの公式ドキュメントのリファレンスマニュアル」の「text
用」のセクションから&^
を探してください。
「pgroonga_text_term_search_ops_v2
演算子クラス」の配下に見つかると思います。
これは、&^
をtext
型のカラムに使う場合は、pgroonga_text_term_search_ops_v2
演算子クラスを指定する必要があることを示しています。
つまり、以下のようにします。
DROP INDEX pgrn_tags_index;
CREATE INDEX pgrn_tags_index ON memos USING pgroonga (content pgroonga_text_term_search_ops_v2);
EXPLAIN ANALYZE VERBOSE SELECT * FROM memos WHERE content &^ 'Postgre';
-- QUERY PLAN
-- ----------------------------------------------------------------------------------------------------------------------------------
-- Index Scan using pgrn_tags_index on public.memos (cost=0.00..4.01 rows=1 width=64) (actual time=0.633..0.636 rows=1 loops=1)
-- Output: title, content
-- Index Cond: (memos.content &^ 'Postgre'::text)
-- Planning Time: 0.798 ms
-- Execution Time: 0.830 ms
-- (5 rows)
ポイントは、CREATE INDEX
のUSING pgroonga (content pgroonga_text_term_search_ops_v2)
の部分です。
上の例のように、演算子クラスを指定する必要がある組み合わせの場合は、pgroonga (インデックスを設定するカラム 演算子クラス)
とします。
無事にインデックスを使った検索ができるようになりました。
この段階で、インデックスを使用した検索ができていればチェックはここで完了です。 演算子クラスの指定が正しくても、インデックスを使った検索にならない場合は次のケースに進んでください。
ケース4「PGroongaのインデックスで指定しているカラムの順序と検索クエリーで記載しているカラムの順序が異なる」
このケースは、以下のように検索対象のカラムが複数あってARRAY[]
を使って複数のカラムを一度にまとめて検索している場合に確認するポイントです。
具体的には以下のケースです。
以下のケースでは、title
カラムとcontent
カラムにGroonga
またはPostgreSQL
が含まれているレコードを検索しています。
CREATE TABLE memos (
title text,
content text
);
CREATE INDEX pgroonga_memos_index
ON memos
USING pgroonga ((ARRAY[title, content]));
INSERT INTO memos VALUES ('PostgreSQL', 'PostgreSQLはリレーショナル・データベース管理システムです。');
INSERT INTO memos VALUES ('Groonga', 'Groongaは日本語対応の高速な全文検索エンジンです。');
INSERT INTO memos VALUES ('PGroonga', 'PGroongaはインデックスとしてGroongaを使うためのPostgreSQLの拡張機能です。');
INSERT INTO memos VALUES ('コマンドライン', 'groongaコマンドがあります。');
SET enable_seqscan = off;
EXPLAIN ANALYZE VERBOSE
SELECT *
FROM memos
WHERE ARRAY[content, title] &@~ 'Groonga OR PostgreSQL';
-- QUERY PLAN
-- -----------------------------------------------------------------------------------------------------------------------------
-- Seq Scan on public.memos (cost=10000000000.00..10000000678.80 rows=1 width=64) (actual time=39.162..39.583 rows=4 loops=1)
-- Output: title, content
-- Filter: (ARRAY[memos.content, memos.title] &@~ 'Groonga OR PostgreSQL'::text)
-- Planning Time: 0.109 ms
-- JIT:
-- Functions: 2
-- Options: Inlining true, Optimization true, Expressions true, Deforming true
-- Timing: Generation 0.081 ms, Inlining 25.679 ms, Optimization 7.429 ms, Emission 4.475 ms, Total 37.664 ms
-- Execution Time: 58.043 ms
-- (9 rows)
上記の例もインデックスを使った検索ではなく、シーケンシャルサーチになっています。
チェックポイント:カラムの順序を確認する
確認するポイントは、以下の2点です。
CREATE INDEX
でインデックス対象の式として指定しているARRAY[title, content]
WHERE
句のARRAY[content, title]
上記のように並べるとわかるのですが、CREATE INDEX
で指定しているARRAY[title, content]
とWHERE
句のARRAY[content, title]
ではARRAY[]
内のカラムの順序が異なります。
上記のように、CREATE INDEX
とWHERE
句でARRAY[]
に指定したカラムの順序が異なるとインデックスを使用しません。
ということは、順序を揃えればインデックスを使った検索をします。 実際に見てみましょう。
EXPLAIN ANALYZE VERBOSE
SELECT *
FROM memos
WHERE ARRAY[title, content] &@~ 'Groonga OR PostgreSQL';
-- QUERY PLAN
-- ------------------------------------------------------------------------------------------------------------------------------------
-- Index Scan using pgroonga_memos_index on public.memos (cost=0.00..4.01 rows=1 width=64) (actual time=1.241..1.247 rows=4 loops=1)
-- Output: title, content
-- Index Cond: (ARRAY[memos.title, memos.content] &@~ 'Groonga OR PostgreSQL'::text)
-- Planning Time: 0.160 ms
-- Execution Time: 1.508 ms
-- (5 rows)
CREATE INDEX
で指定しているARRAY[]
とWHERE
句のARRAY[]
に注目してください。
こちらの例では、どちらもARRAY[title, content]
でARRAY[]
内のカラムの順序が同じになっています。
上記の通り、ARRAY[]
内のカラムの順序が同じであればインデックスを使って検索をするようになりました。
この段階で、インデックスを使用した検索ができていればチェックはここで完了です。 カラムの順序が正しくても、インデックスを使った検索にならない場合はPGroongaの問題の可能性があるので、PGroongaのIsuuesやPGroongaのDiscussionsに報告してください。(日本語で報告いただいても大丈夫です。)
まとめ
今回は、PostgreSQLがPGroongaのインデックスを使ってくれない時に 比較的簡単に確認できるチェックポイントを紹介しました。
もちろん、ここで紹介したもの以外の原因もありますので、この記事の通りに やっても解決しない場合は、GitHubのIssuesやDiscussionsへ報告してください。 解決を急いでいる場合は、クリアコードの有償サポートもありますので、ぜひご検討ください。