SlideShare a Scribd company logo
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Mroongaと
PGroonga導入方法例
須藤功平 クリアコード
MySQLとPostgreSQLと日本語全文検索
2016-09-29
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Mroonga・PGroonga
Mroonga(むるんが)
MySQLに
高速日本語全文検索機能を追加する
プロダクト
PGroonga(ぴーじーるんが)
PostgreSQLに
高速日本語全文検索機能を追加する
プロダクト
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
高速?
Mroonga と PGroonga - Groongaを使って MySQLとPostgreSQLで日本語全文検索 Powered by Rabbit 2.1.9
速さ:検索1
キーワード:テレビアニメ
(ヒット数:約2万3千件)
InnoDB ngram 3m2s
InnoDB MeCab 6m20s
Mroonga:1 0.11s
pg_bigm 4s
PGroonga:2 0.29s
詳細は第1回の資料を参照
http://slide.rabbit-shocker.org/authors/kou/mysql-and-
postgresql-and-japanese-full-text-search/
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
導入方法例
既存システムへの導入方法を紹介
Redmine
チケット管理システム
Ruby on Railsを使用
Zulip
チャットツール
Djangoを使用
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Redmine
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
全文検索プラグイン
GitHub: okkez/redmine_full_text_search
MySQL・PostgreSQL両方対応
MySQLのときはMroongaを利用
PostgreSQLのときはPGroongaを利用
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
速さ
MySQL + Mroongaのケース
プラグイン チケット数 時間
なし 約3000件 467ms
あり 約3000件 93ms
あり 約200万件 380ms
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
速さ:コメント
https://twitter.com/akahane92/status/733832496945594368
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
使いどころ
Mroonga
速さが欲しい
トランザクションはいらない
PGroonga
機能が欲しい
トランザクションも欲しい
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Redmine
トランザクション必須
Mroongaを使うときは一工夫必要
PGroongaはそのままで大丈夫
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Redmine+Mroonga:方針
チケットテーブルは変えない
全文検索用テーブルを別途作成
全文検索用テーブルから
チケットテーブルを参照
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
マイグレーション
def up
create_table(:fts_issues, # 全文検索用テーブル作成
id: false, # idは有効・無効どっちでも可
options: "ENGINE=Mroonga") do |t|
t.belongs_to :issue, index: true, null: false
t.string :subject, default: "", null: false
t.text :description, limit: 65535, null: false
end
execute("INSERT INTO " + # データをコピー
"fts_issues(issue_id, subject, description) " +
"SELECT id, subject, description FROM issues;")
add_index(:fts_issues, [:subject, :description],
type: "fulltext") # 静的インデックス構築(速い)
end
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
モデル
class FtsIssue < ActiveRecord::Base
# 実際はissue_idカラムは主キーではない。
# 主キーなしのテーブルなので
# Active Recordをごまかしているだけ。
self.primary_key = :issue_id
belongs_to :issue
end
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
保存
class Issue
# この後にロールバックされることがあるのでカンペキではない
# 再度同じチケットを更新するかデータを入れ直せば直る
after_save do |record|
fts_record =
FtsIssue.find_or_initialize_by(issue_id: record.id)
fts_record.subject = record.subject
fts_record.description = record.description
fts_record.save!
end
end
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
全文検索
issue.
joins(:fts_issue).
where(["MATCH(fts_issues.subject, " +
"fts_issues.description) " +
"AGAINST (? IN BOOLEAN MODE)",
# ↓デフォルトANDで全文検索
"*D+ #{keywords.join(', ')}"])
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Redmine+Mroonga:まとめ
トランザクション必須
元テーブルを置き換えない
全文検索用テーブルを作成
データ
アプリが複数テーブルに保存
全文検索
JOINしてMATCH AGAINST
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Redmine+PGroonga:方針
全文検索用インデックス作成
インデックスに主キーを含める
検索スコアーを取得するため
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
マイグレーション
def up
enable_extension("pgroonga")
add_index(:issues,
[:id, :subject, :description],
using: "pgroonga")
end
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
モデル
追加・変更なし
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
保存
追加・変更なし
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
全文検索
issue.
# 検索対象のカラムごとに
# クエリーを指定
where(["subject @@ ? OR " +
"description @@ ?",
keywords.join(", "),
keywords.join(", ")])
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Redmine+PGroonga:まとめ
インデックス追加のみでOK
トランザクション対応
データ保存も変更なし
全文検索
カラム1 @@ 'クエリー' OR
カラム2 @@ 'クエリー' OR ...
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Redmine:まとめ
速い!
Mroonga
全文検索用テーブルで実現
PGroonga
全文検索用インデックスで実現
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Zulip
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Zulipの全文検索機能
現在:textsearch
PostgreSQL標準機能
英語のみ対応
NEW!:PGroonga
オプション(=PGroongaに切替可)
全言語対応(日本語を含む)
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Zulip+PGroonga:方針
書き込み速度を落とさない
チャットは書き込みが遅いと微妙
インデックスは裏で更新
PGroongaならリアルタイム更新でも大丈夫かも
詳細:https://github.com/zulip/zulip/pull/700/files
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
マイグレーション
migrations.RunSQL("""
ALTER ROLE zulip SET search_path
TO zulip,public,pgroonga,pg_catalog;
ALTER TABLE zerver_message
ADD COLUMN search_pgroonga text;
UPDATE zerver_message SET search_pgroonga =
subject || ' ' || rendered_content;
CREATE INDEX pgrn_index ON zerver_message
USING pgroonga(search_pgroonga);
""", "...")
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
遅延インデックス更新
メッセージ追加・更新時にログ
別プロセスでログを監視
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
追加・更新時にログ
CREATE FUNCTION append_to_fts_update_log()
RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
INSERT INTO fts_update_log (message_id)
VALUES (NEW.id);
RETURN NEW;
END
$$;
CREATE TRIGGER update_fts_index_async
BEFORE INSERT OR UPDATE OF
subject, rendered_content ON zerver_message
FOR EACH ROW
EXECUTE PROCEDURE append_to_fts_update_log();
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
別プロセスに通知
CREATE FUNCTION do_notify_fts_update_log()
RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
NOTIFY fts_update_log;
RETURN NEW;
END
$$;
CREATE TRIGGER fts_update_log_notify
AFTER INSERT ON fts_update_log
FOR EACH STATEMENT
EXECUTE PROCEDURE do_notify_fts_update_log();
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
インデックス更新プロセス
import psycopg2
conn = psycopg2.connect("user=zulip")
cursor = conn.cursor
cursor.execute("LISTEN fts_update_log;")
while True:
if select.select([conn], [], [], 30) != ([], [], []):
conn.poll()
while conn.notifies:
conn.notifies.pop()
update_fts_columns(cursor)
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
インデックス更新
def update_fts_columns(cursor):
cursor.execute("SELECT id, message_id "
"FROM fts_update_log;")
ids = []
for (id, message_id) in cursor.fetchall():
cursor.execute("UPDATE zerver_message SET "
"search_pgroonga = "
"subject || ' ' || rendered_content "
"WHERE id = %s", (message_id,))
ids.append(id)
cursor.execute("DELETE FROM fts_update_log "
"WHERE id = ANY(%s)", (ids,))
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
全文検索
from sqlalchemy.sql import column
def _by_search_pgroonga(self, query, operand):
# WHERE search_pgroonga @@ 'クエリー'
target = column("search_pgroonga")
condition = target.op("@@")(operand)
return query.where(condition)
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
ハイライト
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
ハイライト:SQL
SELECT
pgroonga.match_positions_byte(
rendered_content,
pgroonga.query_extract_keywords('クエリー'))
AS content_matches,
pgroonga.match_positions_byte(
subject,
pgroonga.query_extract_keywords('クエリー'))
AS subject_matches
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
ハイライト:SQLAlchemy
from sqlalchemy import func
def _by_search_pgroonga(self, query, operand):
match_positions_byte = func.pgroonga.match_positions_byte
query_extract_keywords = func.pgroonga.query_extract_keywords
keywords = query_extract_keywords(operand)
query = query.column(
match_positions_byte(column("rendered_content"),
keywords).label("content_matches"))
query = query.column(
match_positions_byte(column("subject"),
keywords).label("subject_matches"))
# ...
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
ハイライト:Python
def highlight_string_bytes_offsets(text, locs):
# type: (AnyStr, Iterable[Tuple[int, int]]) -> text_type
string = text.encode('utf-8')
highlight_start = b'<span class="highlight">'
highlight_stop = b'</span>'
pos = 0
result = b''
for loc in locs:
(offset, length) = loc
result += string[pos:offset]
result += highlight_start
result += string[offset:offset + length]
result += highlight_stop
pos = offset + length
result += string[pos:]
return result.decode('utf-8')
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
ハイライト:補足
通常はハイライト関数で十分
pgroonga.highlight_html
ただしts_headlineでは不十分
HTML出力に使えない
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
ハイライト:ts_headline
SELECT
ts_headline('english',
'PostgreSQL <is> great!',
to_tsquery('PostgreSQL'),
'HighlightAll=TRUE');
-- ts_headline
-- -------------------------------
-- <b>PostgreSQL</b> <is> great!
-- (1 row) 不正なHTML↑
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
ハイライト:
pgroonga.highlight_html
SELECT
pgroonga.highlight_html(
'PostgreSQL <is> great!',
pgroonga.query_extract_keywords('PostgreSQL'));
-- highlight_html
-- ----------------------------------------
-- <span class="keyword">PostgreSQL</span>
-- &lt;is&gt; great!
-- ↑ ↑HTMLエスケープされている
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
Zulip:まとめ
全言語対応全文検索
textsearch(1言語のみ)→
PGroonga(全言語)
書き込み性能は維持
遅延インデックス更新
Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0
まとめ
Mroonga・PGroongaの
導入方法を実例ベースで紹介
Redmine:チケット管理システム
Zulip:チャットツール
トランザクション必須の場合
Mroonga:別テーブル作成
PGroonga:インデックス追加

