29
Python 使#13 2015.03.25 Nozomu Kaneko

Python を使ってカメリオを高速化した話

Embed Size (px)

Citation preview

Page 1: Python を使ってカメリオを高速化した話

Python を使ってカメリオを高速化した話

白ヤギ勉強会 #13 2015.03.25

Nozomu Kaneko

Page 2: Python を使ってカメリオを高速化した話

アジェンダカメリオについて以前のアーキテクチャ記事とテーマの事前紐づけ(タグ付け)なぜ Python か

Page 3: Python を使ってカメリオを高速化した話

カメリオ● 気になるテーマで最新情報が追える● 2014年 AppStore ベストアプリ● iPhone / Android / Web

Page 4: Python を使ってカメリオを高速化した話

カメリオのシステム全体構成

小さなサーバーで大きなサービスをつくるhttp://aial.shiroyagi.co.jp/2014/11/simple-server-and-huge-service/

Page 5: Python を使ってカメリオを高速化した話

以前の状況 (2014年後半〜)

● テーマに関連する記事の選択は、内部的に検索で実現o テーマ毎に異なる検索クエリが存在する

● ユーザが増えるに従い表示の遅さが目立ってきた

Page 6: Python を使ってカメリオを高速化した話

解決1: 検索サーバを増やす (2014/8〜)

APP 1記事インデックス

記事検索サーバ1

記事検索サーバ2

記事検索サーバ3

ELB ELBAPP 2

APP 3

NFSマウント

Page 7: Python を使ってカメリオを高速化した話

検索サーバを増やすようにした結果● ひとまずスケールするようになった

o 金のチカラで解決…

● アクセスの急増に対応しきれない● DAU と比例して徐々にサーバ台数が増加

o c3.xlarge × 6台 (MAX: 20台)

Page 8: Python を使ってカメリオを高速化した話

検索の限界● キャッシュとの相性

o あまり長くすると新しい情報が入ってこなくなるo 1人しかフォローしていないテーマが大半

● CPU heavyo サーバ1台で同時にさばけるリクエスト数が少ない

● 無駄が多いo 記事とテーマの関連性を毎回計算している

Page 9: Python を使ってカメリオを高速化した話

解決2: 記事とテーマを事前に紐づける● ユーザがアプリを開く前に、すべての記事についてすべてのテーマとの関連度を計算しておく

● 表示するときは SQL で取ってくるだけ

Page 10: Python を使ってカメリオを高速化した話

目標とするパフォーマンステーマ数: 約300万新着記事数: 1日約2万記事→処理速度: 1記事あたり5秒以内

最終目標: ファーストビュー1.5秒以内

Page 11: Python を使ってカメリオを高速化した話

設計方針● 記事精度を担保するため、既存のクエリを使って関連度スコアを計算するo 普通にやると遅すぎて話にならないので、各種の工夫をして現実的な時間で終わるようにする

● 将来的にタグ付けのアルゴリズムを拡張可能にするo ソースとテーマの関連付けなど

Page 12: Python を使ってカメリオを高速化した話

タグ付け処理の概要

...

クローラー データベースタグ付けアルゴリズム

テーマ テーマ テーマ

テーマ

Page 13: Python を使ってカメリオを高速化した話

RabbitMQ

オープンソースのメッセージングミドルウェア● 高い安定性● メッセージの永続化● 非同期タスクや PubSub など、様々な構成に対応

● 公式のチュートリアルが充実している

Page 14: Python を使ってカメリオを高速化した話

タグ付けパイプライン (1)

X

X

P

C

C

C

記事内容に基づくテーマタグ付け

ソースに基づくテーマタグ付け*

topic_tagger topic_tagger_main

topic_tagger_source

topic_tagger_main_queue

X

topic_tagger_source_queue

クローラー→タグ付け

*今回は未実装

Page 15: Python を使ってカメリオを高速化した話

タグ付けパイプライン (2)

P

P

P

