pg_trgmと全文検索

34
Copyright © 2013 NTT DATA Corporation 2013年2月16日 株式会社NTTデータ 基盤システム事業本部 澤田 雅彦 つかってみようpg_trgmやってみよう全文検索

description

pg_trgmと全文検索

Transcript of pg_trgmと全文検索

Page 1: pg_trgmと全文検索

Copyright © 2013 NTT DATA Corporation

2013年2月16日 株式会社NTTデータ 基盤システム事業本部 澤田 雅彦

つかってみよう”pg_trgm”

やってみよう”全文検索”

Page 2: pg_trgmと全文検索

2 Copyright © 2013NTT DATA Corporation

INDEX

01 全文検索とは?

02 pg_trgmってなに?

03 pg_trgmの動きを見てみよう

04 まとめ

Page 3: pg_trgmと全文検索

Copyright © 2013 NTT DATA Corporation 3

1. 全文検索とは?

Page 4: pg_trgmと全文検索

4 Copyright © 2013 NTT DATA Corporation

1.1 全文検索ってなに?

全文検索ってなに?

複数にまたがるテキストからキーワードを含むテキストを見つけ出す事

東京都・・・・ ・・・・・・・・

・・・・・・・・・図書館・・・・ ・・・・・・・・

・・・・・・・・・・・・

・・オープンソース・・・

・・・・・・・・ ・・・・・

・・・・・・・・本。 ・学校・・・・・・・・・

東京都・・・・ ・・・・・・・・

・・・・・・・・・図書館・・・・ ・・・・・・・・

・・・・・・・・・・・・

・・オープンソース・・・

・・・・・・・・ ・・・・・

・・・・・・・・本。 ・学校・・・・・・・・・

東京都で・・・・ ・・・・・・・・ ・・・・・・

・・・データベース・・・ ・・・・・・・・

・・・・・・・・・・・・ ・・・・・

・・・・・・・・

東京都・・・・ ・・・・・・・・

・・・・・・・・・図書館・・・・ ・・・・・・・・

・・・・・・・・・・・・

・・オープンソース・・・

・・・・・・・・ ・・・・・

・・・・・・・・本。 ・学校・・・・・・・・・

全文検索

キーワード

「オープンソース」

Page 5: pg_trgmと全文検索

5 Copyright © 2013 NTT DATA Corporation

1.2 DBの全文検索ってなに?

SQL発行 DB : :

テキスト型の列を持つテーブルから、キーワードを含むレコードを検索すること

一般的に、「全文検索機能がある」といえば、高速にできることを表す

しかし、全文検索ではBtree等のインデックスを使用できないため遅い!

なので、ツールを用いてインデックスを張る必要がある

全文検索を実現するには。。。

N-gram方式、形態素解析方式がある

キーワード:「オープンソース」

Page 6: pg_trgmと全文検索

6 Copyright © 2013 NTT DATA Corporation

1.3 N-gram解析と形態素解析

形態素解析 N-gram方式

分割方法 単語単位で分割 文字単位で分割

インデックスサイズ

○(単語単位) ×(分割数が多いため)

表記の揺れ ○(類義語を定義しやすい) △(表記の揺れに弱い)

検索結果 △(単語の分割精度に依存する)

○(LIKEに近い検索結果になる)

どういう時に使える?

整った文章(論文等)を扱う時 記号や造語を検索する時

例)文字列‘今日は寒天の日’ではどうなる?

キーワード 「今日」,「寒天」,「日」 (3-gramの場合) 「今日は」,「日は寒」,「は寒天」,「寒天の」,「天の日」

Page 7: pg_trgmと全文検索

7 Copyright © 2013 NTT DATA Corporation

1.4 PostgreSQLで全文検索でインデックスを使うためのモジュール

pg_trgm textsearch_senna textsearch_ja

解析方式 3-gram N-gram 形態素解析

対応バージョン 9.1以降 8.2以降 8.3以降

提供形態 contribモジュール 外部ツール 外部ツール

開発主体 PostgreSQLコミュニティ 板垣さん(個人) 板垣さん(個人)

依存モジュール なし

(PostgreSQLのGin、GiSTを利用) Senna

Mecab (PostgreSQLのGin、GiSTを利用)

レプリケーション対応 ○ × ○

リカバリ対応 ○ × ○