More Related Content

What's hot (20)

PDF
初心者向けMroonga・PGroonga情報
Kouhei Sutou
 
PDF
MySQL 8.0でMroonga
Kouhei Sutou
 
PDF
MroongaとPGroonga
Kouhei Sutou
 
PDF
Mroongaの高速全文検索機能でWordPress内のコンテンツを有効活用!
Kouhei Sutou
 
PDF
PGroongaの実装
Kouhei Sutou
 
PDF
Groonga族2015
Kouhei Sutou
 
PDF
Mroonga開発者が来たぞ!
Kouhei Sutou
 
PDF
いろいろ考えると日本語の全文検索もMySQLがいいね!
Kouhei Sutou
 
PDF
RubyもApache Arrowでデータ処理言語の仲間入り
Kouhei Sutou
 
PDF
Groonga族2016
Kouhei Sutou
 
PDF
[Postgre sql9.4新機能]レプリケーション・スロットの活用
Kosuke Kida
 
PDF
PostgreSQLレプリケーション(pgcon17j_t4)
Kosuke Kida
 
PDF
MySQL Casual Talks Vol.4 「MySQL-5.6で始める全文検索 〜InnoDB FTS編〜」
Kentaro Yoshida
 
PDF
pg_bigm(ピージー・バイグラム)を用いた全文検索のしくみ(後編)
Masahiko Sawada
 
