48
blogサービスの 全文検索の話 全文検索エンジンGroongaを囲む夕べ5 長野雅広 (kazeburo)

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

Embed Size (px)

Citation preview

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

blogサービスの全文検索の話

全文検索エンジンGroongaを囲む夕べ5長野雅広 (kazeburo)

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

Me•長野雅広 (Masahiro Nagano)•@kazeburo / github:kazeburo•Operations Engineer / Site Reliability

•LINE corp.• ISUCON 2013,2014 連覇

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

今日のお題

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

livedoor Blog•サービス開始11周年

•国内最大級

•blog開設数 570万件

•総記事数 3億件

•約100億PV/month

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

livedoor Blogを支える技術

•Reverse Proxy - Apache, Nginx

•Application - Perl, Go

•Cache - Memcached

•RDBMS - MySQL 4.0, 5.5

•Search Engine - Mroonga

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

どこで Mroonga が使われているか

Page 8: blogサービスの全文検索の話 - #groonga を囲む夕べ
Page 9: blogサービスの全文検索の話 - #groonga を囲む夕べ
Page 10: blogサービスの全文検索の話 - #groonga を囲む夕べ

blog内の記事検索blog横断の検索はありませんが、

3億件の記事が対象

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

Mroongaを採用した理由

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

競合• MySQLのLIKE検索

➡ 検索機能不足

➡ カテゴリやタグなどの絞り込みが面倒

• MySQL組み込みの全文検索

➡ 日本語非対応

• Elasticsearch

➡ Java/JVMの経験不足

➡ 大規模環境でのトラブルシューティングに不安

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

Mroonga

•MySQL! MySQL! MySQL!

➡レプリケーションやバックアップなどMySQLの知見が活かせる

•日本語で作者とコミュニケーション

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

検索サーバの構成

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

microservicesマイクロサービスっぽく作ってます

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

blogApp

blogApp

blogApp

cmsApp

cmsApp

cmsApp

記事表示 Service 記事編集 Service

SearchAPI

SearchAPI

IndexWorker

IndexWorker

Q4M MappingDB

検索 Serviceshard1 shard2 shard3

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

blogApp

blogApp

blogApp

cmsApp

cmsApp

cmsApp

記事表示 Service 記事編集 Service

SearchAPI

SearchAPI

IndexWorker

IndexWorker

Q4M MappingDB

検索 Service

記事追加

shard1 shard2 shard3

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

blogApp

blogApp

blogApp

cmsApp

cmsApp

cmsApp

記事表示 Service 記事編集 Service

SearchAPI

SearchAPI

IndexWorker

IndexWorker

Q4M MappingDB

検索 Service

記事追加

shard1 shard2 shard3

Queueing

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

blogApp

blogApp

blogApp

cmsApp

cmsApp

cmsApp

記事表示 Service 記事編集 Service

SearchAPI

SearchAPI

IndexWorker

IndexWorker

Q4M MappingDB

検索 Service

記事追加

shard1 shard2 shard3

Queueing

blog_idとshardのmapping

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

blogApp

blogApp

blogApp

cmsApp

cmsApp

cmsApp

記事表示 Service 記事編集 Service

SearchAPI

SearchAPI

IndexWorker

IndexWorker

Q4M MappingDB

検索 Service

記事追加

shard1 shard2 shard3

Queueing

blog_idとshardのmapping

INSERT!

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

blogApp

blogApp

blogApp

cmsApp

cmsApp

cmsApp

記事表示 Service 記事編集 Service

SearchAPI

SearchAPI

IndexWorker

IndexWorker

Q4M MappingDB

検索 Serviceshard1 shard2 shard3

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

blogApp

blogApp

blogApp

cmsApp

cmsApp

cmsApp

記事表示 Service 記事編集 Service

SearchAPI

SearchAPI

IndexWorker

IndexWorker

Q4M MappingDB

検索 Service

検索

shard1 shard2 shard3

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

blogApp

blogApp

blogApp

cmsApp

cmsApp

cmsApp

記事表示 Service 記事編集 Service

SearchAPI

SearchAPI

IndexWorker

IndexWorker

Q4M MappingDB

検索 Service

検索

shard1 shard2 shard3

blog_idとshardのmapping

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

blogApp

blogApp

blogApp

cmsApp

cmsApp

cmsApp

記事表示 Service 記事編集 Service

SearchAPI

SearchAPI

IndexWorker

IndexWorker

Q4M MappingDB

検索 Service

検索

shard1 shard2 shard3

blog_idとshardのmapping

SELECT

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

Mroonga サーバの構成

shard1 shard2 shard3

この中身

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

Shardの構成

master

slave slave

Shard N

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

Shardの構成

master

slave slave

Shard N

参照・更新は全てMaster

Slaveはバックアップ

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

ハードウェア

•Intel Xeon 6core/12thread * 2

•Memory 96GB

•PCI-E SSD

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

テーブルとスキーマ

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

スキーマ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

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

スキーマ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; x150storage mode

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

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)

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

Table 分散

0001 0002 0003 0004 0005 0006

0007 ... ... ... ... 0144

0145 0146 0147 0148 0149 0150

SearchAPI

IndexWorker

blog_idで分散

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

Table 分散

0001 0002 0003 0004 0005 0006

0007 ... ... ... ... 0144

0145 0146 0147 0148 0149 0150

SearchAPI

IndexWorker

blog_idで分散

(murmur_hash(blog_id) % 150) + 1

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

Table分散を行う理由

•Mroonga/Groongaの制限を超えるため

•「最大インデックスサイズ: 256GByte」

•並列性能の向上

•障害時の影響範囲を最小化

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

運用ノウハウ

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

Kernel Tuning$ cat /etc/sysctl.conf# NUMAを無効にvm.zone_reclaim_mode = 0

# 物理メモリ以上のメモリ確保を許可vm.overcommit_memory = 1

# mmapで確保できる最大マッピング数vm.max_map_count = 5000000

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

Linux Tuning (2)

# 透過的hugepageを切る$ echo 'never' > /sys/kernel/mm/transparent_hugepage/enabled

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

my.cnf

[mysqld]table_open_cache = 多め!

MySQL 5.6ではデフォルト2000

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

その他の工夫

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

Splog/巨大記事対策•Mroongaにいれる1記事あたりの最大文字数の設定

➡Splog(spam + blog) の記事は大量のリンクを貼っていたり、自動生成した記事が多いため、1記事あたりの容量が増えがち

• ` ` やHTMLの終了タグの削除

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

参照クエリの並列数制限

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

並列度を上げると性能劣化

https://gist.github.com/kazeburo/9014939

0

37.5

75

112.5

150

1 2 3 4 5 6 7 8 16

参照クエリの並列度と処理時間

処理時間

並列度

http://redmine.groonga.org/issues/2335

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

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”)

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

困っている事

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

更新集中時の負荷

•記事の更新が集中した場合にロードアベレージが上がりやすい

•記事更新にもGET_LOCKが必要?

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

たまに落ちる•masterだけじゃなくて参照が行われていないslaveもMySQLが落ちる

•落ちた時に一部のテーブルのindexが壊れるのか更新ができない状態になり、mysqldump && restoreが必要となる

まだ落ちるパターンが分かればバグレポートあげたい

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

以上です。