X C

DB

topic_collector

topic_collector_queue

タグ付け→タグ保存

Page 16: Python を使ってカメリオを高速化した話

関連度スコア計算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の確率

Page 17: Python を使ってカメリオを高速化した話

スコア計算を高速化する工夫 (1)

クエリの絞り込み● クエリに含まれる単語が文書に1回も出てこなければ、そのクエリは決してマッチしない

● 転置インデックスを使って、評価すべきクエリを絞り込む

● フォロー中のテーマ数 約4万 → 2,000〜10,000テーマ

Page 18: Python を使ってカメリオを高速化した話

クエリの絞り込み

カメリオは白ヤギコーポレーションが開発したニュースアプリです。

カメリオカメルーンカメレオン

カメラクエリに「カメリオ」を含むテーマのリスト

テーマ1テーマ124テーマ3357…

転置インデックス#weight( 1.0 Python 0.5 PyPI 0.1 Guido )

決してマッチしない

Page 19: Python を使ってカメリオを高速化した話

スコア計算を高速化する工夫 (2)

事前計算● クエリの Python コードへの変換

o それまで使っていた検索エンジンと同等の処理(クエリのパース、スコア計算式)を Python で再実装

● クエリ評価に必要な統計値(各単語のコーパス中の出現頻度)の事前取得

● この部分の処理は全部で30分くらい。1日1回更新

Page 20: Python を使ってカメリオを高速化した話

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()))))])

Page 21: Python を使ってカメリオを高速化した話

字句解析と構文解析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'

Page 22: Python を使ってカメリオを高速化した話

クエリから 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

Page 23: Python を使ってカメリオを高速化した話

スコア計算を高速化する工夫 (3)

データ構造● 事前計算したデータはシリアライズしてファイルに保存する。実行時には SQL にアクセスしない。

● 単純に pickle すると読み込みに時間がかかるので、trie と配列を使って読み込みを高速化。

● 300MB -> 100MB (zip 圧縮して 70MB)

ここまでの工夫で、1記事あたり3〜5秒程度でタグ付けが完了する

Page 24: Python を使ってカメリオを高速化した話

結果● 表示速度は改善

o 最初の5テーマの表示に2秒かからないo アプリ側でも読み込みタイミングの調整を行った

● バックエンドのサーバ台数は削減o before: c3.xlarge 7台〜 (アクセスによって変動)o after: c3.xlarge 3台 + c3.2xlarge 1台

● 新着記事が出るまでの時間が短縮o 以前は平均して30分程度の遅延があった

Page 25: Python を使ってカメリオを高速化した話

ほぼリアルタイムに記事を処理できている

通常時tagger 10プロセスcollector 2 プロセス

必要に応じてスポットインスタンスを立ち上げることでスケール可能

結果 (続き)

hourly crawled contentshourly extracted contentshourly filtered contentshourly queued contentshourly tagged contents

Page 26: Python を使ってカメリオを高速化した話

苦労したところ● 精度の評価

o スコア計算のバグやパイプライン処理中の欠落など様々な原因で、表示される記事が一致しない ランダムに選んだテーマで結果を比較 アクセスの多いテーマで誤差の原因を解析

Page 27: Python を使ってカメリオを高速化した話

苦労したところ● RabbitMQ ワーカーが安定しない

o ライブラリ (pika) とマルチスレッドの相性が悪いo heartbeat が途切れるとサーバが勝手に接続を切るo 最終的には詰まっていることを検知して自動再起動するようにした(運用でカバー)

Page 28: Python を使ってカメリオを高速化した話

なぜ Python か● 高速化したいなら C++ や Go の方が Python より 100 倍高速では?

● Python を使った理由o 構文解析のような複雑な処理を、なるべくバグを出さずにかつ素早く書けること

o コードオブジェクトのシリアライズができることo pypy は使っているライブラリが対応していなかった

Page 29: Python を使ってカメリオを高速化した話

おまけ