PDF
JSONBはPostgreSQL9.5でいかに改善されたのか
NTT DATA OSS Professional Services
 
PDF
20120405 setsunaセミナー
Takahiro Iwase
 
PDF
Spark Streaming と Spark GraphX を使用したTwitter解析による レコメンドサービス例
Junichi Noda
 
PDF
20160929_InnoDBの全文検索を使ってみた by 株式会社インサイトテクノロジー 中村範夫
Insight Technology, Inc.
 
PDF
MongoDBを用いたソーシャルアプリのログ解析 〜解析基盤構築からフロントUIまで、MongoDBを最大限に活用する〜
Takahiro Inoue
 
PDF
MongoDBのはじめての運用テキスト
Akihiro Kuwano
 
初心者向けMroonga・PGroonga情報
Kouhei Sutou
 
MySQL 8.0でMroonga
Kouhei Sutou
 
MroongaとPGroonga
Kouhei Sutou
 
Mroongaの高速全文検索機能でWordPress内のコンテンツを有効活用!
Kouhei Sutou
 
PGroongaの実装
Kouhei Sutou
 
Groonga族2015
Kouhei Sutou
 
Mroonga開発者が来たぞ!
Kouhei Sutou
 
いろいろ考えると日本語の全文検索もMySQLがいいね!
Kouhei Sutou
 
