たごもりすメモ

コードとかその他の話とか。

isucon3本戦いってきた&勝ってきた! #isucon

isucon3の本戦にLINE選抜チームとして出た。ガッカリな感じだった予選時の状況はこちら。

isucon3予選参戦の記録 - tagomorisのメモ置き場

引き続き @kazeburo @sugyan @tagomoris の3人チーム。

ざっくりまとめ

  • みんなこれまでこんな楽しいイベントに参加してたのか! ずるい!!!!!!
  • 普段やってることを普段通りやる、と思っていたが焦って普段やらないようなミスを次々連発、社会は厳しい
  • 思いきった構成変更とかできるのがisuconでしょwwww とかドヤ顔していたが、やりきれた。ドヤッ。

これからisuconという名前を口にするときに堂々とドヤ顔しようと思います。嘘です。元出題者のくせに成績出ないとかwwwみたいにならなくて本当に安心しました。終わったあとのビールおいしかったです。

ということで、勝ちました。優勝と、あと僅差で特別賞もいただきました。わーい。

あんなに楽しく難しい良問を用意してくれたカヤックの皆様、特に @fujiwara さん @acidlemon さん、本当にありがとうございました。また運営&スポンサーのLINEおよびデータホテルの皆様、おかげさまでストレスなくイベントを楽しめました。ありがとうございました。色々と空気読まなくてすいません。w

詳細

時系列での経緯については @sugyan が既にすばらしくまとめているのでそちらをどうぞ。

#isucon 2013で優勝しました - すぎゃーんメモ

本戦終了時のコードや設定などはこちらのリポジトリ

tagomoris/isucon3-final-code · GitHub

予選のときの反省を生かし、今回はイベント開始後からけっこうな時間をアプリケーションの把握&作業の準備、および方針確定に費した。

apacheのログ書式を変更して duration を出力するようにしてから一度ベンチを実行し、そのログをざっと集計にかけてアクセス傾向を見る。これには Apache::Log::Parser というcpan module*1を入れるとついてくる analyze_apache_logs というコマンドを使ってざっとした粒度でやる。この時点で画像のアップロードの頻度がそう高くないことを把握*2。レギュレーションの配点には画像アップロード関連での得点ボーナス条項があったが、それを狙うのはおそらく悪手だね、という合意をとる。
画像のダウンロードに注力すればいいんじゃないかなーということでそのための設計を検討。この時点でアプリのボトルネックなどは実は全く見てすらいない。

で、どのようにサーバ5台を使うか、という設計の話。

  • 各サーバのスペックが割と限られてる
  • DBはまったく問題にならない
    • クエリも素直だし、各テーブルのスキーマや行数を見てもぜんぜん大した量じゃない
    • メモリに乗るし、JOINとか見えるけどそれに釣られたら負けですねこれ、無視無視
  • Jpeg画像(とpngアイコン)の配信なのでglobalの100Mbpsがボトルネックになるのは明らか
    • ということでできる限り多くのサーバをフロントに置いてロードバランスしたい
    • internalが1Gbpsなので、フロントに来た画像のリクエスト全部を別サーバに問合せにいっても内側で詰まることはない
    • なので画像は1台のバックエンドサーバにまとめ、MySQLもそこでやろう
    • それ以外の4台をフロントエンドとして帯域を使い切るくらいできればよさそう
    • Perlのコードで画像のパーミッションを解決してからバックエンドから画像持ってきて返そう、PerlのコードやMySQLボトルネックになることはこのアプリだとなさそう
    • バックエンドの画像ストレージ、ローカルファイルシステムをそのままURLにマッピングできるし、クライアント(フロントエンド)もHTTPで楽ちんだし、WebDAVでいいですねー
  • 画像の変換について
    • 帯域が限られているので、まずフルサイズのJpegはできるだけ画質を落とす、image_diffコマンドでレギュレーション満たすか確認できるのは便利
    • 画質劣化やcrop&resizeはどうやってやる? smalllight + squid みたいな某blogでおなじみの構成? それとも予め処理してバックエンドに置いておく?
    • Jpegは1万枚くらい、ベンチ走行中にアップロードされてくるものがほとんどないから、GETを速くするなら事前処理しかない
    • またアクセスログを確認して取得される画像がけっこう幅広いことも確認、キャッシュ戦略は筋が悪そう
    • デフォルトで入っているものは全部事前処理しよう、間に合うだろう、きっと
    • ベンチ中にアップロードされてくるものはいったん受けて queue & worker で変換処理しよう、フロントのAppから切り離したい
  • そのあたりまでをとりあえずやって、そこからのボトルネックとか探しが本番ですかねー
    • まあ夕方前くらいにそこまで行くかな? (と、この時は思ってた。甘かった。)