日本語対応 △(ソース内を再設定しないと

いけない) ○ ○

以下、pg_trgmを扱っていきます

Page 8: pg_trgmと全文検索

Copyright © 2013 NTT DATA Corporation 8

2. pg_trgmとは?

Page 9: pg_trgmと全文検索

9 Copyright © 2013 NTT DATA Corporation

2.1 pg_trgmとは

全文検索はPostgreSQL9.1から対応

contribモジュールとして提供

3-gram方式

GIN,GiSTインデックスに対応

レプリケーション・リカバリに対応

名前 検索 構築・更新

GIN 汎用転置インデックス 速い 遅い

GiST 汎用検索ツリー 遅い 速い

Page 10: pg_trgmと全文検索

10 Copyright © 2013 NTT DATA Corporation

2.2 インストール方法

インストール方法

$ cd $PGSRC/contrib/pg_trgm

$ make

$ make install

【ビルド時の注意】

日本語対応させるためには。。。

trgm.hの #define KEEPONLYALNUM を必ずコメントアウトする!

Page 11: pg_trgmと全文検索

11 Copyright © 2013 NTT DATA Corporation

2.3 動作確認

postgres=# CREATE EXTENSION pg_trgm;

postgres=# CREATE TABLE tbl (col1 text);

postgres=# CREATE INDEX idx on tbl USING gin (col1 gin_trgm_ops); ←INDEX作成

postgres=# INSERT INTO tbl VALUES ('test');

postgres=# EXPLAIN SELECT * FROM tbl WHERE col1 LIKE '%test%';

QUERY PLAN

--------------------------------------------------------------------

Bitmap Heap Scan on tbl (cost=16.16..26.43 rows=21 width=32)

Recheck Cond: (col1 ~~ '%test%'::text)

-> Bitmap Index Scan on idx (cost=0.00..16.16 rows=21 width=0)

Index Cond: (col1 ~~ '%test%'::text)

(4 rows)

Page 12: pg_trgmと全文検索

12 Copyright © 2013 NTT DATA Corporation

2.4 インデックスを使ったとき、使わなかったとき

postgres=# EXPLAIN ANALYZE SELECT * FROM tbl WHERE col1 LIKE '%京都府%';

QUERY PLAN

---------------------------------------------------------------

Bitmap Heap Scan on tbl (cost=20.08..55.53 rows=10 width=32) (actual time=0.021..0.022 rows=3 loops=1)

Recheck Cond: (col1 ~~ '%京都府%'::text)

-> Bitmap Index Scan on idx (cost=0.00..20.07 rows=10 width=0) (actual time=0.013..0.013 rows=3 loops=1)

Index Cond: (col1 ~~ '%京都府%'::text)

Total runtime: 0.051 ms

インデックス使用

postgres=# EXPLAIN ANALYZE SELECT * FROM tbl WHERE col1 LIKE '%京都府%'; QUERY PLAN --------------------------------------------------------------- Seq Scan on tbl (cost=0.00..1662.00 rows=10 width=32) (actual time=22.282..22.284 rows=3 loops=1) Filter: (col1 ~~ '%京都府%'::text)

Total runtime: 22.303 ms

インデックス不使用 400倍以上の差

10万件のデータの中から、少量のデータを取り出すケースで検証。

Page 13: pg_trgmと全文検索

13 Copyright © 2013 NTT DATA Corporation

2.5 2文字以下の検索

pg_trgmは、3文字単位で区切るため、2文字以下の検索ではインデックスを効率的に使えない。

逆にインデックスを使うと「Bitmapの全件アクセス」になるため、SeqScanより遅い。

英語の全文検索では1,2文字はストップワード(inやa)であることが多いので問題な

いのかも。。

しかし日本語では「本」、「学校」など1,2文字で全文検索をする可能性は十分ある! postgres=# EXPLAIN ANALYZE SELECT * FROM tbl WHERE col1 LIKE '%京%'; QUERY PLAN -----------------------------------------------------------------------------------------------------Bitmap Heap Scan on tbl (cost=938.92..2600.80 rows=99990 width=32) (actual time=96.956..139.761 rows=10000 3 loops=1) Recheck Cond: (col1 ~~ '%京%'::text) -> Bitmap Index Scan on idx (cost=0.00..913.92 rows=99990 width=0) (actual time=96.874..96.874 rows=1000 03 loops=1) Index Cond: (col1 ~~ '%京%'::text)