RubyもApache Arrowでデータ処理言語の仲間入り
Kouhei Sutou
 
Groonga族2016
Kouhei Sutou
 
[Postgre sql9.4新機能]レプリケーション・スロットの活用
Kosuke Kida
 
PostgreSQLレプリケーション(pgcon17j_t4)
Kosuke Kida
 
MySQL Casual Talks Vol.4 「MySQL-5.6で始める全文検索 〜InnoDB FTS編〜」
Kentaro Yoshida
 
pg_bigm(ピージー・バイグラム)を用いた全文検索のしくみ(後編)
Masahiko Sawada
 
JSONBはPostgreSQL9.5でいかに改善されたのか
NTT DATA OSS Professional Services
 
20120405 setsunaセミナー
Takahiro Iwase
 
Spark Streaming と Spark GraphX を使用したTwitter解析による レコメンドサービス例
Junichi Noda
 
20160929_InnoDBの全文検索を使ってみた by 株式会社インサイトテクノロジー 中村範夫
Insight Technology, Inc.
 
MongoDBを用いたソーシャルアプリのログ解析 〜解析基盤構築からフロントUIまで、MongoDBを最大限に活用する〜
Takahiro Inoue
 
MongoDBのはじめての運用テキスト
Akihiro Kuwano
 

Similar to Mroonga・PGroonga導入方法 (20)

PDF
GroongaでRedmineを高速全文検索
Kouhei Sutou
 
PDF
Mroonga Meetup 2014/06/29
kenhys
 
PDF
Groonga Meetup 2014/04/29
kenhys
 
PPTX
MySQL対応全文検索システムMroonga(むるんが)
Hideshi Ogoshi
 
PDF
Mroonga 20121129
Kentoku
 
PDF
Groonga族2014
Kouhei Sutou
 
PDF
Mroonga de fts
yoku0825
 
PDF
全文検索でRedmineをさらに活用!
Kouhei Sutou
 
PDF
Mroonga!
Kouhei Sutou
 
PDF
Groongaの特徴
Kouhei Sutou
 
PDF
Groongaの紹介と事例紹介
Naoya Murakami
 
PDF
blogサービスの全文検索の話 - #groonga を囲む夕べ
Masahiro Nagano
 
PDF
Mysql+Mroongaで全文検索
yoyamasaki
 
PDF
第7回フクオカRuby大賞本審査資料:全文検索エンジンGroongaへのmrubyの組み込み
Kouhei Sutou
 
PDF
Droongaのはじめかた
Kouhei Sutou
 
PDF
Introducing mroonga 20111129
Kentoku
 
PDF
Mroonga 20131129
Kentoku
 
PDF
[data analytics showcase] B14: 文字情報の分析基盤 Mroonga by 株式会社インサイトテクノロジー 小幡 一郎
Insight Technology, Inc.
 
PDF
Groongaを支える取り組み
kenhys
 
PDF
Rroongaの全文検索性能 Ruby kansai-20140531-ruby-kansai-20140531
Naoya Murakami
 
GroongaでRedmineを高速全文検索
Kouhei Sutou
 
Mroonga Meetup 2014/06/29
kenhys
 
Groonga Meetup 2014/04/29
kenhys
 
MySQL対応全文検索システムMroonga(むるんが)
Hideshi Ogoshi
 
Mroonga 20121129
Kentoku
 
