2013/11/10

#isucon の本戦問題の解説に書くネタのメモと、とっかかりの見つけ方

こんにちは、れもんです。ISUCON3本戦の事前解答作成、問題フィードバック、ベンチマークのバグ取りとチート対策、当日のオープニングストーリーの作成と封筒ディレクターが主な担当です。ストーリーがだいぶ好評(?)だったようで安堵しています。

みなさんISUCON本戦お疲れさまでした! 予選の時は1日目の競技中にチェッカーのチェックが甘いところが見つかって出題者がダメージを受ける出来事がありましたが、今回は前日徹夜でベンチマークの問題点を探したり、特定の制限によるスコア荒稼ぎができないようにチェックしたりと大分力を入れて調整しました。問題の難易度と相まって前半の時間は全然みんなスコアが伸びてこないので逆に不安になりましたが、しっかり3分計測で6桁スコアを出してきたLINE選抜チームはさすがでした。

解説ネタ

さてまぁ準備の様子とか詳しい感想とかは別途書くとして、ひとまず忘れないうちにISUCON公式サイトに書く解説ネタを先にここにメモってしまおうかと思います。

  1. 画像投稿時に全サイズ変換してキャッシュ vs 画像要求時にオンデマンド変換+キャッシュ
  2. Preforkなアプリのtimelineの詰まり防止テクニック
  3. 複数台構成にするための投稿データ保存戦略(NFSとかキャッシュサーバとか)
  4. 画像の差分検出の話

こんなもんですかねぇ。

1つめの画像変換戦略はそれぞれメリットデメリットがあると思いますのでそれらについての話。

2つめのtimeline詰まり防止テクニックはもしかすると今回の本戦だとあんまり意識するレベルまでいかなかったかもしれないですが、出題作成中はworkloadあたりのtimelineの監視ワーカー数がもっともっと多かったためtimelineの詰まりが結構ボトルネックになりました。PerlのようにPreforkしたアプリサーバー(Starlet)にLong Pollingなコネクションが張り付くとコネクションを占有されるので困る件の対策の話。ちなみにtimelineの監視ワーカー数を減らした理由は初期状態のベンチを通すためというところが大きかったはずです。

3つめはぼくもハマったところですが、やっぱ最高スコアを出すには複数台で通信帯域を全部埋めるくらいベンチが回るようにする必要があるので、投稿画像をどうやって保存するかの問題ですね。

ちなみにNFSでうまくいった方っていらっしゃるんでしょうか? ぼくは結構試行錯誤したんですが、どうしてもテンポラリ名で書き込んだ後リネームしたファイルの存在が別サーバにリアルタイムに反映できなくて(ファイル無い時に0.1秒くらいsleepしたら98%くらい解決したけど完璧ではなかった)NFS使うのあきらめました。懇親会でウチの選抜チームがNFS使ってたのでその辺聞いたらあとでmoveじゃなくてcopyにしたら通るっぽいというような話を聞きました。あと、NFSは再起動順を保証してない環境では使いづらい(それに気付いて注意事項に含めました)。

4つめは各言語にあるいろんな画像ライブラリの話。convertコマンドをforkして起動するのをやめて言語ごとにそれぞれ存在する画像処理ライブラリに入れ替えるのを挑戦したチームは結構いたかとおもいますが、ImageMagick以外でdiffのエラーが出やすかったライブラリで詰まったところもあったのではないかとおもいます。というかPerlのImagerね。ぼくもつらい思いをしました。

こんなもんですかね? もしなんか「こんな点で詰まったから解説ほしい!」っていうのがありましたら教えてください。私は今回もPerlで解いたのでそれ以外の言語特有のハマりどころみたいなのはちょっとわかんないですが、もしあったら移植担当にちょっと聞いてみます。

どうやって解き始めるのがよかったか

今回だいぶ難しかったかと思います。10月中旬の本戦問題作成キックオフでおもわず「うーん」と唸ってしまい、「じゃあこれ解いて」とfujiwaraさんに問題を渡されたときも「コレ難しいですねぇ…どうやって解けばいいのかよくわかんないですねぇ」「大丈夫、出題者もまだあんまりイメージできてない」みたいなやりとりがあってそこからのスタート。