Total runtime: 160.489 ms (5 rows)

postgres=# EXPLAIN ANALYZE SELECT * tbl WHERE col1 LIKE '%京%'; QUERY PLAN ---------------------------------------------------------------------------------------------------- Seq Scan on tbl (cost=0.00..1662.00 rows=99990 width=32) (actual time=0.014..40.286 rows=100003 loops=1) Filter: (col1 ~~ '%京%'::text)

Total runtime: 59.661 ms (3 rows)

インデックスを使った方が遅い

インデックス不使用

インデックス使用

Page 14: pg_trgmと全文検索

Copyright © 2013 NTT DATA Corporation 14

3. pg_trgmの動きを見てみよう

Page 15: pg_trgmと全文検索

15 Copyright © 2013 NTT DATA Corporation

3.1 どんな動きをするの?(インデックス登録編)

INSERT INTO tbl VALUES(‘あいうABC’);

キー TID

SQL発行 TID データ

1 あいうABC

INDEX

TABLE

※△=半角空白

Page 16: pg_trgmと全文検索

16 Copyright © 2013 NTT DATA Corporation

① 3文字単位に分割(前後にスペースを追加)

3.1 どんな動きをするの?(インデックス登録編)

INSERT INTO tbl VALUES(‘あいうABC’);

キー TID

SQL発行 TID データ

1 あいうABC

INDEX

TABLE

△△あ

△あい

あいう

いうA

うAB

ABC

BC△

※△=半角空白

Page 17: pg_trgmと全文検索

17 Copyright © 2013 NTT DATA Corporation

② 4B以上は3Bにハッシュ変換 + ソート

① 3文字単位に分割(前後にスペースを追加)

3.1 どんな動きをするの?(インデックス登録編)

INSERT INTO tbl VALUES(‘あいうABC’);

キー TID

SQL発行 TID データ

1 あいうABC

INDEX

TABLE

△△あ

△あい

あいう

いうA

うAB

ABC

BC△

CRC1

CRC2

CRC3

CRC4

CRC5

ABC

BC△

※△=半角空白

Page 18: pg_trgmと全文検索

18 Copyright © 2013 NTT DATA Corporation

② 4B以上は3Bにハッシュ変換 + ソート

① 3文字単位に分割(前後にスペースを追加)

③ INT値に変換

3.1 どんな動きをするの?(インデックス登録編)

INSERT INTO tbl VALUES(‘あいうABC’);

キー TID

SQL発行 TID データ

1 あいうABC

INDEX

TABLE

△△あ

△あい

あいう

いうA

うAB

ABC

BC△

CRC1

CRC2

CRC3

CRC4

CRC5

ABC

BC△

INT1

INT2

INT3

INT4

INT5

1111

2222

※△=半角空白

Page 19: pg_trgmと全文検索

19 Copyright © 2013 NTT DATA Corporation

② 4B以上は3Bにハッシュ変換 + ソート

① 3文字単位に分割(前後にスペースを追加)

③ INT値に変換

3.1 どんな動きをするの?(インデックス登録編)

INSERT INTO tbl VALUES(‘あいうABC’);

キー TID

INT1 1

INT2 1

INT3 1

INT4 1

INT5 1

1111 1

2222 1

SQL発行 TID データ

1 あいうABC

INDEX

TABLE

△△あ

△あい

あいう

いうA

うAB

ABC

BC△

CRC1

CRC2

CRC3

CRC4

CRC5

ABC

BC△

INT1

INT2

INT3

INT4

INT5

1111

2222

※△=半角空白

Page 20: pg_trgmと全文検索

20 Copyright © 2013 NTT DATA Corporation

3.2 どんな動きをするの?(インデックス検索編)

SELECT * FROM tbl WHERE col1 LIKE ‘%あいうA%’;

SQL発行

TID データ

1 あいうABC

2 あいうDEF

TABLE

※INT値への変換を省いています

キー TID

あいう 1,2

いうA 1

いうD 2

: :

うAB 1

うDE 2

: :

INDEX

Page 21: pg_trgmと全文検索

21 Copyright © 2013 NTT DATA Corporation

① 3文字単位に分割

3.2 どんな動きをするの?(インデックス検索編)

