ISUCON予選にPHP実装で参加して3位になりましたーやったことなどまとめ
by @dekokun on 2013/10/06 20:15
Tagged as: contest.
今日(10/6)はISUCON3の予選2日目に参加しました。
「予選落ちだろうなー」と思いながら挑んだら、意外と3位(暫定。運営の方が提出したAMIを起動しベンチマーク実行し、提出したスコアと比べてあまりにもスコアが低かった場合は失格となる)で本選進出が決まったので嬉しくてブログ書いてます。
スコアは14379で、予選1日目と合わせて9位。
「チームたこやき」という名前で後輩2人と参加しました。
題名に「PHP実装で」と入れたのは、ディスられがちなPHP書きへのエールを込めてです。
基本的にやってたこと
以下、恒常的にやってたこと一覧と、その効果を。
xhprofでのプロファイリング
効果:圧倒的
- facebook謹製プロファイリングツール、xhprof様でございます
- session_startが異様に遅い(memcachedの罠)とか、preg_split使いすぎとか、「今はDBネックだし他のことやってもあまり意味ないよね」とか、いろいろなことがわかりました
- ベンチかけながらブラウザでxhprofの結果をみて遅い場所を判断するということの繰り返しでしたね
スロークエリログの解析
効果:圧倒的
- mysqldumpslowを使ってスロークエリを解析していました
- 遅いクエリを見つけてはindexをはったりアプリケーションを書き換えたり
- xhprof同様、ベンチかけては解析結果を見て を繰り返していました
dstatの使用
効果:普通(私に使いこなせなかった)
- あーCPU使用率高いわーIO高いわー。以上。みたいなそんな感じだった
- せっかくグラフィカルだったし、1枚のディスプレイに表示させてみんなで見ながらやるとよかったなと思いました
topの使用
効果:いい感じ
- topはいいですねぇ
最終的なミドルウェア構成
- DB: MySQL リバースプロキシ: nginx アプリサーバ: Apache2.4(Prefork MPM) + mod_php
- PHPらしい構成ですね
- Apacheだけでいいかなーと思っていたら後輩がnginxを入れて静的ファイルはそちらから配信するようにしてくれていました
- memcachedは使用しておりません
- せっかくApache2.4だったので、Event Mpm + PHP-FPMも試そうかと思っていたのですが、最後までDBネックから抜けだせなかったため「PHP-FPMにしても今は意味ないだろうな」という感じでそのまま終了
開発環境
- 各人に別々のポートで立ち上げた開発環境を用意し、そこで開発して特にエラーが起きなかったらデプロイするという感じ
- git pullによるデプロイ
- isuコマンドという、いろいろなもののラッパースクリプトを作成したりもしました。“isu deploy”とか“isu slow-show(mysqldumoslowを使ってスロークエリを見る)”とか
覚えている範囲で実際に行った作業
以下、特にPHPのロジック変更周りは基本的に後輩2人がやってくれたので私はあまり関与していませんが、記憶している限り書きます
ベンチマークのworkloadオプションを変更
- Lingrを見ていた後輩がworkloadオプションの変更を提案。スコアが上がったため変更
- 情報収集力の大切さを感じました
セッションにmemcacheを使用しない
- プロファイリングの結果、session_startに異様に時間がかかっていたことが判明したために、サーバ1台だけで動いていることもあってファイルを使ったセッション管理に切り替えた
- 知らず知らずのうちに運営のはった罠(純正のmemcachedではなくMySQL 5.6.14のInnoDB Memcached Pluginが使われていた)を抜けていた感じですね
markdownコマンドを表示時ではなく記事投稿時に実施するように変更
- 子プロセスの起動はクソほど重いですからね…
- Markdown作成処理をPHPに行わせることはしていません
- ベンチマークがうまく動いていない時に実施した施策なので、この施策による実際の効果のほどはイマイチわからないが、良かったのではないかと
ユーザ名を記事毎にselectしている部分の撤去
- 悪名高き N + 1クエリ問題
- inner joinして解決
- memosテーブルにユーザ名を持てばよいのではという話もありましたが、「アプリケーション初期化の際にどうすればいいんだ」という話になってボツに
APCの使用
- Opcodeキャッシュは重要ですよね
- これをやってかなりスコアが伸びた記憶が
タイトルを記事投稿時に保存するように変更
- プロファイリングによりトップ画面表示時のタイトル生成のためにpreg_splitが呼び出されまくっていたため、タイトルは投稿時に作成しDBに保存するようにした
- 効果があったらしい(この頃、私はDBにインデックスはったりDBのパラメータチューニングしたりしていて、記憶にない)
countテーブルの作成
- is_privateではないmemosの全件をcountしている部分が遅かったため、専用のcountテーブルを作成し、insertする度に件数をupdateするなどした
Unixドメインソケットを使用した接続
- 同じサーバなのでわざわざTCPを使用することもないなと思いソケットファイルを使用した通信に
- 実施が簡単なので実施したが、特に効果はみられなかった
ログインしてもDBは更新しない
- last_accessカラムを更新していましたが、不要ですね。ということで更新しなくした。運営からのサービスポイントだったようですね。
MySQLのクエリキャッシュを有効に
- query_cache_sizeだけ設定しquery_cache_typeを設定せず、「あれ、キャッシュヒット数が0から増えないなぁ」などとやっておりました。
- クエリキャッシュを実施しても特にスコアの上昇はなかった
所感
- とにかく、地道に、プロファイリングしてはボトルネック部分を直してを繰り返したのが良かった
- 今回、KVSなど使用せず非常に古典的にMySQLとWebサーバのみで如何に遅い部分を潰すかをプロファイル見ながら普通に実施していただけですが、これだけでは本選は戦えないだろうなぁとは思っており、一層の奮闘が必要。
- 後輩2人がすごく頑張ってくれていた。後半は、私は「クエリが早くならないなぁ」というだけの係になっていた感があった
- インデックスを作成したらオプティマイザがバグってexplainの結果にusing filesortとか出てきたためしょうがなくstraight_joinを使用したりした。joinとsortが混じるようなSQLはオプティマイザがバグりやすくて要注意
- 今回、トップページを高速化すれば全体のスコアが上がるようなアプリだったため、「トップページのxhprofを見る」->「遅いところを直す」だけで良かったが、本来ならアクセスログから遅いページを割り出すなどする必要があったなぁ。と思っております。アクセスログにかかった時間をマイクロ秒単位で出力するようにしたものの最後までそれが使われることはなかった…
- ISUCON応募締め切り10分前くらいに後輩にISUCONのこと話したら後輩も乗り気だったため締め切り3分前くらいに応募を完了したんだけど、出てよかったなと思いました
- NHN選抜チームを抜けそうだったので、本選では抜きたいと思います
- 白金動物園を抜けて結構うれしい
- 運営の追試によって失格にならないか怖くて仕方がない