#ISUCON 4 本戦で5位でした (lily white)
結果発表 にある通り、 KLab から参加した3チームは、 2チームが8000点前後の過密地帯で、 lily white だけがほんの少し飛び出て5位でした。
1位、2位が Cache-Control 勢、 3, 4 位が Public/Private ネットワーク束ねた勢だと思われますが、 lily white は API は public, 動画は private と使い分けたため、 片側の帯域を使い切った 過密地帯から少し上に出ることができて5位でした。
以下、時系列でどんな事をやっていたのかを残していきます。
前日: 休み、モニタリング環境準備
実は ISUCON 本戦への参加をカンファレンスでの講演と同じ休出扱いにしてもらって、 前日は振替休日として家族サービスと休息に割り当てました。 おかげで去年や予選時と違って途中で集中力を切らさず最後まで頑張れました。
一応モニタリングツールの準備もしていて、 AWS に InfluxDB + Grafana の scripted dashboard を準備し、さらに Go で Linux 用の agent を作っておいて、当日すぐにダッシュボードを使えるように しておきました。
11:15~14:30 remote からのベンチマークを実行
最初にアクセスログを集計するまではチューニングに手を付けない、という方針で、2人に初期設定や アクセスログの集計スクリプトの準備 (URLに含まれる変数部分をまとめて同一APIを集計するカスタマイズ) を依頼しておき、問題と Go のソースコードを読みました。
選択言語ですが、もともと Python と Go から当日問題を見て選ぶ予定でした。 今回 Go を選んだ理由ですが、動画配信がネックになる可能性が高く、後でアプリの情報に基づいた 帯域制限などで nginx に勝てる可能性があると判断したからです。 (Google が ダウンロードサーバーを C++ から Go に書きなおした話とかも有名ですよね)
初期状態で iowait が発生していたので、 Redis の設定から AOF を削除して rdb に置き換えたり、 swappiness を 10 にしたりして、 workload=1 の状態では iowait がほぼ消えたことを確認しました。
ここから、帯域を調べるために remote ベンチマークをかけつついろいろ計測していきたかったのですが、 運が悪く複数のトラブルに見まわれ、やっとまともに public 側と private 側の2回のベンチが 成功したのが 14:30 頃でした。
inada-n [14:26] もう一回ベンチやります inada-n [14:26] まずは public [isucon@isu25a ~]$ ./benchmarker remote -H 203.104.111.173 -w 2 inada-n [14:29] { "errors": {}, "score": { "fail": 0, "success": 7289.18, "total": 7289.18 } inada-n [14:29] 次はローカル [isucon@isu25a ~]$ ./benchmarker remote -H 10.11.54.173 -w 2 inada-n [14:32] { "errors": {}, "score": { "fail": 0, "success": 3964.6, "total": 3964.6 }
この時点で、 public 側が 1Gbps 、 private 側はその半分強?と判断したのですが、 あとで考えると同時に実行している他のチームのベンチマークと被った側が遅くなっているだけだったようです。
ちなみに、 remote からのベンチができず苦しみ始めた 12:50 ころに、ダメ元で帯域を教えてもらえないか運営に 質問してみたのですが、
@methane すみませんが、お答えできません。が、動くようになった時に、両方試していただくと感じるものがあると思います。
というとても思わせぶりな回答があり、2つのネットワークの使い方に突破口がある可能性を強く意識してしまっていました。 この時点で負けが確定したのだと思います。
14:30~16:00 Redis の分散構成
とりあえずあとで分散構成を組むときにじゃまになりそうだったので、 log の redis 化は先に片付けました。
Workload を増やすと iowait と swap がまだ出るので、次は動画データを書く Redis の分散を始めました。
これはあとから考えると絶対に悪手なのですが、ちゃんと分散構成を組む前にまずもっと帯域の突破口を調べたい、 そのためにメモリが足りない問題だけ手早く片付けたいと考えていました。
結局突破口は見つからないまま、しかも workload を増やすとやはりメモリが足りなくなる (Redis から動画全体を読み込んでクライアントに返すので、同じ動画のコピーがメモリ上に複数存在する状態になる) ようだったので、 16:00 ごろから分散構成を始めました。
16:00~18:00 提出用の分散構成
突破口が見つからないまま、提出に向けての構成を作り始めます。
マシンは3台 (a, b, c) あるのですが、全部メモリは1GBで、CPUは a だけ1コア、それ以外は 2 コアでした。 しかし、 remote からのテストはまともな計測ができておらず、3台束ねて片側1Gbpsだということは分かっていませんでした。
この状態で組んだ構成が以下のとおりです。
- a: Web
- b: Web + Redis
- c: Web + ローカルベンチのアタック元
動画は、アップロードされた時点で id の剰余を使って3台に分散させ、クライアントには直接動画ファイルが あるIPアドレスを教えて proxy なしでアクセスされるようにしました。
この辺りで 33 万点がダッシュボードに乗りました。動画をまともに返していては、絶対にこの点数は出ません。どこかに突破口があるはずです。
そこで思いついた仮定が、クライアントは動画をストリーミング再生していて、最後まで再生が終わらなくても 途中で再生を止めてクリックしてくれるというものです。それなら、並列数を増やしつつ、動画をゆっくり返せば高得点がゲットできるはずです。
なので、APIアクセスはなるべく速く返し、動画のストリーミングは帯域を絞るために、速い(と思い込んでいた) public 側を API に割り当て、 private 側を動画に割り当てました。
18:00~19:00 remote アタックが一度も成功しないまま終了
ルールは読んでいたのですが、ベンチマーカーコマンドのヘルプやサポートチャットのログをちゃんと読んでいなくて workload の上限が 8 だという事を知らなかったので、 workload=12 で検証を始めました。
ローカルベンチは workload=8 になると全然メモリが足りなくて iowait だらけだし、 private 側もローカルだと速いので、この仮定を試すベンチには使えません。 スコアは無視して、ベンチが FAIL せず完走することの確認にだけ利用していました。
しかし、ローカルベンチは成功するものの、 remote ベンチが理由の分からない FAIL で完走しません。
18:30 ごろにローカルベンチで FAIL が再現し、そこからどうやら initialize で redis のリセットが ちゃんとできていないようだと判断して次のような修正をしました。
--- a/go/app.go +++ b/go/app.go @@ -474,10 +474,7 @@ func routeGetFinalReport(req *http.Request, r render.Render) { } func routePostInitialize() (int, string) { - keys, _ := rdMain.Keys("isu4:*").Result() - for _, key := range keys { - rdMain.Del(key) - } + rdMain.FlushAll() return 200, "OK" }
この変更前のコードのどこが悪いのか、または実は原因は別のところにあるのか今でも分かりませんが、 とりえずこれで複数回ローカルベンチが完走することを確認し、18:45 頃に最後の remote ベンチをかけました。 しかし、ラストの駆け込み需要で順番待ちが長く、結局実行前にフィニッシュとなってしまいました。
結局ラスト2時間ほど、一度も remote ベンチを通って無いのですが、結果的に FAIL せずに5位に入れたのはラッキーでした。
感想
ずっと帯域の問題だと思ったまま、しかも帯域を反映したベンチマークができる唯一の方法である remote が待ち時間が長かったり理由の分からないFAILばっかりでほとんどまともに計測できず、不完全燃焼のまま終わってしまいました。
Cache-Control / Expires ヘッダの件ですが、たぶん運営は、もっと早く気づくチームが複数現れると予想していたのでしょうが、 このへんの予想は難しいですね。自分が運営でも、やはり適切なヒントを提供することはできなかったと思います。
特にPCブラウザ向けのサービスを普段やってない弊社は、 WebView とか Unity とかキャッシュに クセがあるので、CDNとクライアントの実装をよく確認せずいきなりキャッシュの設定を他サイトからコピペするなんてまずあり得ません。「普段やってる事をやれば強い」とか「秘伝のタレを注いだらラッキーヒット」という攻略ルートは存在しませんでした。
他の決勝進出チームも、たぶん普段ならコピペしている設定も ISUCON では1つずつ効果を確認しながら やっていたので、なかなか気づかなかったのだと思います。
本当は今年こそ優勝して来年運営側に入るのが目標だったのですが、出題・運営の難しさを思い知らされて、 大分自信を無くしました。今年の運営に自分が入っていたらと想像しただけで胃が気持ち悪くなります。
運営の皆様、本当にお疲れ様でした。
そして今年の超難問を見事突破して2連覇したLINE選抜チームの方、おめでとうございます。 来年はまた出題側に戻っていただけるそうなので、またよろしくお願いします。