Groonga族2014
Kouhei Sutou
 
Mroonga de fts
yoku0825
 
全文検索でRedmineをさらに活用!
Kouhei Sutou
 
Mroonga!
Kouhei Sutou
 
Groongaの特徴
Kouhei Sutou
 
Groongaの紹介と事例紹介
Naoya Murakami
 
blogサービスの全文検索の話 - #groonga を囲む夕べ
Masahiro Nagano
 
Mysql+Mroongaで全文検索
yoyamasaki
 
第7回フクオカRuby大賞本審査資料:全文検索エンジンGroongaへのmrubyの組み込み
Kouhei Sutou
 
Droongaのはじめかた
Kouhei Sutou
 
Introducing mroonga 20111129
Kentoku
 
Mroonga 20131129
Kentoku
 
[data analytics showcase] B14: 文字情報の分析基盤 Mroonga by 株式会社インサイトテクノロジー 小幡 一郎
Insight Technology, Inc.
 
Groongaを支える取り組み
kenhys
 
Rroongaの全文検索性能 Ruby kansai-20140531-ruby-kansai-20140531
Naoya Murakami
 
Ad

More from Kouhei Sutou (19)

PDF
RubyKaigi 2022 - Fast data processing with Ruby and Apache Arrow
Kouhei Sutou
 
PDF
Apache Arrow Flight – ビッグデータ用高速データ転送フレームワーク #dbts2021
Kouhei Sutou
 
PDF
RubyKaigi Takeout 2021 - Red Arrow - Ruby and Apache Arrow
Kouhei Sutou
 
PDF
Rubyと仕事と自由なソフトウェア
Kouhei Sutou
 
PDF
Apache Arrowフォーマットはなぜ速いのか
Kouhei Sutou
 
PDF
Apache Arrow 1.0 - A cross-language development platform for in-memory data
Kouhei Sutou
 
PDF
Apache Arrow 2019
Kouhei Sutou
 
PDF
Redmine検索の未来像
Kouhei Sutou
 
PDF
Apache Arrow - A cross-language development platform for in-memory data
Kouhei Sutou
 
PDF
Better CSV processing with Ruby 2.6
Kouhei Sutou
 
PDF
Apache Arrow
Kouhei Sutou
 
PDF
Apache Arrow - データ処理ツールの次世代プラットフォーム
Kouhei Sutou
 
PDF
Apache Arrow
Kouhei Sutou
 
PDF
My way with Ruby
Kouhei Sutou
 
PDF
Red Data Tools
Kouhei Sutou
 
PDF
PGroonga 2 – Make PostgreSQL rich full text search system backend!
Kouhei Sutou
 
PDF
Improve extension API: C++ as better language for extension
Kouhei Sutou
 
PDF
PGroonga & Zulip
Kouhei Sutou
 
PDF
株式会社クリアコード
Kouhei Sutou
 
RubyKaigi 2022 - Fast data processing with Ruby and Apache Arrow
Kouhei Sutou
 
Apache Arrow Flight – ビッグデータ用高速データ転送フレームワーク #dbts2021
Kouhei Sutou
 
RubyKaigi Takeout 2021 - Red Arrow - Ruby and Apache Arrow
Kouhei Sutou
 
Rubyと仕事と自由なソフトウェア
Kouhei Sutou
 
Apache Arrowフォーマットはなぜ速いのか
Kouhei Sutou
 
Apache Arrow 1.0 - A cross-language development platform for in-memory data
Kouhei Sutou
 
Apache Arrow 2019
Kouhei Sutou
 
Redmine検索の未来像
Kouhei Sutou
 
Apache Arrow - A cross-language development platform for in-memory data
Kouhei Sutou
 
Better CSV processing with Ruby 2.6
Kouhei Sutou
 
Apache Arrow
Kouhei Sutou
 
Apache Arrow - データ処理ツールの次世代プラットフォーム
Kouhei Sutou
 
Apache Arrow
Kouhei Sutou
 
My way with Ruby
Kouhei Sutou
 
Red Data Tools
Kouhei Sutou
 
