PGroongaの実装¶ ↑
: subtitle
(('note:ぴーじーるんがのじっそう'))
: author
須藤功平
: institution
株式会社クリアコード
: content-source
PostgreSQLカンファレンス2015
: date
2015-11-27
: allotted-time
40m
: theme
.
(建前の)目的¶ ↑
PostgreSQLのamindexを説明
方法¶ ↑
PGroongaの実装を紹介n ((‘note:PGroongaはamindexとして実装されているため’))n ((‘note:ただし、ヒントだけなので詳細はPGroongaのソースを参照’))n ((‘note:github.com/pgroonga/pgroonga’))
本当の目的¶ ↑
PGroongaの自慢
PGroongaとは¶ ↑
amindexの一種
amindex¶ ↑
索引の実装をn PostgreSQLにn 追加する仕組み
amindexの使い方¶ ↑
# coderay sql CREATE INDEX name ON table USING pgroonga (column);
((‘tag:center’)) 組み込みの索引と同じn ((({USING}))を指定するだけ)
PGroongaの提供機能¶ ↑
索引を使ったn ((*超高速な*))n 全文検索機能
PostgreSQLと全文検索¶ ↑
課題あり
PostgreSQLと全文検索1¶ ↑
# coderay sql CREATE INDEX name ON table USING gin (to_tsvector('english', column)); SELECT * FROM table WHERE to_tsvector('english', column) @@ '...';
((‘tag:center’)) 組み込みの全文検索機能n 日本語非対応n ((‘note:www.postgresql.org/docs/current/static/textsearch.html’))
PostgreSQLと全文検索2¶ ↑
# coderay sql CREATE INDEX name ON table USING gin (column gin_trgm_ops); SELECT * FROM table WHERE column LIKE '%...%';
((‘tag:center’)) contrib/pg_trgmn 日本語非対応n ((‘note:www.postgresql.org/docs/current/static/pgtrgm.html’))
PostgreSQLと全文検索¶ ↑
日本語非対応
PGroongaとPostgreSQL¶ ↑
日本語対応!
PGroongaを使う¶ ↑
# coderay sql CREATE INDEX name ON table USING pgroonga (column); SELECT * FROM table WHERE column @@ '全文検索';
((‘tag:center’)) 日本語対応!
しかも速い!¶ ↑
ヒット数と検索時間¶ ↑
# RT delimiter = [|] ヒット数 | 検索時間 368 | 0.030s((' ')) 17,172 | 0.121s((' ')) 22,885 | 0.179s((' ')) 625,792 | 0.646s(*)
((‘note:(*) work_memを10MBに増やしている’))
((‘tag:center’)) データ:Wikipedia日本語版n ((‘note:約184万レコード・平均サイズ約3.8KB’))n ((‘note:詳細:www.clear-code.com/blog/2015/5/25.html’))
検索時間:比較¶ ↑
# RT delimiter = [|] ヒット数 | PGroonga | pg_bigm 368 | ((*0.030s*)) | (('0.107s')) 17,172 | ((*0.121s*)) | (('1.224s')) 22,885 | ((*0.179s*)) | (('2.472s')) 625,792\n(('note:(*)')) | (('0.646s')) | ((*0.556s*))
((‘note:(*) 他は検索語が3文字以上でこれだけ2文字’))
((‘tag:center’)) PGroongaは安定して速い!
なぜ速いのか¶ ↑
バックエンドがn 外部の本格的なn 全文検索n ライブラリー
amindexでのポイント¶ ↑
* (({_PG_init()})) * ライブラリーを初期化 * (({on_proc_exit()})) * 後始末
全文検索ライブラリー¶ ↑
Groongan (ぐるんが)
Groonga¶ ↑
((*本格的な*))n 全文検索n エンジンn ((‘note:サーバーとしてもライブラリーとしても使える’))
本格的な例1¶ ↑
((‘tag:center’)) 長い文書でも検索性能が落ちない
((‘tag:margin-top * 2’)) この特徴が有用なサービス例:
* Wiki * イメージ:Wikipedia * ドキュメント検索 * イメージ:ファイルサーバー検索
長い文書でも速い理由¶ ↑
((*完全*))n 転置索引
転置索引¶ ↑
* 完全:位置情報あり * Groonga * 無印:位置情報なし * GIN * Groonga(('note:(必要なければ入れないことができる)'))
転置索引の違い¶ ↑
# image # src = images/inverted-index.svg # relative_height = 100
索引での検索の違い¶ ↑
# image # src = images/search-with-inverted-index.svg # relative_height = 100
検索速度の違い¶ ↑
* 完全転置索引:安定して速い * 索引だけで検索完了 * 転置索引:速さが安定しない * 索引での検索+全件スキャン * 候補文書が多い・長い→遅くなる
amindexでのポイント¶ ↑
* 全件スキャンを無効にする * (({scan->xs_recheck = false})) * (({tbm_add_tuples(..., false)}))
本格的な例2¶ ↑
((‘tag:center’)) 常時更新・常時検索に強い
((‘tag:margin-top * 2’)) この特徴が有用なサービス例:
* SNS * イメージ:Twitter * ナレッジ共有サービス * イメージ:Qiita・teratail
常時更新・検索に強い?¶ ↑
更新中もn 参照性能がn 落ちない
性能傾向¶ ↑
# image # src = images/performance-charcteristic-for-constant-read-and-write.svg # relative_height = 95
落ちない理由¶ ↑
更新時にn 参照ロックなし
参照ロック¶ ↑
獲得したらn 他の処理はn 参照不可になるn ロック
GINと更新と参照¶ ↑
# image # src = images/read-while-write-gin.svg # relative_height = 95
Groongaと更新と参照¶ ↑
# image # src = images/read-while-write-groonga.svg # relative_height = 95
PGroongaと更新と参照¶ ↑
# image # src = images/read-while-write-pgroonga.svg # relative_height = 95
参照ロックフリーの実現¶ ↑
# image # src = images/reference-lock-free-idea.svg # relative_height = 100
amindexでのポイント¶ ↑
* (({Lock*()}))(('note:(*)'))を呼ばない\n (('note:(*) PostgreSQL提供のロックAPI')) * 呼ぶとGroongaの参照ロックフリーの有意性を殺してしまう
本格的な例3¶ ↑
((‘tag:center’)) 継続的な更新に強い
((‘tag:margin-top * 2’)) この特徴が有用なサービス例:
* SNS * イメージ:Twitter・Facebook * チャット * イメージ:Slack
継続的な更新に強い?¶ ↑
* 間欠的な性能劣化がない * 更新も検索も * GINは両方ある * (({FASTUPDATE}))を無効にしていない場合
間欠的性能劣化:Groonga¶ ↑
((‘tag:center’)) Groongaは間欠的性能劣化なし
* 常に最新ポスティングリストを\n 維持しているから * 更新負荷が高くならない対策入り\n (('note:https://github.com/groonga/groonga/wiki/Memo'))
Groongaの索引更新¶ ↑
# image # src = images/update-groonga.svg # relative_height = 100
間欠的性能劣化:GIN¶ ↑
((‘tag:center’)) GINは間欠的性能劣化あり
* 最新ポスティングリスト維持を\n サボって高速化しているから * サボったつけを払うときに性能劣化 * 例:検索時・更新が溜まりすぎた時
GINの索引更新¶ ↑
# image # src = images/update-gin.svg # relative_height = 100
GINの索引検索¶ ↑
# image # src = images/search-gin.svg # relative_height = 100
本格的な例4¶ ↑
((‘tag:center’)) 索引の作り直しが速い
((‘tag:margin-top * 2’)) この特徴が有用なケース:
* ダンプのリストア * サービス復旧
索引作成¶ ↑
# RT delimiter = [|] 元データの\nロード時間 | 索引\n作成時間 16分31秒 | 25分37秒
((‘tag:center’)) データ:Wikipedia日本語版n ((‘note:約184万レコード・平均サイズ約3.8KB’))n ((‘note:詳細:www.clear-code.com/blog/2015/5/25.html’))
索引作成:比較¶ ↑
# RT delimiter = [|] PGroonga | pg_bigm 25分37秒 | 5時間56分15秒
((‘tag:center’)) pg_bigmより約14倍速い!
速い理由¶ ↑
((*静的*))n 索引構築をn サポート
索引構築方法¶ ↑
* ((*動的*))索引構築 * 構築中も完了した分は検索可能 * 即時反映可・一括登録不向き * ((*静的*))索引構築 * 構築完了まで使えないが速い\n (('note:(処理時間は入力に比例。指数関数的ではない。)')) * 即時反映不可・一括登録向き
SQLの違い¶ ↑
# coderay sql -- 動的索引構築 CREATE INDEX ...; INSERT ...; -- 静的索引構築 INSERT ...; CREATE INDEX ...;
amindexでのポイント¶ ↑
* (({aminsert()})) * 動的索引構築を実装 * (({ambuild()})) * 静的索引構築を実装
速い理由のまとめ¶ ↑
* 索引だけで検索可能 * 更新中も検索可能 * 間欠的な性能劣化なし * 静的索引構築をサポート
速さ以外の利点1¶ ↑
便利な独自機能
独自機能1¶ ↑
見慣れたn クエリー言語
例¶ ↑
# coderay sql body @@ 'PostgreSQL OR MySQL -Oracle' * Web検索エンジン互換 * →ユーザーの入力をそのまま使える * デフォルトAND * OR・-(除外)あり
独自機能2¶ ↑
配列+全文検索
例¶ ↑
# coderay sql CREATE TABLE logs (hosts text[]); INSERT INTO logs VALUES (Array['web', 'db']); CREATE INDEX index ON logs USING pgroonga (hosts); SELECT * FROM logs WHERE hosts @@ '各ホスト名を全文検索';
独自機能3¶ ↑
JSON+全文検索
例¶ ↑
# coderay sql INSERT INTO logs (record) VALUES ('{"host": "ダウンホスト"}'), ('{"message": "シャットダウン"}'); SELECT * FROM logs WHERE record @@ 'string @@ "ダウン"' -- record -- ---------------------------- -- {"host": "ダウンホスト"} -- {"message": "シャットダウン"}
((‘tag:center’)) JSON内の全テキストから全文検索
独自機能4¶ ↑
ノーマライザー
ノーマライザー¶ ↑
* 文字を正規化するモジュール * 表記の違いを吸収できる * (('wait'))アルファベット:全部小文字 * (('wait'))ひらがな・カタカナ:全部全角 * (('wait'))㍉→ミリ * (('wait'))UnicodeのNFKCベース
独自機能5¶ ↑
トークナイザー
トークナイザー¶ ↑
* キーワード切り出しモジュール * クエリーに指定できる\n キーワードを調整 * 例:すもも|も|もも|も
デフォルト:可変長Ngram¶ ↑
* 英語:字種区切り * 例:Hello|World|!!! * Bigramだとノイズが多い * 日本語:Bigram * 例:ポスグレ→ポス|スグ|グレ|レ * 漏れがない
形態素解析器ベース¶ ↑
* MeCab:OSS * 新語対応には辞書メンテが必要 * 参考:mecab-ipadic-neologd * JMAT:商用製品 * ジャストシステム社製 * ATOKでも使っている辞書を提供 * 新語にも強い\n (('note:参考:「JMAT Groonga Tokenizer Talks」で検索'))
amindexでのポイント¶ ↑
* (({amoptions()})) * (({CREATE INDEX}))でのオプションを定義 # coderay sql CREATE INDEX index ON memos USING pgroonga (content) WITH (normalizer = 'NormalizerMySQLUnicodeCI', tokenizer = 'TokenMecab');
速さ以外の利点2¶ ↑
見慣れた機能n ((‘note:B-tree・GINの代わりに使える’))
タグ検索¶ ↑
# coderay sql CREATE TABLE memos ( tags varchar(1023)[] ); CREATE INDEX index ON memos USING pgroonga (tags); SELECT * FROM memos WHERE tags %% 'タグ';
範囲検索¶ ↑
# coderay sql CREATE TABLE users (age int); CREATE INDEX index ON users USING pgroonga (age); SELECT * FROM users WHERE age < 20;
Index Only Scan¶ ↑
* 索引がデータも返す * テーブルにアクセスしないので高速 * PGroonga・B-tree:サポート\n (('note:PostgreSQL 9.5からはGiSTもサポート')) * GIN:未サポート
amindexでのポイント¶ ↑
* (({amcanreturn()})) * (({true}))を返す * (({amgettuple()})) * (({scan->xs_want_itup}))なら\n (({scan->xs_itup}))にデータを設定
LIKE¶ ↑
# coderay sql CREATE INDEX index ON memos USING pgroonga (content); SELECT * FROM memos WHERE content LIKE '%...%';
((‘tag:center’)) 索引を使って高速検索n アプリケーションの変更不要
マルチカラムインデックス¶ ↑
# coderay sql CREATE INDEX index ON memos USING pgroonga (title, content); SELECT * FROM memos WHERE title @@ '...' AND content @@ '...';
((‘tag:center’)) ((‘note:titleでもcontentでもマッチ!’))n ((‘note:と書けないのでそんなにうれしくない’))
設計ミス¶ ↑
(({text @@ text}))¶ ↑
((‘tag:center’)) 組み込みの定義と競合n ((‘note:ts_vector @@ ts_queryにキャストされる’))
((‘tag:margin-top * 2’)) 回避方法:
# coderay sql ALTER DATABASE name SET search_path = '$user',public,pgroonga,pg_catalog;
(({jsonb @@ text}))¶ ↑
((‘tag:center’)) 全文検索にすればよかった
# coderay sql jsonb @@ 'string @ "キーワード"' -- ↓ jsonb @@ 'キーワード'
((‘tag:center’)) ((‘note:今の細かい検索条件を指定できる機能は別演算子にする’))
今後¶ ↑
もっとGroongaを活かす¶ ↑
# RT delimiter = [|] PGroonga | pg_bigm | Groonga (('0.646s')) | (('0.556s')) | ((*0.085s*))
((‘note:ヒット数635,792、検索語は2文字’))
((‘tag:center’)) 生Groongaは1桁速い!n ((‘note:詳細:github.com/groonga/wikipedia-search/issues/3’))
同義語展開サポート¶ ↑
# coderay sql body @@ pgroonga.expand_query('ネジ') -- ↓ body @@ 'ネジ OR ねじ OR ボルト'
((‘tag:center’)) ((‘note:Groongaでは使える’))
ステミングサポート¶ ↑
found/findsn ↓n findn ((‘note:Groongaでは使える’))
text @@ pgroonga.query¶ ↑
# coderay sql body @@ 'ポスグレ'::pgroonga.query
((‘tag:center’)) ((‘note:組み込みの(({text @@ text}))との競合回避’))
重みサポート¶ ↑
# coderay sql -- タイトルのほうが本文より10倍重要 body @@ ('title * 10 || body', 'ポスグレ')
((‘tag:center’)) ((‘note:Groongaでは使える’))
複合主キーサポート¶ ↑
# coderay sql CREATE TABLE t ( c1 INT, c2 INT, PRIMARY KEY (c1, c2) ); CREATE INDEX index ON t USING pgroonga (c1, c2);
((‘ ’))¶ ↑
まとめn ((‘note:時間が余ったらチュートリアルを自慢する’))
まとめ¶ ↑
* PGroongaは速い * PGroongaは便利 * PGroongaには設計ミスがある * PGroongaはもっと便利になる
((‘wait’)) ((‘tag:center’)) PGroongaを使おう!
おしらせ¶ ↑
* Groonga Meatup 2015\n (('note:https://groonga.doorkeeper.jp/events/31482')) * PGroongaの話題もアリ * 多少空きアリ(('note:(定員を多少増やせる)')) * 11月29日(日)13:30開始 * (('note:来年2月9日は「MySQLとPostgreSQLと日本語全文検索」'))\n (('note:https://groonga.doorkeeper.jp/events/35295'))
# == スライドプロパティ
# : background-image # private/images/groonga-meatup-2015-design.svg
# : background-image-relative-width # 22
# : background-image-align # right
# : background-image-vertical-align # bottom