Rabbit Slide Show

PHPでPostgreSQLとPGroongaを使って高速日本語全文検索!

2017-06-28

Description

PHPでPostgreSQLとPGroongaを使って高速日本語全文検索を実現する方法を紹介します!これを参考にLIKEよりも速くリッチな日本語全文検索機能を実現しましょう!

Text

Page: 1

PHPで
PostgreSQLと
PGroongaを使って
高速日本語全文検索!
須藤功平
クリアコード
第115回 PHP勉強会@東京
2017-06-28
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 2

PostgreSQLと全文検索
LIKE:組込機能
textsearch:組込機能
pg_trgm:標準添付
アーカイブには含まれている
別途インストールすれば使える
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 3

LIKE
少ないデータ
十分実用的
400文字×20万件くらいなら1秒とか
少なくないデータ
性能問題アリ
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 4

textsearch
インデックスを作るので速い
言語毎にモジュールが必要
英語やフランス語などは組込
日本語は別途必要
日本語用モジュール
公式にはメンテナンスされていない
forkして動くようにしている人はいる
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 5

pg_trgm
インデックスを作るので速い
注:ヒット件数が増えると遅い
注:テキスト量が多いと遅い
注:1,2文字の検索は遅い (米・日本)
日本語を使うにはひと工夫必要
C.UTF-8を使う
ソースを変更してビルド
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 6

プラグイン
pg_bigm
pg_trgmの日本語対応強化版
PGroonga
本気の全文検索エンジンを利用
速いし日本語もバッチリ!
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 7

ベンチマーク:pg_bigm
3
2.5
2
pg_bigm
Data: Japanese Wikipedia
(Many records and large documents)
N records: About 0.9millions
Average text size: 6.7KiB
1.5
Slow
Slow
1
0.5
0
311
14706
20389
N hits
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 8

ベンチマーク:PGroonga
3
2.5
2
PGroonga
pg_bigm
Data: Japanese Wikipedia
(Many records and large documents)
N records: About 0.9millions
Average text size: 6.7KiB
1.5
1
0.5
0
311
Fast Fast
14706 20389
N hits
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 9

よし!
PostgreSQLとPGroongaを使って
高速日本語全文検索サービスを
PHPで作ろう!
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 10

PHP document search
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 11

機能
検索キーワードハイライト
キーワード周辺テキスト表示
オートコンプリート
ローマ字対応(seiki→正規表現)
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 12

作り方:ツール
フレームワーク
Laravel
RDBMS
PostgreSQL
高速日本語全文検索機能
PGroonga
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 13

作り方:インストール
Laravel
省略
PostgreSQL
パッケージで
PGroonga
パッケージで
https://pgroonga.github.io/ja/install/
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 14

初期化:Laravel
% laravel new php-document-search
% cd php-document-search
% editor .env
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 15

初期化:データベース
% sudo -u postgres -H \
createdb php_document_search
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 16

初期化:PGroonga
-- ↓を実行する必要がある
CREATE EXTENSION pgroonga;
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 17

初期化:PGroonga
マイグレーションファイル作成
% php artisan \
make:migration enable_pgroonga
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 18

マイグレーション
public function up()
{
DB::statement("CREATE EXTENSION pgroonga;");
}
public function down()
{
DB::statement("DROP EXTENSION pgroonga;");
}
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 19

モデル作成
ドキュメントはモデル
名前:Entry
1ページ1インスタンス
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 20

モデル作成
% php artisan \
make:model \
--migration \
--controller \
--resource \
Entry
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 21

マイグレーション
public function up() {
Schema::create('entries', function ($table) {
$table->increments('id');
table->text('url');
$table->text('title');
$table->text('content');
// PGroonga用インデックス。デフォルトで全文検索用。
// 主キー(id)も入れるのが大事!スコアー取得に必要。
$table->index(
['id', 'title', 'content'], null, 'pgroonga');
});
}
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 22

データ登録
1. PHPのドキュメントを
ローカルで生成
PHPのドキュメントの作り方
http://doc.php.net/tutorial/
フィードバックチャンスがいろい
ろあったよ!(後述)
2. ページ毎にPostgreSQLに挿入
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 23

コマンド作成
% php artisan \
make:command \
--command=doc:register \
RegisterDocuments
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 24

登録コマンド実装(一部)
public function handle()
{
foreach (glob("public/doc/*.html") as $html_path) {
$document = new \DOMDocument();
@$document->loadHTMLFile($html_path);
$xpath = new \DOMXPath($document);
$entry = new Entry();
$entry->url = "/doc/" . basename($html_path);
// XPathでテキスト抽出
$this->extract_title($entry, $xpath);
$this->extract_content($entry, $xpath);
$entry->save();
}
}
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 25

登録
% php artisan doc:register
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 26

検索用コントローラー
public function index(Request $request)
{
$query = $request['query'];
$entries = Entry::query()
// ↓はモデルに作る(後述)
->fullTextSearch($query)
->limit(10)
->get();
return view('entry.search.index',
[
'entries' => $entries,
'query' => $query,
]);
}
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 27

検索対象モデル
public function
scopeFullTextSearch($query, $search_query)
{
if ($search_query) {
return ...; // クエリーがあったら検索
} else {
return ...; // なかったら適当に返す(省略)
}
}
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 28