ざっくり動かしてみて足りないINDEXを貼ったらその時点でボトルネックはDBじゃなくてアプリのCPUになりました。ということは画像変換処理を軽減してネットワークが最大限回るようにするのがゴールかなと言うことで、事前解答作成は大体以下のような構成を目指しました。

  • 初期画像は全部リサイズ済みのものを用意してそれをアプリ経由で配信。ファイルは全マシンに配っておく
  • プロフィールのpng画像はアクセス制御不要なので、1回アプリから返したらフロント(nginx or varnish)でキャッシュ
  • 公開レベル2(全体公開)の画像はアクセス制御不要なので、フロントでキャッシュしたいけど、画像削除機能があるからそこを何とかする必要があるのでそこを含めて出来そうだったらやる
  • 5台のうちフロント+アプリが4台、MySQL+キャッシュサーバ(共有ストレージ)が1台
  • 新規投稿画像は共有ストレージに突っ込む
  • アプリに画像の表示要求がきたら共有ストレージに元画像があるかチェックして、あれば共有ストレージから返す、なかったら初期画像なのでローカルファイルから返す

優勝チームのsugyanさんのエントリを読む限り、わりと優勝チームの構成と似ています。はわわ。ちなみにぼくはWebDAVという発想がなかったので事前解答作成時にはRedisにむりやり突っ込んで回しました。「えぇ…それRedisなの…」というのはまぁごもっともな部分もあるのですが、問題作成中は安定性というよりもベンチマーク自身のCPU負荷のチェックとかボトルネック探しとかチート対策が主体なので、事前解答者にはどんな手を使ってもいいから出来るだけ高速にすることが求められます…ってそれISUCONそのものだ。

で、解き始めのとっかかりですが、とりあえず複数人でやる場合はアプリの改良はもちろん必要ですが、コア数が少ないのでDBをさっさと別サーバに分離するのをやるのがよいですね。dumpは初期データとしておいてあるので、別マシンにそれを展開して、DBの接続先をそこに向けるだけ。そのあとDBのクエリとかを眺めてDBチューニング出来るところを探したりしますが、今回はタイムラインのポーリングに使ってるSQLはあれ以上どうしようもなく、DBはINDEXを2つ貼っておわりという感じだったのではないかと思います。

アプリの作りとかファイルキャッシュ方法的に1台でしかアプリを動かせない構成になっていても、少なくともDBは別ホストに分けられますのでDBサーバを分離するだけでアプリのCPUに使えるリソースを増やすことができます。だいたいそこがスタートラインになるかと思います。

ちなみに1台100Mbpsの制限があるので、1台をフロント/リバースプロキシ専用にしてベンチを1台で受ける構成にするのは100Mbps以上の転送量をだせないため優勝できません。なので、あくまでもDNSラウンドロビンを前提としてリバースプロキシとアプリを同居させたものを並べた方が良いですね。

とまぁ完成形をイメージしてから作業できるとよいかなーと思いますが、完成形をイメージするにはボトルネックの計測とか、APIごとのアクセス量を数えることなしにはできませんので、やっぱりその辺を最初にやらないと完成形のイメージがブレる可能性があるのでそこが難しいところですね…。

ストーリーでは語られませんでしたが、VPSには100Mbps制限があるので、それを知っている先輩はスケールアップしたいという気持ちもありつつも、そもそも通信帯域側でスループットの限界が来ることを予見し、スケールアップよりもDNSラウンドロビンを使って複数のVPSを並べて帯域を確保したほうがサービスがスケールするイメージを持ったため社長に文句を言わなかったのです! はい、後づけの設定をいま思いつきました。

まぁでもああいう後先考えないような社長は勘弁してほしいものです。てかプロモ打つときはちゃんと事前に技術者に相談して!

うされもん @acidlemonについて

|'-')/ acidlemonです。鎌倉市在住で、鎌倉で働く普通のITエンジニアです。

30年弱住んだ北海道を離れ、鎌倉でまったりぽわぽわしています。

外部サイト情報

  • twitter
  • github
  • facebook
  • instagram
  • work on kayac