初めまして、
MySQLで完結する日本語対応の全文検索プロダクトである
そこで、
1ヶ月間の検証期間で見つかった総計16にも及ぶ機能追加や不具合改善を、
数回に渡り、
Tritonnが実現できたことと、残された課題
MySQLが標準で備える全文検索機能は簡素なもので、
Tritonnは、
- 実現できたこと
- MeCabやN-gramでのトークナイズに対応
- 完全転置インデックスを用いた高速な検索を実現
- 転置インデックスの一部をmmapすることで、
データ更新速度の大幅改善を実現 - MeCabを用いてトークナイズした形態素を単語として転置インデックスとすることで、
高精度を実現 - SQLならではの複雑な問い合わせにも対応できる柔軟性を維持
- 残された課題
- パフォーマンス問題
- LIMIT句, COUNT(*), OR条件追加時の検索が遅い
- MySQLが持つ1テーブル1インデックスという制約を受け、
全文検索以外の条件を加えて絞り込むと遅い - MyISAMを利用しているため、
更新中はテーブルロックとなり参照クエリを発行できない
- 信頼性の問題
- ストレージエンジンがMyISAMのため、
トランザクションに対応していない
- ストレージエンジンがMyISAMのため、
- パフォーマンス問題
これらの課題を解決すべく、
次の記事も参照ください。
Tritonn, mroonga以外のMySQLで完結する全文検索プロダクト
mroonga移行を検討している際に見つけた、
InnoDB fulltext search (InnoDB FTS)
MySQL 5.
そのため、
より詳細な情報は次の記事を参照ください。
- InnoDB Full-Text Search is in MySQL 5.
6.4 - InnoDB Full-text Search in MySQL 5.
6 (part 1) - MySQL 5.
6.4からの新機能 「InnoDB FullText Search」 を用いた全文検索エンジンのベンチマークLTをしました。#mysqlcasual
MySQL-ftppc
MySQL-ftppcとは、
これは5つのトークナイザに対応しており、
パフォーマンスとしてはXeon L5520のマシンで400万行、
より詳細な情報は次の記事を参照ください。
- 全文検索エンジンTritonn (MySQL 5.
0+Senna) からMySQL 5. 6対応 「mysqlftppc」 への移行ガイド - ニコニコニュースでも使われている全文検索パーサプラグイン
「MySQL-ftppc」 のMySQL 5. 6対応版を作りました
機能比較
これまでに取り上げた4つのプロダクト、
Tritonn | mroonga | MySQL-ftppc | InnoDB FTS | |
---|---|---|---|---|
InnoDB | × | ○ | × | ○ |
MyISAM | ○ | ○ | ○ | × |
MeCab | ○ | ○ | ○ | × |
N-gram | ○ | ◎ | ○ | × |
Dプラグマ | ○ | ○ | × | × |
Eプラグマ | ○ | × | × | × |
Wプラグマ | ○ | ○ | × | × |
MySQL 5. |
○ | × | × | × |
MySQL 5. |
× | ○ | ○ | × |
MySQL 5. |
× | ○ | ○ | × |
MySQL 5. |
× | ○ | ○ | ○ |
mroongaのN-gramについては、mysql-mroonga-3.
にて、
Tritonnから乗り換えるならば、
Tritonnからmroongaへ移行する7つのメリット
それでは早速、
MySQL 5.6対応であること
もはやレガシーとなったMySQL 5.
- CPUスケーラビリティの向上
- サブクエリを始めとするオプティマイザの最適化
- GTIDを利用したフェイルオーバー
- memcached API対応
- トランザクション対応である
- クラッシュセーフである
- 更新と参照が入り乱れた場合の同時実行性能が良い
- 以下に該当するクエリを利用している
- INSERT IGNORE INTO ...
- INSERT INTO ... ON DUPLICATE KEY UPDATE ...
- LOAD DATA ... IGNORE INTO ...
- 行削除するケースがある
より詳細な情報は次の記事を参照ください。
参照ロックフリー
ストレージモードでは、
ストレージエンジンにInnoDBを利用できること
mroongaのラッパーモードを利用すれば、
InnoDBの主な特徴としては次の通りです。
MySQL 5.
プラガブルストレージエンジンである
アップデートが容易なプラガブルストレージエンジン、
bigram以外の豊富なN-gramトークナイザが利用可能
Tritonnではmecab、
デフォルトはparser "TokenBigram"
が指定されているものとして動作しますが、
CREATE TABLE search (
id INT PRIMARY KEY AUTO_INCREMENT,
content VARCHAR(255),
FULLTEXT INDEX (content) COMMENT 'parser "TokenUnigram"'
) ENGINE=mroonga DEFAULT CHARSET=utf8;
より強化された文字列正規化機能が利用可能
Tritonnでは、NO NORMALIZE
と指定することで無効化される挙動でした。
mroongaでは全文検索機能を完全に外部で持つため、
しかしながら、groonga-normalizer-mysql
パッケージをインストールの上、COMMENT 'normalizer "NormalizerMySQLGeneralCI"'
のように指定しましょう。
CREATE TABLE search (
id INT PRIMARY KEY AUTO_INCREMENT,
content VARCHAR(255),
FULLTEXT INDEX (content) COMMENT 'normalizer "NormalizerMySQLGeneralCI"'
) ENGINE=mroonga DEFAULT CHARSET=utf8;
より具体的な挙動の違いに関しては、
位置情報検索に対応
mroongaのストレージモード・
テーブルのスキーマは次のように定義します。
-- ストレージモードでは、POINT型を指定する
CREATE TABLE shops (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
location POINT NOT NULL,
SPATIAL INDEX (location)
) ENGINE = mroonga;
-- InnoDBラッパーモードでは、GEOMETRY型を指定する
CREATE TABLE shops (
id INT PRIMARY KEY AUTO_INCREMENT,
name TEXT,
location GEOMETRY NOT NULL,
SPATIAL INDEX location_index (location)
) ENGINE = mroonga comment = 'engine "innodb"';
データの登録・
-- データの登録例
-- GeomFromText()関数を利用し、文字列からPOINT型に変換します
INSERT INTO shops VALUES (null, 'Naniwaya', GeomFromText('POINT(139.796234 35.730061)'));
-- データの検索例
-- 池袋駅(139.7101 35.7292)を左上の点、東京駅(139.7662 35.6815)を右下の点とした長方形内にあるお店を探す場合のクエリです
SELECT id, name, AsText(location) AS location_text FROM shops
WHERE MBRContains(GeomFromText('LineString(139.7101 35.7292, 139.7662 35.6815)'), location);
ただし、
汎用的に矩形での位置情報検索をするなら、
移行する際に気をつけたいポイント
ストレージモードとラッパーモード
ストレージモードとは、
そういった事情もあり、
ラッパーモードで使うストレージエンジン
COMMENT='engine "innodb"'
のように、
CREATE TABLE search (
id INT PRIMARY KEY AUTO_INCREMENT,
content VARCHAR(255),
FULLTEXT INDEX (content)
) ENGINE=mroonga DEFAULT CHARSET=utf8 COMMENT='engine "innodb"';
ストレージエンジンをMyISAMからInnoDBへ切り替える際の注意
mroongaと間接的に関わる話として、
具体的にどのような挙動の違いがあるか、
-- テーブルを作成
CREATE TABLE test (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(10),
UNIQUE INDEX (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
IGNORE INTO文の動作の違い
INSERT IGNORE INTO ...
を用いて解説します。なお、INSERT INTO ... ON DUPLICATE KEY UPDATE ...
の挙動も同一です。
-- 1つめのレコードを入れます
mysql> INSERT IGNORE INTO test (name) VALUES('トマト');
Query OK, 1 row affected (0.00 sec)
-- 1件目なので、1が返ります
mysql> SELECT LAST_INSERT_ID()\G
*************************** 1. row ***************************
LAST_INSERT_ID(): 1
1 row in set (0.00 sec)
-- 重複のためスキップされるレコードを入れます
mysql> INSERT IGNORE INTO test (name) VALUES('トマト');
Query OK, 0 rows affected (0.00 sec)
-- 重複のため、1のままです
mysql> SELECT LAST_INSERT_ID()\G
*************************** 1. row ***************************
LAST_INSERT_ID(): 1
1 row in set (0.00 sec)
-- 重複とならないデータを追加します
mysql> INSERT IGNORE INTO test (name) VALUES('トマト2');
Query OK, 1 row affected (0.00 sec)
-- MyISAMではidが2となるのですが、InnoDBでは3となります
mysql> SELECT LAST_INSERT_ID()\G
*************************** 1. row ***************************
LAST_INSERT_ID(): 3
1 row in set (0.00 sec)
-- InnoDBのテーブル内容は以下の通りです
mysql> select * from test;
+----+------------+
| id | name |
+----+------------+
| 1 | トマト |
| 3 | トマト2 |
+----+------------+
2 rows in set (0.00 sec)
データを削除した時のプライマリキーの挙動の違い
InnoDBの場合、
具体例とともに解説します。
-- まずは1件目のデータを挿入します
mysql> INSERT INTO test (name) VALUES('トマト1');
Query OK, 1 row affected (0.02 sec)
-- 続けて2件目のデータを挿入します
mysql> INSERT INTO test (name) VALUES('トマト2');
Query OK, 1 row affected (0.02 sec)
-- 期待通り、idは2となりました
mysql> SELECT LAST_INSERT_ID()\G
*************************** 1. row ***************************
LAST_INSERT_ID(): 2
1 row in set (0.00 sec)
-- idが2のデータを削除します
mysql> DELETE FROM test WHERE name IN('トマト2');
Query OK, 1 row affected (0.03 sec)
-- 続けて3件目のデータを挿入します
mysql> INSERT INTO test (name) VALUES('トマト3');
Query OK, 1 row affected (0.03 sec)
-- 期待通り、idは3となりました
mysql> SELECT LAST_INSERT_ID()\G
*************************** 1. row ***************************
LAST_INSERT_ID(): 3
1 row in set (0.00 sec)
-- idが3のデータを削除します
mysql> DELETE FROM test WHERE name IN('トマト3');
Query OK, 1 row affected (0.02 sec)
-- ここで、MySQLを再起動し、再度接続後に以下の行を挿入します
mysql> INSERT INTO test (name) VALUES('トマト4');
Query OK, 1 row affected (0.03 sec)
-- 再起動により、MyISAMでは4となる一方、InnoDBでは2となりました
mysql> SELECT LAST_INSERT_ID()\G
*************************** 1. row ***************************
LAST_INSERT_ID(): 2
1 row in set (0.00 sec)
-- InnoDBのテーブル内容は以下の通りです
mysql> select * from test;
+----+------------+
| id | name |
+----+------------+
| 1 | トマト1 |
| 2 | トマト4 |
+----+------------+
2 rows in set (0.01 sec)
この挙動となる仕組みについては、
なお、COMMENT='engine "myisam"'
と指定することで、
その他、
パーティショニング
InnoDBラッパーモードそのものでは、
具体的な方法に関しては、
まとめと次回予告
今回はTritonnとmroongaそれぞれの紹介と移行時の要注意点、
次回は、