検索対象モデル:検索
return $query
->select('id', 'url')
// 適合度をスコアーとして返す
->selectRaw('pgroonga.score(entries) AS score')
// キーワードハイライト
->highlightHTML('title', $search_query)
// キーワード周辺のテキスト(キーワードハイライト付き)
->snippetHTML('content', $search_query)
// タイトルと本文を全文検索(後で補足)
->whereRaw('title @@ ? OR content @@ ?',
[$search_query, $search_query])
// それっぽい文書の順に返す
->orderBy('score', 'DESC');
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 29

キーワードハイライト
public function scopeHighlightHTML($query,
$column,
$search_query)
{
return $query
// PGroonga提供ハイライト関数
->selectRaw("pgroonga.highlight_html($column, " .
// PGroonga提供クエリーからキーワードを抽出する関数
"pgroonga.query_extract_keywords(?)) " .
"AS highlighted_$column",
[$search_query]);
}
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 30

検索結果
<div class="entries">
@foreach ($entries as $entry)
<a href="{{ $entry->url }}">
<h4>
{{-- マークアップ済み! --}}
{!! $entry->highlighted_title !!}
<span class="score">{{ $entry->score }}</span>
</h4>
{{-- 周辺テキストはtext[](後で補足) --}}
@foreach ($entry->content_snippets as $snippet)
<pre class="snippet">{!! $snippet !!}</pre>
@endforeach
</a>
@endforeach
</div>
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 31

検索対象モデル:配列
public function getContentSnippetsAttribute($value)
{ // PostgreSQLは配列をサポートしているがPDOは未サポート
// '["...","..."]'という文字列になるのでそれを配列に変換
return array_map(
function ($e) {
// 「"」が「\"」になっているので戻す
return preg_replace('/\\\\(.)/', '$1', $e);
},
explode('","', substr($value, 2, -2)));
}
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 32

高速日本語全文検索!
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 33

オートコンプリート
必要なもの
候補用テーブル
候補のヨミガナ(カタカナ)
PGroonga!!!
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 34

モデル作成
% php artisan \
make:model \
--migration \
--controller \
--resource \
Term
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 35

マイグレーション:カラム
public function up()
{
Schema::create('terms', function ($table) {
$table->increments('id');
$table->text('term');
$table->text('label');
$table->text('reading'); // 本当は配列にしたい
$table->timestamps();
// インデックス定義(後述)
});
}
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 36

マイグレーション
インデックス
$table->index([
// 候補に対する前方一致検索用
DB::raw('term pgroonga.text_term_search_ops_v2'),
// ヨミガナに対する前方一致RK検索用
DB::raw('reading pgroonga.text_term_search_ops_v2'),
], null, 'pgroonga');
// 候補に対する全文検索用(中間一致用)
$table->index([DB::raw('term')], null, 'pgroonga');
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 37

前方一致RK検索
日本語特化の前方一致検索
ローマ字・ひらがな・カタカナで
カタカナを前方一致検索できる
gy→ギュウニュウ
ぎ→ギュウニュウ
ギ→ギュウニュウ
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 38

候補モデル:検索
public function
scopeComplete($query, $search_query)
{
return $query
->select("label")
->highlightHTML('label', $search_query)
->whereRaw("term &^ :query OR " . // 前方一致検索
"reading &^~ :query OR " . // 前方一致RK検索
"term @@ :query", // 全文検索
["query" => $search_query])
->orderBy("label")
->limit(10);
}
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 39

コントローラー
public function index(Request $request)
{
$query = $request["query"];
// モデルに実装した検索処理を呼び出し
$terms = Term::query()->complete($query);
$data = [];
foreach ($terms->get() as $term) {
$data[] = [
"value" => $term->label,
"label" => $term->highlighted_label,
];
}
// JSONで候補を返す
return response()->json($data);
}
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 40

UI
$('#query').autocomplete({
source: function(request, response) {
$.ajax({
url: "/terms/", // コントローラー呼び出し
dataType: "json",
data: {query: this.term},
success: response
});
}
}).autocomplete("instance")._renderItem = function(ul, item) {
return $("<li>")
.attr("data-value", item.value) // 候補には生データを使う
.append(item.label) // ハイライトしたデータを表示
.appendTo(ul);
};
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 41

オートコンプリート!
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 42

まとめ
PGroongaを使えば…
高速日本語全文検索サービスを…
PHPで簡単に作れる!
PHP document searchのソース
https://github.com/kou/php-document-
search
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 43

その他(1)
PHP+MySQL+Mroongaでも簡単!
Groongaではじめる全文検索
https://grnbook-ja.tumblr.com/
著者:北市真
PHP+Mroonga入門の電子書籍
今はまだ無料!
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 44

その他(2)
だれかPHP document searchを
メンテナンスしませんか?
普通に便利じゃないかと!
複数バージョン対応とか
複数言語対応とか
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 45

その他(3)
PHPの開発に参加しませんか?
PDOのPostgreSQL対応強化とか
ドキュメントまわりとか
やりたいけど自分はムリそう…
そんなことはないんですよ!
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 46

その他(4)
OSS Gateワークショップ
OSS開発未経験者を経験者にする
ワークショップ
PHPもOSS!
次回は7月29日
https://oss-gate.doorkeeper.jp/events/
upcoming
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Page: 47

その他(5)
PHPカンファレンス2017内で
OSS Gateワークショップ開催は
どうですか!?
PHP関連のOSSの開発に参加する人が
増えるとうれしい?
うれしいならコラボできそう
PHPで PostgreSQLと PGroongaを使って 高速日本語全文検索!
Powered by Rabbit 2.2.1

Other slides

Apache Arrow
2017-06-13
Apache Arrow
2017-05-28
Mroonga!
2015-10-30