PGroonga 2 – Make PostgreSQL rich full text search system backend!
Kouhei Sutou
 
Improve extension API: C++ as better language for extension
Kouhei Sutou
 
PGroonga & Zulip
Kouhei Sutou
 
株式会社クリアコード
Kouhei Sutou
 
Ad

Recently uploaded (6)

PDF
20250711JIMUC総会_先進IT運用管理分科会Connpass公開資料.pdf
ChikakoInami1
 
PDF
PostgreSQL18新機能紹介(db tech showcase 2025 発表資料)
NTT DATA Technology & Innovation
 
PDF
20250711_日本IBM ミドルウエア・ユーザー研究会(JIMUC)総会_中村会長資料.pdf
ChikakoInami1
 
PPTX
Devcontainerのススメ(1)-Devcontainerとはどういう技術?-
iPride Co., Ltd.
 
PDF
20250717_Devin×GitHubCopilotで10人分の仕事は出来るのか?.pdf
Masaki Yamakawa
 
PDF
20250711JIMUC総会IBM Automation_Platform最新情報_Connpass公開版.pdf
ChikakoInami1
 
20250711JIMUC総会_先進IT運用管理分科会Connpass公開資料.pdf
ChikakoInami1
 
PostgreSQL18新機能紹介(db tech showcase 2025 発表資料)
NTT DATA Technology & Innovation
 
20250711_日本IBM ミドルウエア・ユーザー研究会(JIMUC)総会_中村会長資料.pdf
ChikakoInami1
 
Devcontainerのススメ(1)-Devcontainerとはどういう技術?-
iPride Co., Ltd.
 
20250717_Devin×GitHubCopilotで10人分の仕事は出来るのか?.pdf
Masaki Yamakawa
 
20250711JIMUC総会IBM Automation_Platform最新情報_Connpass公開版.pdf
ChikakoInami1
 

