前日の記事では、Groongaのインデックスの構造とインデックスを使った検索の仕方の詳細について述べました。Groonga Advent Calendar 2016 16日目のこの記事では、その続きとして、select
コマンドの--match_columns
パラメータの指定の仕方による検索の動作の違いを取り上げます。
同じカラム対して複数のインデックスが存在する場合や、同じテーブルの複数のカラムにまたがる検索を行う場合、--match_columns
の指定の仕方によって行われる検索処理の実態が大きく変わります。適切に指定しないと、せっかくのインデックスを活かせず無駄に時間やリソースを消費してしまうことにもなります。この解説を見て、目的に応じたより適切な--match_columns
の指定の仕方を把握しましょう。
なお、以下の説明は--filter
で使用できるスクリプト構文のquery()
関数の第1引数の指定にも当てはまります。--filter
の方を使っている場合、適宜説明を読み替えて下さい。
準備
実験用に、インデックスを複数持つテーブルを用意します。
$ cat dump.grn
table_create Entries TABLE_HASH_KEY ShortText
column_create Entries title COLUMN_SCALAR ShortText
column_create Entries body COLUMN_SCALAR ShortText
load --table Entries
[
["_key","title","body"],
["http://example.com/entry1","Hello Groonga.", "This is my first entry."],
["http://example.com/entry2","Hello world.","I love Groonga!"],
["http://example.com/entry3","Hello Mroonga, bye Groonga.","I use Mroonga."],
["http://example.com/entry4","Say, Hello Groonga!","I'm back."]
]
table_create Terms TABLE_PAT_KEY ShortText --default_tokenizer TokenBigram --normalizer NormalizerAuto
column_create Terms entries_title COLUMN_INDEX|WITH_POSITION Entries title
column_create Terms entries_body COLUMN_INDEX|WITH_POSITION Entries body
column_create Terms entries_whole COLUMN_INDEX|WITH_POSITION|WITH_SECTION Entries title,body
$ rm -rf /tmp/groonga-databases
$ mkdir -p /tmp/groonga-databases
$ groonga -n /tmp/groonga-databases/database.db quit
$ cat dump.grn | groonga /tmp/groonga-databases/database.db
このデータベース内には、以下のような内容を持つEntries
というテーブルがあります。
_id | _key | title (sid=1) | body (sid=2) |
---|---|---|---|
1 | http://example.com/entry1 | Hello Groonga. | This is my first entry. |
2 | http://example.com/entry2 | Hello world. | I love Groonga! |
3 | http://example.com/entry3 | Hello Mroonga, bye Groonga. | I use Mroonga. |
4 | http://example.com/entry4 | Say, Hello Groonga! | I'm back. |
また、このEntries
テーブルのtitle
とbody
の各カラムに対して、語彙表のTerms
テーブルに以下のような3つのインデックスが作成されています。
_key | entries_title | entries_body | entries_whole |
---|---|---|---|
Hello | [id=1,pos=1], [id=2,pos=1], [id=3,pos=1], [id=4,pos=2] | [id=1,pos=1,sid=1], [id=2,pos=1,sid=1], [id=3,pos=1,sid=1], [id=4,pos=2,sid=1] | |
Groonga | [id=1,pos=2], [id=3,pos=4], [id=4,pos=3] | [id=2,pos=3] | [id=1,pos=2,sid=1], [id=3,pos=4,sid=1], [id=4,pos=3,sid=1], [id=2,pos=3,sid=2] |
world | [id=2,pos=2] | [id=2,pos=2,sid=1] | |
bye | [id=3,pos=3] | [id=3,pos=3,sid=1] | |
Mroonga | [id=3,pos=2] | [id=3,pos=3] | [id=3,pos=2,sid=1], [id=3,pos=3,sid=2] |
Say | [id=4,pos=1] | [id=4,pos=1,sid=1] |
ここでは簡単のため、title
カラム由来のトークンに基づくインデックスの情報だけを示しています。複数のカラムにまたがるインデックスって何?という方は、先に前日の記事の方をご覧下さい。
以下、これを材料にして--match_columns
の指定の仕方による検索の動作の違いを解説していきます。
CASE1:単独のデータカラムを指定した場合
--match_columns
の使い方で最も一般的なのは、select --table Entries --match_columns title --query "Hello Groonga"
というような、データカラムの名前を1つだけ指定する書き方でしょう。
この時Groongaは、そのデータカラムに対応するインデックスカラムを自動的に探し出して、そのインデックスを使って前日の記事で述べたような検索処理を行います。
ここで1つ疑問が浮かびます。「自動的に」と言いましたが、この記事の例のように1つのデータカラムに対して複数のインデックスがある場合には、どのインデックスが使われるのでしょうか?
結論から言うと、その場合の動作は不定です。
実際の所、Groonga 6.1.1の場合は「最後に作られたインデックスが自動選択される」という結果になります。が、これはたまたまそのような結果になる実装がなされているというだけで、特に仕様としてそのような動作が保証されているわけではありません。なので、将来的には動作が変わる可能性があります。
以上を踏まえると、安定した検索結果を返すためには、1つのデータカラムに対応するインデックスが複数ある場合は--match_columns
でそのデータカラム名を指定するのは避けた方がよいということが言えます。このようなケースでは、後述するインデックスカラム名を指定する方法を取るのがお勧めです。
CASE2:複数のデータカラムを指定した場合
--match_columns
には、select --table Entries --match_columns title||body --query "Hello Groonga"
のように、||
で区切って複数のデータカラム名を列挙する書き方があります。
この時Groongaは、それぞれのデータカラムに対応するインデックスカラムを自動的に探し出して、そのインデックスを使って個別に検索を行い、それらの結果の論理和を最終的な検索結果として返します。つまり、複数カラムにまたがった検索を行います。
この時も、データカラムの名前からインデックスを一意に特定できない場合にどのインデックスが使われるかは不定です。安定した検索結果を返すためには、こちらも後述するインデックスカラム名を指定する方法を取る事をお勧めします。
CASE3:インデックスカラムを指定した場合
CASE3-1:単独カラムの指定
今の所まだチュートリアル等に詳しい説明を用意できていないのですが、実は--match_columns
にはインデックスカラムも指定できます。この指定の仕方であれば、同じカラムに複数のインデックスがある場合でも、どのインデックスを使うか明示することができます。
特定のカラムに結び付いたインデックスを指定すれば、そのインデックスを使って、暗黙的にそのカラムだけを対象に検索することになります。
-
select --table Entries --match_columns Terms.entries_title --query "Hello Groonga"
→Terms.entries_title
をインデックスとして使い、暗黙的にtitle
カラムだけを対象に検索する。 -
select --table Entries --match_columns Terms.entries_body --query "Hello Groonga"
→Terms.entries_body
をインデックスとして使い、暗黙的にbody
カラムだけを対象に検索する。
複数のカラムにまたがるインデックスでは、インデックスカラム名の後にデータカラム名を指定して「どのインデックスを使い、どのデータカラムを対象に検索するか」を明示できます。
-
select --table Entries --match_columns Terms.entries_whole.title --query "Hello Groonga"
→Terms.entries_whole
をインデックスとして使い、明示的にtitle
カラムだけを対象に検索する。 -
select --table Entries --match_columns Terms.entries_whole.body --query "Hello Groonga"
→Terms.entries_whole
をインデックスとして使い、明示的にbody
カラムだけを対象に検索する。
この指定の仕方は、BigramやMeCabやJMATという風に複数のトークナイザーを使い分けたい場合に有用です。select
の時点でのカラム指定で簡単に使用インデックスを切り替えられるため、それぞれのトークナイザーの性能評価などが容易になります。
CASE3-2:複数カラムの指定
インデックスカラムを指定する場合も、CASE2と同様に||
でカラム名を列挙できます。例えばselect --table Entries --match_columns Terms.entries_title||Terms.entries_body --query "Hello Groonga"
とすれば、複数のインデックスカラムを使った検索結果の論理和が検索結果として返されます。
この場合も、CASE2に比べてどのインデックスを使うかをはっきりと明示できるのが利点となります。
CASE3-3:複数カラムにまたがる検索
複数のカラムにまたがるインデックスに対して、select --table Entries --match_columns Terms.entries_whole --query "Hello Groonga"
のようにデータカラム名を明示せずにインデックスカラム名だけを指定した場合、そのインデックスを共有しているすべてのカラムにまたがった検索が行われます。
この記事の例だと、複数のインデックスを||
で列挙してselect --table Entries --match_columns Terms.entries_title||Terms.entries_body --query "Hello Groonga"
と指定した場合と検索結果としては変わりません。しかし、検索処理の効率の点では違いがあります。
-
--match_columns Terms.entries_title||Terms.entries_body
→それぞれのインデックスで個別に検索を行い、その論理和を返す。2回検索するので時間がかかる。 -
--match_columns Terms.entries_whole
→インデックスのセクションIDを無視してマッチングを行い、検索結果を返す。検索が1回で済むので高速。
ただ、複数カラムにまたがるインデックスカラム名を単独で指定した場合、データカラムごとの重み付けはできなくなります。重み付けが必要な場合にはこちらの指定ではなく、CASE2やCASE3-2のように各データカラムやインデックスカラム名を列挙する必要があります。
まとめ
--match_columns
の指定の仕方について、いくつかのパターンのメリット・デメリットをそれぞれ解説しました。
ちなみに、--match_columns
にはこの他に、同一テーブルの複数カラムでは無く、複数のテーブルのカラムをまたいだインデックスを指定する使い方もあります。長くなるので説明は省略しますが、こちらの方法では「ブログのコメント欄の内容を対象に検索して、その親エントリーを検索結果として得る」というようなことができます。興味のある方は、リンク先のチュートリアルもご覧下さい。
--match_columns
の指定の仕方を工夫すると、Groongaのインデックスをより効果的に活用できます。Groongaはこのようにインデックスの詳細を理解した上でのきめ細やかな指定によって、エンジンの性能を限界まで引き出すことができるのが特長です。「なんかだかよくわからないけど、インデックスを使うと検索が早くなる」というレベルの理解から一歩踏み込んで、皆さんもぜひ計算機資源を有効に利用して下さい。
以上、Groonga Advent Calendar 2016 16日目として--match_columns
の指定の仕方による検索動作の変化を解説しました。引き続きGroonga Advent Calendarをお楽しみ下さい!