SELECT * FROM tbl WHERE col1 LIKE ‘%あいうA%’;

SQL発行

TID データ

1 あいうABC

2 あいうDEF

TABLE

あいう

いうA

※INT値への変換を省いています

INDEX キー TID

あいう 1,2

いうA 1

いうD 2

: :

うAB 1

うDE 2

: :

Page 22: pg_trgmと全文検索

22 Copyright © 2013 NTT DATA Corporation

② インデックスを検索

① 3文字単位に分割

3.2 どんな動きをするの?(インデックス検索編)

SELECT * FROM tbl WHERE col1 LIKE ‘%あいうA%’;

キー TID

あいう 1,2

いうA 1

いうD 2

: :

うAB 1

うDE 2

: :

SQL発行

TID データ

1 あいうABC

2 あいうDEF

INDEX

TABLE

あいう

いうA

※INT値への変換を省いています

Page 23: pg_trgmと全文検索

23 Copyright © 2013 NTT DATA Corporation

TID決定

② インデックスを検索

① 3文字単位に分割

3.2 どんな動きをするの?(インデックス検索編)

SELECT * FROM tbl WHERE col1 LIKE ‘%あいうA%’;

キー TID

あいう 1,2

いうA 1

いうD 2

: :

うAB 1

うDE 2

: :

SQL発行

TID データ

1 あいうABC

2 あいうDEF

INDEX

TABLE

あいう

いうA 「あいう」→1,2 「いうA」→1

より、TID1

※INT値への変換を省いています

Page 24: pg_trgmと全文検索

24 Copyright © 2013 NTT DATA Corporation

Recheck処理

TID決定

② インデックスを検索

① 3文字単位に分割

3.2 どんな動きをするの?(インデックス検索編)

SELECT * FROM tbl WHERE col1 LIKE ‘%あいうA%’;

キー TID

あいう 1,2

いうA 1

いうD 2

: :

うAB 1

うDE 2

: :

SQL発行

TID データ

1 あいうABC

2 あいうDEF

INDEX

TABLE

あいう

いうA 「あいう」→1,2 「いうA」→1

より、TID1

PostgreSQL内部の

Recheck処理行う

※INT値への変換を省いています

Page 25: pg_trgmと全文検索

25 Copyright © 2013 NTT DATA Corporation

3.3 Recheck処理の必要性

キー TID

小学校 1

学校長 1

学校と 1

: :

検索ワード:

「小学校長」

TABLE

INDEX

Recheck処理で再検査!

例えばこんな時。。

1 小学校と学校長 : :

Page 26: pg_trgmと全文検索

26 Copyright © 2013 NTT DATA Corporation

3.3 Recheck処理の必要性

キー TID

小学校 1

学校長 1

学校と 1

: :

検索ワード:

「小学校長」

「小学校」 : TID1 「学校長」 : TID1

TABLE

INDEX

間違った結果を取ってきてしまう

INDEXを検索 TID決定

Recheck処理で再検査!

例えばこんな時。。

1 小学校と学校長 : :

Page 27: pg_trgmと全文検索

27 Copyright © 2013 NTT DATA Corporation

3.3 Reckech処理の必要性

実際に見てみる。

postgres=# EXPLAIN ANALYZE SELECT * FROM tbl2 WHERE col1 LIKE '%小学校長%';

QUERY PLAN

---------------------------------------------------------

Bitmap Heap Scan on tbl2 (cost=16.00..20.01 rows=1 width=32) (actualtime=0.019..0.019 rows=0 loops=1)

Recheck Cond: (col1 ~~ '%小学校長%'::text)

-> Bitmap Index Scan on tbl2idx (cost=0.00..16.00 rows=1 width=0) (actualtime=0.010..0.010 rows=1 loops=1)

Index Cond: (col1 ~~ '%小学校長%'::text)

Total runtime: 0.046 ms

(5 rows)

Bitmap Index Scanでは1行検出しているが、 Rechek処理により間違った結果を排除している

ことがわかる。

Page 28: pg_trgmと全文検索

Copyright © 2013 NTT DATA Corporation 28

4. まとめ

Page 29: pg_trgmと全文検索

29 Copyright © 2013 NTT DATA Corporation

4. まとめ

pg_trgmの強み

pg_trgmを使うことで、全文検索を高速に行う事が出来る。

