ISUCON3 - 予選から本戦を終えて

TwitterでISUCONに参加したいとぼやいていたら、@f440 さんが一緒にどうかと誘ってくれたので、ISUCON3に参加してきました。

オシャレ怪盗スワロウテイルとして参加し、本戦2位という結果で幕を閉じました。 参加した動機として、自分がWebエンジニアとしてどれくらい通用するのかという腕試しでもあり、また優勝を狙っていたので、あとからじわじわ悔しさが溢れだしながらこのまとめを書いています。

僕も考えていた全体的な心構えとか、方針とかは相方の @f440 さんのエントリ ISUCON3 の参加記録 に読みやすくまとめらているので、合わせて読んでもらえると僕達がどう考えてISUCON3に取り組んだのかが深まると思います。

予選前

今回がISUCON初参加、ということもあり勝手があまりわかっていない状態だったので、@f440 さんにざっと解説してもらい過去の問題(ISUCON2KAYACさんの社内ISUCONRubyに書き直して)をチューニングする、みたいなことをしていました。

予選でやったこと

予選の問題は、メモスニペットアプリ(投稿するテキストがMarkdownで編集できる)でした。 セッションを伴うWebアプリは出題されそうだよね(KAYACさんの社内ISUCONがまさに)という話は事前にしていて、出題されたときに予想どおりでテンションが上がったのを覚えています。 また、公開されたamiからAWSに自前で用意して挑戦するというのも、AWSに慣れた身としては気持ちも楽でした。

やったこととしてそんなに多くなく、

  • クエリのチューニング
  • DBのチューニング
  • Varnishによるページキャッシュ
  • テーブルのインメモリ化

という感じでした。 クエリのチューニング、DBのチューニングは早々に潰せ、 さらに、Varnishをページキャッシュとして導入したことにより一気にスコアを上げることができたまではよかった。 のだけれども、アプリが軽くなってくると、Markdownで変換しているところが重たくなってくる、のは目に見えてわかってきていたのに、どうでもよいチューニングを繰り返して、結局1位を取れず、2日目2位、総合5位で予選通過という結果になりました。

予選が終わってからの反省会で、「なんで巨大なテキスト取ってきてsplitしてるんだよ」「普通Redcarpet使うよね」「カウントしてるところとかRedisでincrするでよいのでは」と、冷静になると効果的なチューニングができることに気づいて少し凹んだりもしました。

予選を終えて

予選の結果があまりにも不甲斐ない終わり方だったので、 メンバーとチャットで予選の反省会しつつ、本戦に向けての準備を各自する、ということを続けていました。

  • 「今までずっと、コンテンツはテキストベースだったよね」
  • Flickrみたいな画像のアップローダーとか来そう?」
  • 「INSERTばかりでなくて、UPDATEやDELETEが大量にあるのでは」
  • SNS系だよなぁ。タイムラインは複雑になると破綻するし」
  • Twitterもどきはそろそろ出題されそう」
  • 「本戦でも多くてもテーブルは3つか4つくらいだよね」

などなどお互い予想しつつ、僕はもっぱらこういったアプリのデータ構造をRDBで持つアプリにしてチューニングする、ということを繰り返していました。 たとえばRDBじゃなくてKVSにする場合はどういう持ち方にするだろうか、キャッシュとの親和性を高めるにはどうすればいいか、等を考えていました。 具体的には、MySQLからRedisへデータ構造に落としこんだり、キャッシュとアプリの親和性を高めるために、ワーカーでアプリの断片を生成しつつ、nginxのSSIで組み立てるみたいなことも特訓していました。

また、KAYACさんが予選で使用したGo製のベンチマークツールも公開してくれていたので、手を加えて予選の問題を改造して、削除や更新も追加して負荷をかけたりして、ベンチマーク作る側からすればどういう問題が出そうか、みたいなことも考えたりしました。

どうでもいいですが、Go、大変良いですね。最近Rubyより書いています。

本戦でやったこと

本戦のお題は、公開レベルを設定できる画像のみ投稿可能なTwitterでした

構成がシングルページアプリケーションと今となっては珍しくないですが、しっかりした作りでチーム内で「シンプルに作られてて良い」みたいな話もしつつ、兎に角プロファイルです。 予選のときに、いろいろ手当たり次第な感じにチューニングを進めてしまったことを反省して、本戦では、アプリを読み込む、プロファイルをとることに注力しようとう話を事前にしていました。

まずは、コードを読みつつ画面を見ながらアプリを操作しつつ静的解析してからデータ構造を把握したところで、 素の状態で、以下のプロファイルを取ります。

  • Apacheのアクセスログ
  • MySQLのクエリログ(long_query_tme=0)
  • Sinatraのアクションログ(enable :logging)

これらのログから、「画像配信」がボトルネックだということが、すぐにわかりました。 DBにはほぼ負荷がないことに若干戸惑いもしました(mysqldumpslowすると一撃でわかります)。

そこで、画像配信をどうすれば速くできるか。午前中はそればかりを話し合っていました。アクションログや、アプリのコードを読みつつ、

  • 初期配置された画像はすべて事前にすべてのサイズで変換しておく
  • アプリで変換済みのものがあれば変換しない
  • 変換したら、保存して2度変換しない
  • アイコンはすべてpublicだよね? これはすべて変換してしまって静的ファイルとして返す
  • 画像も公開範囲は途中で変更できないので、publicなものはアイコンと同様に静的ファイルとして返す
  • 投稿されたときに非同期でワーカーで画像を変換する

という一連の案を素早く閃けたのは、なかなか良い感じでした。 あとは、作業分担して淡々とチューニングをすすめるだけなのですが、

  • 画像のバックアップ先がMacBook Airだった
  • そのままMacBookAirで、画像変換スクリプトを走らせてしまった(MacBook Proあるのに!)
  • ワーカーを作成したけど、思ったほど効果が出ない
    • 最初Watchrで書いたのですが思う通り動かず、Guardにしたのですが、ファイル検知が遅すぎて使い物になりませんでした。あとから、フロントからResqueなりSidekiqに投げればよかったとか思いつき悶絶したり
  • 構成として、1台のみアプリを動かして、4台にnginxを配置してフロントにしたのだけど全台をフロントにしてそれぞれでアプリを稼働させればスケールしてた

と、予選と同じくやはり本番中ではなぜか気づけず、タイムアップ後やチャット上で冷静になった頭で考えるとあれこれアイデアが浮かんでくるというなんともやり切れなかった感が残りました。 ただ、アグレッシブな方法を取らず、基本を忠実に、Failせずに完走できたのは良かったと思います。

まとめ

とにかくISUCONでは、

  • 熱くなっても冷静になる
  • ふつうのWeb技術力
  • アプリケーションを短時間で把握する力
  • Web技術の引き出しを増やして、問題解決のために使えるものを選択できる力
    • 知ってるとかちょっと使えるだけではだめで、これをこう使うと問題をこう解決できる、というレベルにシフトしておく

が、大切だなーと実感しました。 仕事と同じで、問題解決にはまずどこが問題なのかを把握するところから始めます。 ISUCONでは、それを7時間という短い時間内に的確に見極めて、手を入れていくのはとても面白いものがありました。

調度良い難易度の問題を作成してくださったKAYACさん、また、インフラを提供してくださった、データホテルさん、イベントを開催していただいたLINEさん、本当にありがとうございました。 オシャレ怪盗スワロウテイルはここで解散となりますが、また来年リベンジしたいと思っています。