だいたいこんな感じの方針が決まったのが12時半前。
並行して2〜5番サーバへのデータのコピー、nginx(openresty)や使い慣れたバージョンのMySQLおよびアプリケーションサーバ魔改造Starlet*3への入れ替えとベンチ走行して通ることのチェック、手元でのアプリケーション開発環境の準備、アプリケーションデプロイ方法の整備などをみんなで分担してやってたので、ようやくここから実際にアプリケーションに手を入れる段階になった。

@sugyan と相談してとりあえずフロントエンドサーバのローカルで処理する形のまま画像アップロード部分を変更してもらうようにお願いし、自分はバックエンドサーバの準備にかかる。nginxをWebDAVモジュールを有効にして再度ビルドし直し、設定をガッと書いて使えるようにする。curlで動作確認できるのが非常に楽で良い。手元での開発で便利なように、フロントエンドサーバ以外にISUCON会場のIPアドレスからもリクエストを受けるようACLを設定。

その後にJpeg画像の処理に着手。
まずはJpeg画質劣化ということで、500枚の画像を convert -quality で画質劣化させては image_diff を使ってレギュレーションにひっかかるものが出るかどうかをチェックを繰り返した。結果的には -quality 40 でいちばんピクセル差分が多いものが3%ちょっとくらい。画像ファイルサイズは 1/3 以下になったので、このくらいでよかろうということにした。全ファイルの劣化処理には1時間くらい? けっこうかかった。ローカルファイルシステム上で処理すればWebDAVからすぐ見えるのがグレイト。

それからサイズMやSの画像の作成。これが難物だった。サイズM画像の生成を元の処理の通りに identify -> convert でやろうとしたら500ファイルの処理に15分かかり、全ファイルをこれでやろうと思うと6時間。無理無理。いったん諦めかけたもののこれでくじけて負けたら悲しすぎるので Imager を使って実行するコードをガッと書く。焦って書いたら何度もエンバグして泣きそうになったものの、なんとかやれた。Imager版だとMサイズ画像の全ファイル生成が1時間もかからずに終わりガッツポーズ。今回あまり大きい画像がなかったので、identify/convertだとforkのコストが大きすぎたんだろうなあ。
サイズSの生成はサイズMからconvertでやって割とすぐ終わった。そのまま返す刀でiconの変更も実施、これもすぐ終わる。ベンチのチェックでもdiffはひっかからなかったようなので安心した、ところでもう17時前。この時点でスコアはデフォルト状態で走らせたときの1600くらいがベスト。w

いっぽう @kazeburo @sugyan の側でフロントエンドnginxでのiconのキャッシュやベンチ走行中にアップロードされた画像のcrop&resizeなどをやってもらってた。はず。何をやってるかは聞きつつもコードとかはもう完全に信頼してて自分は見てない。たまにうまく動かないー!っていうのを見てみたりしたくらいか。
@sugyan さんがblogに書いてるけど、画像の変換の非同期化は設計としてマズかったのが途中でわかって厳しい感じだったっぽい。落ち着いて考えればわかることだったはずなのに、実際にやるまで気付かなかった。ううううむ、修行が足りない。

そこからちょいちょいバグがあったりして直しつつ、ようやく当初考えいてたものが全部揃って動いたのが17時過ぎくらい。少しバグがあったりで失敗しつつも10000くらいのスコア。そこから直したり、自分がアップロードされた画像の変換を convert から Imager に変えたり*4。17:20くらいにworkload 1 でちゃんと通ったのが13000くらいだったっけかな、そのくらい出た。
で、4台分はスケールするはずだから、とworkload 4で出したスコアがどかんと 41122 で特別賞ゲット! これが記録によると 17:28 か。このときはようやくマトモなスコアが出て本当に嬉しくて、やることやってもスコア出なかったらどうしようという不安とか、いちおうちゃんと時間内にやれたという安心感とか、特別賞があればこのあと何かあって負けてもしょんぼりにならなくて済む嬉しさとか、そういう諸々のプレッシャーから解放されて本当にほっとしてた。@kazeburoさんのガッツポーズとか滅多に見られませんよみなさん。

そこから更にworkloadを8に増やして60000くらいだっけ。そのくらいのスコアをとった。暫定2位は10分以上伸び悩んでて40000いってなかったと思う。

さてあと30分弱、というとき、どこから手をつけようかみたいな空気になったので、その前から少し手が空いていた自分がえいやと割り込んだ。もう時間ないから、最適なworkloadを探して、サーバ再起動のテストもやって、それで確実に勝とうよと。たぶんそれで勝てるんじゃないかなと。

