blogサービスの 
全文検索の話 
全文検索エンジンGroongaを囲む夕べ5 
長野雅広 (kazeburo)
Me 
•長野雅広 (Masahiro Nagano) 
•@kazeburo / github:kazeburo 
•Operations Engineer / Site 
Reliability 
•LINE corp. 
•ISUCON 2013,2014 連覇
今日のお題
livedoor Blog 
•サービス開始11周年 
•国内最大級 
•blog開設数 570万件 
•総記事数 3億件 
•約100億PV/month
livedoor Blogを支える技術 
•Reverse Proxy - Apache, Nginx 
•Application - Perl, Go 
•Cache - Memcached 
•RDBMS - MySQL 4.0, 5.5 
•Search Engine - Mroonga
どこで Mroonga が 
使われているか
blog内の記事検索 
blog横断の検索はありませんが、 
3億件の記事が対象
Mroongaを 
採用した理由
競合 
• MySQLのLIKE検索 
➡ 検索機能不足 
➡ カテゴリやタグなどの絞り込みが面倒 
• MySQL組み込みの全文検索 
➡ 日本語非対応 
• Elasticsearch 
➡ Java/JVMの経験不足 
➡ 大規模環境でのトラブルシューティングに不安
Mroonga 
•MySQL! MySQL! MySQL! 
➡レプリケーションやバックアップな 
どMySQLの知見が活かせる 
•日本語で作者とコミュニケーション
検索サーバの構成
microservices 
マイクロサービスっぽく作ってます
記事表示 Service 記事編集 Service 
blog 
App 
blog 
App 
blog 
App 
cms 
App 
cms 
App 
cms 
App 
Search 
API 
Search 
API 
Index 
Worker 
Index 
Worker 
Q4M MappingDB 
shard1 shard2 shard3 
検索 Service
記事表示 Service 記事編集 Service 
blog 
App 
blog 
App 
blog 
App 
cms 
App 
cms 
App 
cms 
App 
Search 
API 
Search 
API 
Index 
Worker 
Index 
Worker 
Q4M MappingDB 
検索 Service 
記事追加 
shard1 shard2 shard3
記事表示 Service 記事編集 Service 
blog 
App 
blog 
App 
blog 
App 
cms 
App 
cms 
App 
cms 
App 
Search 
API 
Search 
API 
Index 
Worker 
Index 
Worker 
Q4M MappingDB 
検索 Service 
記事追加 
shard1 shard2 shard3 
Queueing
記事表示 Service 記事編集 Service 
blog 
App 
blog 
App 
blog 
App 
cms 
App 
cms 
App 
cms 
App 
Search 
API 
Search 
API 
Index 
Worker 
Index 
Worker 
Q4M MappingDB 
検索 Service 
記事追加 
shard1 shard2 shard3 
Queueing 
blog_idと 
shardの 
mapping
記事表示 Service 記事編集 Service 
blog 
App 
blog 
App 
blog 
App 
cms 
App 
cms 
App 
cms 
App 
Search 
API 
Search 
API 
Index 
Worker 
Index 
Worker 
Q4M MappingDB 
検索 Service 
記事追加 
shard1 shard2 shard3 
Queueing 
blog_idと 
shardの 
mapping 
INSERT!
記事表示 Service 記事編集 Service 
blog 
App 
blog 
App 
blog 
App 
cms 
App 
cms 
App 
cms 
App 
Search 
API 
Search 
API 
Index 
Worker 
Index 
Worker 
Q4M MappingDB 
shard1 shard2 shard3 
検索 Service
記事表示 Service 記事編集 Service 
blog 
App 
blog 
App 
blog 
App 
cms 
App 
cms 
App 
cms 
App 
Search 
API 
Search 
API 
Index 
Worker 
Index 
Worker 
検索 
Q4M MappingDB 
shard1 shard2 shard3 
検索 Service
記事表示 Service 記事編集 Service 
blog 
App 
blog 
App 
blog 
App 
cms 
App 
cms 
App 
cms 
App 
Search 
API 
Search 
API 
Index 
Worker 
Index 
Worker 
検索 
Q4M MappingDB 
shard1 shard2 shard3 
検索 Service 
blog_idと 
shardの 
mapping
記事表示 Service 記事編集 Service 
blog 
App 
blog 
App 
blog 
App 
cms 
App 
cms 
App 
cms 
App 
Search 
API 
Search 
API 
Index 
Worker 
Index 
Worker 
検索 
Q4M MappingDB 
shard1 shard2 shard3 
検索 Service 
blog_idと 
shardの 
mapping 
SELECT
Mroonga サーバの構成 
shard1 shard2 shard3 
この中身
Shardの構成 
master 
slave slave 
Shard N
Shardの構成 
master 
slave slave 
Shard N 
参照・更新は 
全てMaster 
Slaveは 
バックアップ
ハードウェア 
•Intel Xeon 6core/12thread * 2 
•Memory 96GB 
•PCI-E SSD
テーブルとスキーマ
スキーマ 
CREATE TABLE article_index_0001 ( 
id bigint unsigned NOT NULL AUTO_INCREMENT, 
blog_id int unsigned NOT NULL, 
article_id int unsigned NOT NULL, 
status tinyint NOT NULL, 
public_terms mediumtext, 
private_terms mediumtext, 
article_datetime datetime NOT NULL, 
PRIMARY KEY (id), 
UNIQUE KEY unique_entry (blog_id,article_id), 
FULLTEXT KEY for_public (public_terms), 
FULLTEXT KEY for_cms (public_terms,private_terms) 
) ENGINE=mroonga; 
storage mode
スキーマ 
CREATE TABLE article_index_0001 ( 
id bigint unsigned NOT NULL AUTO_INCREMENT, 
blog_id int unsigned NOT NULL, 
article_id int unsigned NOT NULL, 
status tinyint NOT NULL, 
public_terms mediumtext, 
private_terms mediumtext, 
article_datetime datetime NOT NULL, 
PRIMARY KEY (id), 
UNIQUE KEY unique_entry (blog_id,article_id), 
FULLTEXT KEY for_public (public_terms), 
FULLTEXT KEY for_cms (public_terms,private_terms) 
) ENGINE=mroonga; x150 storage mode
mysql>show tables; 
+--------------------+ 
| Tables_in_hermes | 
+--------------------+ 
| article_index_0001 | 
| article_index_0002 | 
| article_index_0003 | 
| article_index_0004 | 
| article_index_0005 | 
| article_index_0006 | 
| article_index_0007 | 
... 
... 
| article_index_0142 | 
| article_index_0143 | 
| article_index_0144 | 
| article_index_0145 | 
| article_index_0146 | 
| article_index_0147 | 
| article_index_0148 | 
| article_index_0149 | 
| article_index_0150 | 
+--------------------+ 
150 rows in set (0.00 sec)
Table 分散 
Search 
API 
Index 
Worker 
blog_idで分散 
0001 0002 0003 0004 0005 0006 
0007 ... ... ... ... 0144 
0145 0146 0147 0148 0149 0150
Table 分散 
Search 
API 
Index 
Worker 
blog_idで分散 
(murmur_hash(blog_id) % 150) + 1 
0001 0002 0003 0004 0005 0006 
0007 ... ... ... ... 0144 
0145 0146 0147 0148 0149 0150
Table分散を行う理由 
•Mroonga/Groongaの制限を超えるため 
•「最大インデックスサイズ: 256GByte」 
•並列性能の向上 
•障害時の影響範囲を最小化
運用ノウハウ
Kernel Tuning 
$ cat /etc/sysctl.conf 
# NUMAを無効に 
vm.zone_reclaim_mode = 0 
# 物理メモリ以上のメモリ確保を許可 
vm.overcommit_memory = 1 
# mmapで確保できる最大マッピング数 
vm.max_map_count = 5000000
Linux Tuning (2) 
# 透過的hugepageを切る 
$ echo 'never' > /sys/kernel/mm/ 
transparent_hugepage/enabled
my.cnf 
[mysqld] 
table_open_cache = 多め! 
MySQL 5.6では 
デフォルト2000
その他の工夫
Splog/巨大記事対策 
•Mroongaにいれる1記事あたりの最大文 
字数の設定 
➡ Splog(spam + blog) の記事は大量の 
リンクを貼っていたり、自動生成した記 
事が多いため、1記事あたりの容量が増 
えがち 
•` ` やHTMLの終了タグの削除
参照クエリの 
並列数制限
並列度を上げると性能劣化 
参照クエリの並列度と処理時間 
処理時間並列度 
https://gist.github.com/kazeburo/9014939 
150 
112.5 
75 
37.5 
0 
1 2 3 4 5 6 7 8 16 
http://redmine.groonga.org/issues/2335
GET_LOCKで並列度制限 
# table名でlock 
mysql> SELECT GET_LOCK(“article_index_0099”,30); 
# 検索 
mysql> SELECT article_id FROM article_index_0099  
WHERE blog_id=30 AND status=1  
AND MATCH(public_terms) AGAINST(? IN BOOLEAN MODE)  
ORDER BY article_datetime DESC LIMIT 10 OFFSET 0; 
# 終わったらlockを解放 
mysql> SELECT RELEASE_LOCK(“article_index_0099”)
困っている事
更新集中時の負荷 
•記事の更新が集中した場合にロードア 
ベレージが上がりやすい 
•記事更新にもGET_LOCKが必要?
たまに落ちる 
•masterだけじゃなくて参照が行われて 
いないslaveもMySQLが落ちる 
•落ちた時に一部のテーブルのindexが 
壊れるのか更新ができない状態にな 
り、mysqldump && restoreが必要 
となる 
まだ落ちるパターンが分かれば 
バグレポートあげたい
以上です。

blogサービスの全文検索の話 - #groonga を囲む夕べ