Upload
nozomu-kaneko
View
193
Download
4
Embed Size (px)
Citation preview
Python を使ってカメリオを高速化した話
白ヤギ勉強会 #13 2015.03.25
Nozomu Kaneko
アジェンダカメリオについて以前のアーキテクチャ記事とテーマの事前紐づけ(タグ付け)なぜ Python か
カメリオ● 気になるテーマで最新情報が追える● 2014年 AppStore ベストアプリ● iPhone / Android / Web
カメリオのシステム全体構成
小さなサーバーで大きなサービスをつくるhttp://aial.shiroyagi.co.jp/2014/11/simple-server-and-huge-service/
以前の状況 (2014年後半〜)
● テーマに関連する記事の選択は、内部的に検索で実現o テーマ毎に異なる検索クエリが存在する
● ユーザが増えるに従い表示の遅さが目立ってきた
解決1: 検索サーバを増やす (2014/8〜)
APP 1記事インデックス
記事検索サーバ1
記事検索サーバ2
記事検索サーバ3
ELB ELBAPP 2
APP 3
NFSマウント
検索サーバを増やすようにした結果● ひとまずスケールするようになった
o 金のチカラで解決…
● アクセスの急増に対応しきれない● DAU と比例して徐々にサーバ台数が増加
o c3.xlarge × 6台 (MAX: 20台)
検索の限界● キャッシュとの相性
o あまり長くすると新しい情報が入ってこなくなるo 1人しかフォローしていないテーマが大半
● CPU heavyo サーバ1台で同時にさばけるリクエスト数が少ない
● 無駄が多いo 記事とテーマの関連性を毎回計算している
解決2: 記事とテーマを事前に紐づける● ユーザがアプリを開く前に、すべての記事についてすべてのテーマとの関連度を計算しておく
● 表示するときは SQL で取ってくるだけ
目標とするパフォーマンステーマ数: 約300万新着記事数: 1日約2万記事→処理速度: 1記事あたり5秒以内
最終目標: ファーストビュー1.5秒以内
設計方針● 記事精度を担保するため、既存のクエリを使って関連度スコアを計算するo 普通にやると遅すぎて話にならないので、各種の工夫をして現実的な時間で終わるようにする
● 将来的にタグ付けのアルゴリズムを拡張可能にするo ソースとテーマの関連付けなど
タグ付け処理の概要
...
クローラー データベースタグ付けアルゴリズム
テーマ テーマ テーマ
テーマ
RabbitMQ
オープンソースのメッセージングミドルウェア● 高い安定性● メッセージの永続化● 非同期タスクや PubSub など、様々な構成に対応
● 公式のチュートリアルが充実している
タグ付けパイプライン (1)
X
X
P
C
C
C
記事内容に基づくテーマタグ付け
ソースに基づくテーマタグ付け*
topic_tagger topic_tagger_main
topic_tagger_source
topic_tagger_main_queue
X
topic_tagger_source_queue
クローラー→タグ付け
*今回は未実装
タグ付けパイプライン (2)
P
P
P
X C
DB
topic_collector
topic_collector_queue
タグ付け→タグ保存
関連度スコア計算Q = "#weight(3.0 #syn(カメリオ kamelio) 1.0 白ヤギ)"
score(Q|D) = (3.0 * log P(カメリオ or kamelio|D) + 1.0 * log P(白ヤギ|D)) / 4.0
表現rの文書D中の出現頻度
文書Dの総単語数
表現rのコーパス中の出現確率
μ=200〜2500 (スムージングパラメータ)
文書Dにおける表現(≒単語)rの確率
スコア計算を高速化する工夫 (1)
クエリの絞り込み● クエリに含まれる単語が文書に1回も出てこなければ、そのクエリは決してマッチしない
● 転置インデックスを使って、評価すべきクエリを絞り込む
● フォロー中のテーマ数 約4万 → 2,000〜10,000テーマ
クエリの絞り込み
カメリオは白ヤギコーポレーションが開発したニュースアプリです。
カメリオカメルーンカメレオン
カメラクエリに「カメリオ」を含むテーマのリスト
テーマ1テーマ124テーマ3357…
転置インデックス#weight( 1.0 Python 0.5 PyPI 0.1 Guido )
決してマッチしない
スコア計算を高速化する工夫 (2)
事前計算● クエリの Python コードへの変換
o それまで使っていた検索エンジンと同等の処理(クエリのパース、スコア計算式)を Python で再実装
● クエリ評価に必要な統計値(各単語のコーパス中の出現頻度)の事前取得
● この部分の処理は全部で30分くらい。1日1回更新
ast
● Python の抽象構文木を扱うためのモジュール● compile() 関数を使って Python コードに変換できる
>>> print ast.dump(ast.parse("1 + 2 * x"))Module(body=[ Expr(value=BinOp(op=Add(), left=Num(n=1), right=BinOp(op=Mult(), left=Num(n=2), right=Name(id='x', ctx=Load()))))])
字句解析と構文解析BNF の例 (数式のみ)
<expression> ::= <term> ('+' <term>)* | <term> ('-' <term>)*<term> ::= <factor> ('*' <factor>)* | <factor> ('/' <factor>)*<factor> ::= '(' <expression> ')' | '+' <expression> | '-' <expression> | <number><number> ::= <integer>('.'<digit>*)+<integer> ::= <nonzerodigit><digit>* | '0'<nonzerodigit> ::= '1'...'9'<digit> ::= '0'...'9'
クエリから Python コードへの変換#weight(3.0 #syn(カメリオ kamelio) 1.0 白ヤギ)
#weight
3.0 1.0 白ヤギ
カメリオ
def func(log_p, p): return 0.75 * log(p("カメリオ", "kamelio")) + 0.25 * log_p["白ヤギ"]
+
* *
0.75 0.25log() []
p()
カメリオ kamelio
白ヤギlog_p
クエリのパース
ast の生成
コンパイル
kamelio
#syn
スコア計算を高速化する工夫 (3)
データ構造● 事前計算したデータはシリアライズしてファイルに保存する。実行時には SQL にアクセスしない。
● 単純に pickle すると読み込みに時間がかかるので、trie と配列を使って読み込みを高速化。
● 300MB -> 100MB (zip 圧縮して 70MB)
ここまでの工夫で、1記事あたり3〜5秒程度でタグ付けが完了する
結果● 表示速度は改善
o 最初の5テーマの表示に2秒かからないo アプリ側でも読み込みタイミングの調整を行った
● バックエンドのサーバ台数は削減o before: c3.xlarge 7台〜 (アクセスによって変動)o after: c3.xlarge 3台 + c3.2xlarge 1台
● 新着記事が出るまでの時間が短縮o 以前は平均して30分程度の遅延があった
ほぼリアルタイムに記事を処理できている
通常時tagger 10プロセスcollector 2 プロセス
必要に応じてスポットインスタンスを立ち上げることでスケール可能
結果 (続き)
hourly crawled contentshourly extracted contentshourly filtered contentshourly queued contentshourly tagged contents
苦労したところ● 精度の評価
o スコア計算のバグやパイプライン処理中の欠落など様々な原因で、表示される記事が一致しない ランダムに選んだテーマで結果を比較 アクセスの多いテーマで誤差の原因を解析
苦労したところ● RabbitMQ ワーカーが安定しない
o ライブラリ (pika) とマルチスレッドの相性が悪いo heartbeat が途切れるとサーバが勝手に接続を切るo 最終的には詰まっていることを検知して自動再起動するようにした(運用でカバー)
なぜ Python か● 高速化したいなら C++ や Go の方が Python より 100 倍高速では?
● Python を使った理由o 構文解析のような複雑な処理を、なるべくバグを出さずにかつ素早く書けること
o コードオブジェクトのシリアライズができることo pypy は使っているライブラリが対応していなかった
おまけ