実際には連続してベンチを走らせるとfailするバグ*5が見付かったりしてそれを直したりはしてた。安定するworkloadがどこかがいまいち見えなくて本番計測用はギリギリまで workload 4 にしてたんだけど、サーバ再起動後に workload 8 でのベンチが2回通ったのを確認して、8に設定。そこでタイムアップ。サーバ再起動での動作確認、安定して通りそうなworkloadの確認、をちゃんとやれて終えられたので、本番計測をちゃんと通せたかなという気がする。

本番計測の結果を見ると、終了前に3万以上のスコアをとっていた4チームくらいが軒並み fail していたのかなと思う。おかげでうちのチームが3分で順当に3倍くらいのスコアになったいっぽう、2位集団がぜんぶいなくなったせいで1チームだけ得点がおかしいみたいになってたw あれは違うんですよ、他が自滅したんですよ……。

雑感

時間が無い中だと本当に次々と些細なバグを量産してしまってて、これはもう本当に修行が足りないとしか言えない。短い時間でちゃんと動くコードをさくっと書けるようにならねばならない。

いっぽう最初にこうすべきだと決めた設計がちゃんとハマって良いスコアが出せたのは本当によかった。後から聞いたら本番計測時に各フロントエンドサーバはglobal側の100Mbpsをほぼ使いきっていたらしいので、実際にあれ以上やろうと思うと5台目も無理に外向きに使うとか、あるいは画質をギリギリまで落とす挑戦をするとか、そういうツラい感じのことしかなかったかも。
とはいえ、実は最後にスコアが出た状態については自分達でどこがボトルネックになっているかすら調べられていないような状態。アプリのプロファイルとか今回一度もとってない。勝てたからいいとはいえ、それはそれでどうなんだという気がする。もっとさっさと改修を済ませられないとあんまり胸を張れたものではない……。16時くらいには改修終わって、そこから更に性能を詰めるというくらいの気分だったんだけどなあ。

チーム構成がよかったのは本当によかったと思う。午前中の準備作業もサーバのオペレーションとアプリケーションの環境整備をうまいこと分担してみんなでさっさと済ませられたし、その後もフロントの性能とキャッシュ戦略の人(kazeburo)、アプリケーションのコードをゴリゴリの人(sugyan)、バックエンドの整備と画像変換の人(tagomoris)、という感じで綺麗に分けられた。もちろんお互い見たり見てもらったりしながら。チームの人に安心して他の作業を任せつつ自分のことに集中できるというのは得難いものだと思う。特にISUCONみたいな状況では。

予選だとnginxでキャッシュや!みたいな戦略が勝ったのもあって一概には言えないかもしれないけど、nginxであんまり色々やりすぎるのはリスクかなー、というのは今回思った。
nginxで頑張るの、やっぱなんだかんだでハマりやすいと思う。一度 kazeburo さんがハマって悩んでて、自分が ugly なworkaroundを提案して回避したというのがあったけど*6、そもそもそのようなソフトウェアに複雑なルールやロジックを重ねていくというのは単純明快さに欠ける部分があって、致命傷になりかねないなあ、と感じる部分がある。

まあ、ともあれ、過去出題者としては、出場側に回ってもいちおうやるべきことをやれたと思う。面目が保たれた。うれしい。

あと当日、だいぶ煮詰まってるところに @kazeburo さんご家族の応援とかあってなごんだ。差し入れのカードのお言葉がうれしかったです。ありがとうございました。

え、次まじでまた自分達が出題するのー? 誰か我こそは出題せんという人はいませんかね。一度社内ISUCONやってみれば感覚がわかると思いますので、その上でぜひという人がいればお願いしたいくらいなんだけどなあ。出題者、大変だけど、面白いですよ。

いや、あの、自分がまた出たいから言ってるわけではないですよ。

ものすんごく面白かった。いい経験ができました。 @kazeburo さん @sugyan さんありがとうございました。おつかれさまでした!

おまけ

isucon3前々日に行ったイベント(Cloudera World Tokyo 2013)のオライリーブースでふと買ったマスタリングNginxを持っていたらことのほか役に立ちましたことよ。

*1:tagomorisという人が11/08にリリースしたらしいよ!

*2:http request methodが集計に入ってなくてちょっとアレだったけど。あとでなおす。

*3:きっとkazeburoさんがblogに書く

*4:これがまた Imager まわりのコードがバグってたり、各サーバへのインストールがダメな感じで @sugyan に助けてもらったり……。

*5:これは多分Furlのconnection poolのまわりの問題っぽかった

*6:そういえばアレの原因調べないと……nginxで [a-z0-9]{32} みたいな正規表現って期待通り動かない?