rabbit-slide-kou-mysql-and-postgresql-and-japanese-full-text-search-3 2016.9.29.1 → 2016.9.29.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/config.yaml +1 -1
- data/mroonga-and-pgroonga.rab +2 -2
- data/mypgft3.md +205 -0
- data/pdf/mysql-and-postgresql-and-japanese-full-text-search-3-mroonga-and-pgroonga.pdf +0 -0
- metadata +2 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8a7e9133fb0379251b27f937ccc07b01bba05d69
|
4
|
+
data.tar.gz: cfa0b94b9792e68a52c042971842e96069a2c7df
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e725f5fe183960883fe75810123dca8f27085cfe2fb175c313989bf7c6066de560809d2cf2d3ba7ba72a9c340de592bd5ad4e732e749d904740e551b193aec4e
|
7
|
+
data.tar.gz: 3ccbc9da173beb65a24c78bd105ca2c3a868eac50e331e11acdfd5115a28b7dcb13843fcbe24c4329399a94deb6c35af0e12319bf27364dd0ec38c759a4474f8
|
data/config.yaml
CHANGED
data/mroonga-and-pgroonga.rab
CHANGED
@@ -43,7 +43,7 @@
|
|
43
43
|
|
44
44
|
* Redmine
|
45
45
|
* チケット管理システム
|
46
|
-
* Ruby on
|
46
|
+
* Ruby on Railsを使用
|
47
47
|
* Zulip
|
48
48
|
* チャットツール
|
49
49
|
* Djangoを使用
|
@@ -174,7 +174,7 @@ MySQL + Mroongaのケース
|
|
174
174
|
class Issue
|
175
175
|
# この後にロールバックされることがあるのでカンペキではない
|
176
176
|
# 再度同じチケットを更新するかデータを入れ直せば直る
|
177
|
-
|
177
|
+
after_save do |record|
|
178
178
|
fts_record =
|
179
179
|
FtsIssue.find_or_initialize_by(issue_id: record.id)
|
180
180
|
fts_record.subject = record.subject
|
data/mypgft3.md
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
# [Groonga] MySQLとPostgreSQLと日本語全文検索3:MroongaとPGroongaの導入方法例 #mypgft
|
2
|
+
|
3
|
+
2016年9月29日(肉の日!)に「[MySQLとPostgreSQLと日本語全文検索3](https://groonga.doorkeeper.jp/events/50541)」というイベントを開催しました。その名の通りMySQLとPostgreSQLでの日本語全文検索についての話題を扱うイベントです。今回も[DMM.comラボ](http://labo.dmm.com/)さんに会場を提供してもらいました。
|
4
|
+
|
5
|
+
[2月9日に開催した1回目のイベント](20160209)では[Mroonga](http://mroonga.org/ja/)・[PGroonga](https://pgroonga.github.io/ja/)については次の2つのことについて紹介しました。
|
6
|
+
|
7
|
+
* Mroonga・PGroongaが速いということ
|
8
|
+
* Mroonga・PGroongaの使い方
|
9
|
+
|
10
|
+
[6月9日に開催した2回目のイベント](20160609)ではMroonga・PGroongaについては次の2つのことについて紹介しました。
|
11
|
+
|
12
|
+
* Mroonga・PGroongaのオススメの使い方
|
13
|
+
* レプリケーションまわり
|
14
|
+
|
15
|
+
今回はMroonga・PGroongaについては次のことについて紹介しました。
|
16
|
+
|
17
|
+
* [Redmine](http://www.redmine.org/)・[Zulip](https://zulip.org/)にMroonga・PGroongaを導入する方法
|
18
|
+
|
19
|
+
<div class="rabbit-slide">
|
20
|
+
<iframe src="https://slide.rabbit-shocker.org/authors/kou/mysql-and-postgresql-and-japanese-full-text-search-3/viewer.html"
|
21
|
+
width="640" height="524"
|
22
|
+
frameborder="0"
|
23
|
+
marginwidth="0"
|
24
|
+
marginheight="0"
|
25
|
+
scrolling="no"
|
26
|
+
style="border: 1px solid #ccc; border-width: 1px 1px 0; margin-bottom: 5px"
|
27
|
+
allowfullscreen> </iframe>
|
28
|
+
<div style="margin-bottom: 5px">
|
29
|
+
<a href="https://slide.rabbit-shocker.org/authors/kou/mysql-and-postgresql-and-japanese-full-text-search-3/" title="Mroonga・PGroonga導入方法">Mroonga・PGroonga導入方法</a>
|
30
|
+
</div>
|
31
|
+
</div>
|
32
|
+
|
33
|
+
関連リンク:
|
34
|
+
|
35
|
+
* [スライド(Rabbit Slide Show)](https://slide.rabbit-shocker.org/authors/kou/mysql-and-postgresql-and-japanese-full-text-search-3/)
|
36
|
+
* [スライド(SlideShare)](http://www.slideshare.net/kou/mysql-and-postgresql-and-japanese-full-text-search-3)
|
37
|
+
* [リポジトリー](https://github.com/kou/rabbit-slide-kou-mysql-and-postgresql-and-japanese-full-text-search-3)
|
38
|
+
|
39
|
+
## Redmineへの導入方法
|
40
|
+
|
41
|
+
RedmineへのMroonga・PGroongaの導入方法を説明します。RedmineはRuby on Railsを利用しているのでRuby on Railsを使っているアプリケーションに導入する例ということになります。
|
42
|
+
|
43
|
+
[redmine\_full\_text\_searchプラグイン](20160411)を使うとRedmineでMroongaまたはPGroongaを使って全文検索できるようになります。
|
44
|
+
|
45
|
+
このプラグインを使うとRedmineの全文検索が高速になります。たとえば、クリアコードで使っているRedmineには3000件くらいのチケットがありますが、その環境では次のように高速になりました。
|
46
|
+
|
47
|
+
<table>
|
48
|
+
<thead>
|
49
|
+
<tr>
|
50
|
+
<th>プラグイン</th>
|
51
|
+
<th>時間</th>
|
52
|
+
</tr>
|
53
|
+
</thead>
|
54
|
+
<tbody>
|
55
|
+
<tr>
|
56
|
+
<td>なし</td>
|
57
|
+
<td>467ms</td>
|
58
|
+
</tr>
|
59
|
+
<tr>
|
60
|
+
<td>あり</td>
|
61
|
+
<td>93ms</td>
|
62
|
+
</tr>
|
63
|
+
</tbody>
|
64
|
+
</table>
|
65
|
+
|
66
|
+
200万件のチケットがある環境でも約380msで検索できているという報告もあります。
|
67
|
+
|
68
|
+
<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">200万チケット<a href="https://twitter.com/MySQL">@MySQL</a>でやってみたよ。検索時間は約380ms。 <a href="https://twitter.com/hashtag/Redmine?src=hash">#Redmine</a> の未来が広がって嬉しいな。ありがたいな。/Redmineで高速に全文検索する方法 - ククログ(2016-04-11) <a href="https://t.co/s7FA4gSThu">https://t.co/s7FA4gSThu</a> <a href="https://twitter.com/_clear_code">@_clear_code</a></p>— Kuniharu AKAHANE (@akahane92) <a href="https://twitter.com/akahane92/status/733832496945594368">2016年5月21日</a></blockquote>
|
69
|
+
<script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
|
70
|
+
|
71
|
+
### Mroongaを導入する方法
|
72
|
+
|
73
|
+
Mroongaはトランザクションに対応していないのでトランザクションが必須のRedmineに組み込む場合はひと工夫必要になります。単純に、`ALTER TABLE table ENGINE=Mroonga ADD FULLTEXT INDEX (column)`とするわけにはいきません。
|
74
|
+
|
75
|
+
ではどうするかというと別途全文検索用のテーブルを作成して元のテーブルとは`JOIN`できるようにします。(他にもレプリケーションしてレプリケーション先をMroongaにするという[2回目のイベントで紹介した方法](20160609)もありますが、プラグインでやるには大掛かりなのでこの方法を使っています。)
|
76
|
+
|
77
|
+
マイグレーションファイルでいうと次のようにします。ここでは`issues`テーブル用の全文検索用のテーブルを作成しています。
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
def up
|
81
|
+
create_table(:fts_issues, # 全文検索用テーブル作成
|
82
|
+
id: false, # idは有効・無効どっちでも可
|
83
|
+
options: "ENGINE=Mroonga") do |t|
|
84
|
+
t.belongs_to :issue, index: true, null: false
|
85
|
+
t.string :subject, default: "", null: false
|
86
|
+
t.text :description, limit: 65535, null: false
|
87
|
+
t.index [:subject, :description], type: "fulltext"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
全文検索用のテーブルには元のデータをコピーする必要があります。マイグレーション時には既存のデータを一気にコピーします。そのため、本当のマイグレーションの内容は次のようになります。データコピー後にインデックスを追加するようにしているのはそっちの方が速いからです。
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
def up
|
96
|
+
create_table(:fts_issues, # 全文検索用テーブル作成
|
97
|
+
id: false, # idは有効・無効どっちでも可
|
98
|
+
options: "ENGINE=Mroonga") do |t|
|
99
|
+
t.belongs_to :issue, index: true, null: false
|
100
|
+
t.string :subject, default: "", null: false
|
101
|
+
t.text :description, limit: 65535, null: false
|
102
|
+
end
|
103
|
+
execute("INSERT INTO " + # データをコピー
|
104
|
+
"fts_issues(issue_id, subject, description) " +
|
105
|
+
"SELECT id, subject, description FROM issues;")
|
106
|
+
add_index(:fts_issues, [:subject, :description],
|
107
|
+
type: "fulltext") # 静的インデックス構築(速い)
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
このテーブルのモデルは次のようになります。
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
class FtsIssue < ActiveRecord::Base
|
115
|
+
# 実際はissue_idカラムは主キーではない。
|
116
|
+
# 主キーなしのテーブルなので
|
117
|
+
# Active Recordをごまかしているだけ。
|
118
|
+
self.primary_key = :issue_id
|
119
|
+
belongs_to :issue
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
Mroonga導入後に更新されたデータはアプリケーション(Redmine)側でデータをコピーします。Active Recordの`after_save`フックを利用します。Mroongaがトランザクションをサポートしていないため、ロールバックのタイミングによってはデータに不整合が発生することがありますが、再度保存すれば復旧できることとそれほどロールバックは発生しないため、実運用時には問題になることはないでしょう。
|
124
|
+
|
125
|
+
```ruby
|
126
|
+
class Issue
|
127
|
+
# この後にロールバックされることがあるのでカンペキではない
|
128
|
+
# 再度同じチケットを更新するかデータを入れ直せば直る
|
129
|
+
after_save do |record|
|
130
|
+
fts_record =
|
131
|
+
FtsIssue.find_or_initialize_by(issue_id: record.id)
|
132
|
+
fts_record.subject = record.subject
|
133
|
+
fts_record.description = record.description
|
134
|
+
fts_record.save!
|
135
|
+
end
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
全文検索時は全文検索用のテーブルを`JOIN`して`MATCH AGAINST`を使います。
|
140
|
+
|
141
|
+
```ruby
|
142
|
+
issue.
|
143
|
+
joins(:fts_issue).
|
144
|
+
where(["MATCH(fts_issues.subject, " +
|
145
|
+
"fts_issues.description) " +
|
146
|
+
"AGAINST (? IN BOOLEAN MODE)",
|
147
|
+
# ↓デフォルトANDで全文検索
|
148
|
+
"*D+ #{keywords.join(', ')}"])
|
149
|
+
```
|
150
|
+
|
151
|
+
この説明はわかりやすさのために[実際の実装](https://github.com/okkez/redmine_full_text_search)を単純化しています。詳細が知りたい方は実装を確認してください。
|
152
|
+
|
153
|
+
### PGroongaを導入する方法
|
154
|
+
|
155
|
+
PGroongaはトランザクションに対応しているので別途全文検索用のテーブルを作成する必要はありません。既存のテーブルに全文検索用のインデックスを作成します。
|
156
|
+
|
157
|
+
マイグレーションファイルでいうと次のようにします。ここでは`issues`テーブルに全文検索用のインデックスを作成しています。`enable_extension("pgroonga")`はPGroongaを使えるようにするためのSQLです。
|
158
|
+
|
159
|
+
```ruby
|
160
|
+
def up
|
161
|
+
enable_extension("pgroonga")
|
162
|
+
add_index(:issues,
|
163
|
+
[:id, :subject, :description],
|
164
|
+
using: "pgroonga")
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
あとは検索時に全文検索条件をつけるだけです。
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
issue.
|
172
|
+
# 検索対象のカラムごとに
|
173
|
+
# クエリーを指定
|
174
|
+
where(["subject @@ ? OR " +
|
175
|
+
"description @@ ?",
|
176
|
+
keywords.join(", "),
|
177
|
+
keywords.join(", ")])
|
178
|
+
```
|
179
|
+
|
180
|
+
この説明もわかりやすさのために[実際の実装](https://github.com/okkez/redmine_full_text_search)を単純化しています。詳細が知りたい方は実装を確認してください。
|
181
|
+
|
182
|
+
## Zulipへの導入方法
|
183
|
+
|
184
|
+
ZulipへのPGroongaの導入方法を説明します。ZulipはPostgreSQLを使っているので、導入するのはPGroongaだけです。ZulipはDjangoを使っているのでDjangoを使っているアプリケーションに導入する例ということになります。
|
185
|
+
|
186
|
+
Zulipはチャットツールです。チャットツールなので小さなテキストの書き込みが頻繁に発生する傾向があります。各書き込みは十分速く完了する必要があります。書き込みが遅いとユーザーの不満が溜まりやすいからです。
|
187
|
+
|
188
|
+
Zulipは書き込みをできるだけ速くするためにインデックスの更新を遅延させています。インデックスの更新はデータの追加よりも重い処理なので、その処理を後回しにしているということです。(PGroongaは検索だけでなく更新も速いので遅延させずにリアルタイムで更新しても十分速いかもしれません。アプリケーションの要件次第でどのような実装にするか検討する必要があります。)
|
189
|
+
|
190
|
+
Zulipは、インデックスの更新を遅延させるため、カラムの値を直接全文検索対象にせずに、別途全文検索用のカラムを用意しています。その全文検索用のカラムの更新を後回しにすることでインデックスの更新を遅延させています。
|
191
|
+
|
192
|
+
マイグレーションファイルでいうと次のようにします。最初の`ALTER ROLE`はPGroongaが提供する`@@`という全文検索用のオペレーターの優先順位を調整するためのものです。本質ではないのでここでは気にしなくて構いません。
|
193
|
+
|
194
|
+
```python
|
195
|
+
migrations.RunSQL("""
|
196
|
+
ALTER ROLE zulip SET search_path
|
197
|
+
TO zulip,public,pgroonga,pg_catalog;
|
198
|
+
ALTER TABLE zerver_message
|
199
|
+
ADD COLUMN search_pgroonga text;
|
200
|
+
UPDATE zerver_message SET search_pgroonga =
|
201
|
+
subject || ' ' || rendered_content;
|
202
|
+
CREATE INDEX pgrn_index ON zerver_message
|
203
|
+
USING pgroonga(search_pg$roonga);
|
204
|
+
""", "...")
|
205
|
+
```
|
Binary file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rabbit-slide-kou-mysql-and-postgresql-and-japanese-full-text-search-3
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2016.9.29.
|
4
|
+
version: 2016.9.29.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 須藤功平
|
@@ -40,6 +40,7 @@ files:
|
|
40
40
|
- images/zulip-full-text-search-form.png
|
41
41
|
- images/zulip-highlight.png
|
42
42
|
- mroonga-and-pgroonga.rab
|
43
|
+
- mypgft3.md
|
43
44
|
- pdf/mysql-and-postgresql-and-japanese-full-text-search-3-mroonga-and-pgroonga.pdf
|
44
45
|
homepage: http://slide.rabbit-shocker.org/authors/kou/mysql-and-postgresql-and-japanese-full-text-search-3/
|
45
46
|
licenses:
|