Mroonga・PGroonga導入方法

  • 1. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Mroongaと PGroonga導入方法例 須藤功平 クリアコード MySQLとPostgreSQLと日本語全文検索 2016-09-29
  • 2. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Mroonga・PGroonga Mroonga(むるんが) MySQLに 高速日本語全文検索機能を追加する プロダクト PGroonga(ぴーじーるんが) PostgreSQLに 高速日本語全文検索機能を追加する プロダクト
  • 3. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 高速? Mroonga と PGroonga - Groongaを使って MySQLとPostgreSQLで日本語全文検索 Powered by Rabbit 2.1.9 速さ:検索1 キーワード:テレビアニメ (ヒット数:約2万3千件) InnoDB ngram 3m2s InnoDB MeCab 6m20s Mroonga:1 0.11s pg_bigm 4s PGroonga:2 0.29s 詳細は第1回の資料を参照 http://slide.rabbit-shocker.org/authors/kou/mysql-and- postgresql-and-japanese-full-text-search/
  • 4. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 導入方法例 既存システムへの導入方法を紹介 Redmine チケット管理システム Ruby on Railsを使用 Zulip チャットツール Djangoを使用
  • 5. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Redmine
  • 6. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 全文検索プラグイン GitHub: okkez/redmine_full_text_search MySQL・PostgreSQL両方対応 MySQLのときはMroongaを利用 PostgreSQLのときはPGroongaを利用
  • 7. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 速さ MySQL + Mroongaのケース プラグイン チケット数 時間 なし 約3000件 467ms あり 約3000件 93ms あり 約200万件 380ms
  • 8. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 速さ:コメント https://twitter.com/akahane92/status/733832496945594368
  • 9. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 使いどころ Mroonga 速さが欲しい トランザクションはいらない PGroonga 機能が欲しい トランザクションも欲しい
  • 10. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Redmine トランザクション必須 Mroongaを使うときは一工夫必要 PGroongaはそのままで大丈夫
  • 11. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Redmine+Mroonga:方針 チケットテーブルは変えない 全文検索用テーブルを別途作成 全文検索用テーブルから チケットテーブルを参照
  • 12. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 マイグレーション def up create_table(:fts_issues, # 全文検索用テーブル作成 id: false, # idは有効・無効どっちでも可 options: "ENGINE=Mroonga") do |t| t.belongs_to :issue, index: true, null: false t.string :subject, default: "", null: false t.text :description, limit: 65535, null: false end execute("INSERT INTO " + # データをコピー "fts_issues(issue_id, subject, description) " + "SELECT id, subject, description FROM issues;") add_index(:fts_issues, [:subject, :description], type: "fulltext") # 静的インデックス構築(速い) end
  • 13. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 モデル class FtsIssue < ActiveRecord::Base # 実際はissue_idカラムは主キーではない。 # 主キーなしのテーブルなので # Active Recordをごまかしているだけ。 self.primary_key = :issue_id belongs_to :issue end
  • 14. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 保存 class Issue # この後にロールバックされることがあるのでカンペキではない # 再度同じチケットを更新するかデータを入れ直せば直る after_save do |record| fts_record = FtsIssue.find_or_initialize_by(issue_id: record.id) fts_record.subject = record.subject fts_record.description = record.description fts_record.save! end end
  • 15. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 全文検索 issue. joins(:fts_issue). where(["MATCH(fts_issues.subject, " + "fts_issues.description) " + "AGAINST (? IN BOOLEAN MODE)", # ↓デフォルトANDで全文検索 "*D+ #{keywords.join(', ')}"])
  • 16. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Redmine+Mroonga:まとめ トランザクション必須 元テーブルを置き換えない 全文検索用テーブルを作成 データ アプリが複数テーブルに保存 全文検索 JOINしてMATCH AGAINST
  • 17. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Redmine+PGroonga:方針 全文検索用インデックス作成 インデックスに主キーを含める 検索スコアーを取得するため
  • 18. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 マイグレーション def up enable_extension("pgroonga") add_index(:issues, [:id, :subject, :description], using: "pgroonga") end
  • 19. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 モデル 追加・変更なし
  • 20. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 保存 追加・変更なし
  • 21. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 全文検索 issue. # 検索対象のカラムごとに # クエリーを指定 where(["subject @@ ? OR " + "description @@ ?", keywords.join(", "), keywords.join(", ")])
  • 22. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Redmine+PGroonga:まとめ インデックス追加のみでOK トランザクション対応 データ保存も変更なし 全文検索 カラム1 @@ 'クエリー' OR カラム2 @@ 'クエリー' OR ...
  • 23. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Redmine:まとめ 速い! Mroonga 全文検索用テーブルで実現 PGroonga 全文検索用インデックスで実現
  • 24. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Zulip
  • 25. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Zulipの全文検索機能 現在:textsearch PostgreSQL標準機能 英語のみ対応 NEW!:PGroonga オプション(=PGroongaに切替可) 全言語対応(日本語を含む)
  • 26. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Zulip+PGroonga:方針 書き込み速度を落とさない チャットは書き込みが遅いと微妙 インデックスは裏で更新 PGroongaならリアルタイム更新でも大丈夫かも 詳細:https://github.com/zulip/zulip/pull/700/files
  • 27. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 マイグレーション migrations.RunSQL(""" ALTER ROLE zulip SET search_path TO zulip,public,pgroonga,pg_catalog; ALTER TABLE zerver_message ADD COLUMN search_pgroonga text; UPDATE zerver_message SET search_pgroonga = subject || ' ' || rendered_content; CREATE INDEX pgrn_index ON zerver_message USING pgroonga(search_pgroonga); """, "...")
  • 28. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 遅延インデックス更新 メッセージ追加・更新時にログ 別プロセスでログを監視
  • 29. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 追加・更新時にログ CREATE FUNCTION append_to_fts_update_log() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN INSERT INTO fts_update_log (message_id) VALUES (NEW.id); RETURN NEW; END $$; CREATE TRIGGER update_fts_index_async BEFORE INSERT OR UPDATE OF subject, rendered_content ON zerver_message FOR EACH ROW EXECUTE PROCEDURE append_to_fts_update_log();
  • 30. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 別プロセスに通知 CREATE FUNCTION do_notify_fts_update_log() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NOTIFY fts_update_log; RETURN NEW; END $$; CREATE TRIGGER fts_update_log_notify AFTER INSERT ON fts_update_log FOR EACH STATEMENT EXECUTE PROCEDURE do_notify_fts_update_log();
  • 31. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 インデックス更新プロセス import psycopg2 conn = psycopg2.connect("user=zulip") cursor = conn.cursor cursor.execute("LISTEN fts_update_log;") while True: if select.select([conn], [], [], 30) != ([], [], []): conn.poll() while conn.notifies: conn.notifies.pop() update_fts_columns(cursor)
  • 32. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 インデックス更新 def update_fts_columns(cursor): cursor.execute("SELECT id, message_id " "FROM fts_update_log;") ids = [] for (id, message_id) in cursor.fetchall(): cursor.execute("UPDATE zerver_message SET " "search_pgroonga = " "subject || ' ' || rendered_content " "WHERE id = %s", (message_id,)) ids.append(id) cursor.execute("DELETE FROM fts_update_log " "WHERE id = ANY(%s)", (ids,))
  • 33. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 全文検索 from sqlalchemy.sql import column def _by_search_pgroonga(self, query, operand): # WHERE search_pgroonga @@ 'クエリー' target = column("search_pgroonga") condition = target.op("@@")(operand) return query.where(condition)
  • 34. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 ハイライト
  • 35. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 ハイライト:SQL SELECT pgroonga.match_positions_byte( rendered_content, pgroonga.query_extract_keywords('クエリー')) AS content_matches, pgroonga.match_positions_byte( subject, pgroonga.query_extract_keywords('クエリー')) AS subject_matches
  • 36. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 ハイライト:SQLAlchemy from sqlalchemy import func def _by_search_pgroonga(self, query, operand): match_positions_byte = func.pgroonga.match_positions_byte query_extract_keywords = func.pgroonga.query_extract_keywords keywords = query_extract_keywords(operand) query = query.column( match_positions_byte(column("rendered_content"), keywords).label("content_matches")) query = query.column( match_positions_byte(column("subject"), keywords).label("subject_matches")) # ...
  • 37. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 ハイライト:Python def highlight_string_bytes_offsets(text, locs): # type: (AnyStr, Iterable[Tuple[int, int]]) -> text_type string = text.encode('utf-8') highlight_start = b'<span class="highlight">' highlight_stop = b'</span>' pos = 0 result = b'' for loc in locs: (offset, length) = loc result += string[pos:offset] result += highlight_start result += string[offset:offset + length] result += highlight_stop pos = offset + length result += string[pos:] return result.decode('utf-8')
  • 38. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 ハイライト:補足 通常はハイライト関数で十分 pgroonga.highlight_html ただしts_headlineでは不十分 HTML出力に使えない
  • 39. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 ハイライト:ts_headline SELECT ts_headline('english', 'PostgreSQL <is> great!', to_tsquery('PostgreSQL'), 'HighlightAll=TRUE'); -- ts_headline -- ------------------------------- -- <b>PostgreSQL</b> <is> great! -- (1 row) 不正なHTML↑
  • 40. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 ハイライト: pgroonga.highlight_html SELECT pgroonga.highlight_html( 'PostgreSQL <is> great!', pgroonga.query_extract_keywords('PostgreSQL')); -- highlight_html -- ---------------------------------------- -- <span class="keyword">PostgreSQL</span> -- &lt;is&gt; great! -- ↑ ↑HTMLエスケープされている
  • 41. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 Zulip:まとめ 全言語対応全文検索 textsearch(1言語のみ)→ PGroonga(全言語) 書き込み性能は維持 遅延インデックス更新
  • 42. Mroonga と PGroonga - 導入方法例 Powered by Rabbit 2.2.0 まとめ Mroonga・PGroongaの 導入方法を実例ベースで紹介 Redmine:チケット管理システム Zulip:チャットツール トランザクション必須の場合 Mroonga:別テーブル作成 PGroonga:インデックス追加