contribモジュールに入っているため、メンテナンスを破棄される可能

性が低い

pg_trgmの弱み

二文字以下の検索では効率的なインデックス検索ができない(日本

語では「本」、「学校」など利用シーンはある)

日本語対応させるためには、ソース内の設定を変更しなくてはいけ

ない

Page 30: pg_trgmと全文検索

Copyright © 2011 NTT DATA Corporation

Copyright © 2013 NTT DATA Corporation

Page 31: pg_trgmと全文検索

31 Copyright © 2013 NTT DATA Corporation

(参考)ワイルドカードの有無による検索の挙動の違い

・インデックスに登録するときは先頭に2つ、末尾1つに空白を追加して3文字分割する。

→”cat”の場合は、”△△c”,”△ca”,”cat”,”at△”

キー TID

△△A 1,2

△△X 3

△AB 1,2

△XA 3

ABC 1,2,3

BC△ 1,3

BCD 2

CD△ 2

XAB 3

‘△AB’ 1,2 ‘ABC’→ ’ABC’ → 1,2,3 → 1 ‘BC△’ 1,3 ‘△△A’ 1,2

‘%ABC%’ → ‘ABC’ →1,2,3

TID データ

1 ABC

2 ABCD

3 XABC

Page 32: pg_trgmと全文検索

32 Copyright © 2013 NTT DATA Corporation

(参考)Gin,Gistの更新・構築、検索速度の差

○Ginはなぜ更新・構築が遅い?

→一つのレコード挿入につき、分割した単語分のインデックスを更新する必要があるため。

(例:1000文字のレコードを1行INSERTするとGINインデックスは最大1000個更新する必要がある)

→それに比べ、Gistは一つのレコード挿入につき、インデックスには一つ登録するだけなので、Ginに比べると早い。

○Gistはなぜ検索が遅い?

→Gistではインデックスに登録された値と文章が非可逆なため、列候補を挙げた後、再チェックする必要があります。そのため、検索が遅くなります。

Page 33: pg_trgmと全文検索

33 Copyright © 2013 NTT DATA Corporation

(参考)CRC処理のソース

CRC処理のソース部分。

static void

cnt_trigram(trgm *tptr, char *str, int bytelen)

{

if (bytelen == 3)

{

CPTRGM(tptr, str);

}

else

{

pg_crc32 crc;

INIT_CRC32(crc);

COMP_CRC32(crc, str, bytelen);

FIN_CRC32(crc);

/*

* use only 3 upper bytes from crc, hope, it's good enough hashing

*/

CPTRGM(tptr, &crc);

}

}

#define INIT_CRC32(crc) ((crc) = 0xFFFFFFFF)

#define FIN_CRC32(crc) ((crc) ^= 0xFFFFFFFF)

#define COMP_CRC32(crc, data, len)¥

do { ¥

const unsigned char *__data = (const unsigned char *)(data); ¥

uint32 __len = (len); ¥

¥

while (__len-- > 0) ¥

{ ¥

int __tab_index = ((int) ((crc) >> 24) ^ *__data++) & 0xFF; ¥

(crc) = pg_crc32_table[__tab_index] ^ ((crc) << 8); ¥

} ¥

} while (0)

Page 34: pg_trgmと全文検索

34 Copyright © 2013 NTT DATA Corporation

(参考)KEEPONLYALNUMをコメントアウトしなかったら

○trgm_op.cのソースの一部

#ifdef KEEPONLYALNUM

#define iswordchr(c) (t_isalpha(c) || t_isdigit(c)) ←英数字の時にTrue

#else

#define iswordchr(c) (!t_isspace(c)) ←スペースでないときにTrue

#endif

static char *

○trgm_gin.cのソースの一部(データから空白を除いて文字の塊を見つけるところ。例:’today is sunny’→’today’,’is’,’sunny’)

find_word(char *str, int lenstr, char **endword, int *charlen)

{

char *beginword = str;

while (beginword - str < lenstr && !iswordchr(beginword))

beginword += pg_mblen(beginword);

if (beginword - str >= lenstr)

return NULL;

*endword = beginword;

*charlen = 0;

while (*endword - str < lenstr && iswordchr(*endword))

{

*endword += pg_mblen(*endword);

(*charlen)++;

}

return beginword;

}