moshisora.memo

もくもくと。

ISUCON6にmorimoto組で出場して準優勝でした

ISUCON6に出場しました

ISUCON6本戦@渋谷ヒカリエ LINE株式会社 f:id:moshisora:20161022090704j:plain:w480

ISUCON6に「morimoto組」で出場して、準優勝できました。メンバーは、

という布陣でした。言語は自分とボンゴレ氏がPerlの方が使い慣れている関係でPerlを選びました。

スコア推移

ラスト1時間は他のチームのスコアが非表示ですがこんな推移でした。一台でチューニングしてから最後に複数台構成になったので結果発表ではダークホース感があったかもしれません…。

f:id:moshisora:20161024182157p:plain

Time PASS/FAIL Score 変更
11:02:53 PASS 942 Perl初期スコア
11:21:28 PASS 1159
11:58:31 PASS 1364
12:31:15 FAIL 253
12:36:34 PASS 1616
12:39:18 PASS 2051 nginx導入
12:43:40 PASS 3175 perlのプロセス数を200に(していたらしい)
12:46:41 PASS 3543
12:49:26 FAIL 12
12:53:18 PASS 2816
13:03:36 PASS 3314
13:35:14 FAIL 251
13:47:46 FAIL 251
14:07:14 PASS 3211 pointsをstrokesのカラムにjsonで保存するように
14:09:41 FAIL 0
14:10:56 FAIL 0
14:11:25 FAIL 0
14:14:25 PASS 3470
14:15:21 FAIL 0
14:16:38 FAIL 0
14:20:25 FAIL 0
14:24:03 PASS 4697 /img/* で返すSVGPerlレンダリング
14:27:05 FAIL 252
14:29:53 PASS 4781
15:17:25 FAIL 106
15:21:32 PASS 7412 /img/* をRedisにCache
15:31:52 PASS 6040
16:09:04 FAIL 253
16:10:24 FAIL 253
16:12:32 PASS 8020
16:54:56 FAIL 253
16:58:52 FAIL 253
17:10:31 PASS 9339 /api/stream/rooms をRedis pub/subとGo実装に置き換え
17:41:33 PASS 15241 2台構成
17:43:36 PASS 30714 5台構成
17:45:12 PASS 29301 isu02再起動試験
17:48:35 PASS 36067 isu01にnginx,MySQL,Redis、その他でPerl,Go,Reactの5台構成

タイムライン

10:00~12:00

開始間も無くしてAzureへのデプロイがうまくいかなかったり、アプリを読んだり触ったり、Perl実装を反映してベンチをかけるまでに1時間ほどかかりました。その後手元でperlのサーバを動かしたりと準備をしているといつの間にかお昼頃に。

12:00~15:30

pointsをstrokesのカラムにjsonで保存

points は一点ごとにレコードになっていて、必ず strokes と一対で、かつ更新もなさそうだったので、これは strokes と一緒にしてしまっていいよね、ということで strokes テーブルにカラムを追加して、そこにjsonで入れることに。同時に、初期データもjsonに変換するスクリプトを書いて strokes のカラムに保持、としたもののこの時点ではあまり効果は無し。

/img/* で返すSVGPerlレンダリング

SVGの生成はReact側からperlAPI経由で pointsjsonを取得して、React側で表示するSVGを生成していたので、これをperl側で生成するようにする実装が、残り2名で points の変更を入れている間にfujiwaraさんにより導入。

/img/* をredisにcache

stroke が新たに追加されるまで、 /img/* の結果は更新する必要がないので、これをredisにcache。新たに stroke が追加されたときにキャッシュを破棄するように。これで結構伸びて7412。

15:30~17:10

この時間が苦しかった…。

/api/stream/rooms をRedis pub/subとGo実装に置き換え

200プロセスでゴリ押していた stream がやはり辛いという話になり、この部分の実装をGoに置き換えることに。Perl側から新しいstrokeの投稿があったときにstrokeをpointsと一緒にpublish、Go側でsubscribeしてクライアントに配信するように変更。この変更でPerlでは500msごとに新しいstrokeをまとめて送信していたものが、新しいstrokeが来るたびに送信できるように。一旦は完成したかと思いきや、ベンチを走らせると 必要なwatch_countが送信されていません 旨のエラーメッセージでベンチが通らず、しばらくこれの解決に食われましたが、ボンゴレ氏が、「これ、publishされなくても500msしたら返さないといけなくないですか?」と閃いてなんとかベンチ通過。パフォーマンスも改善して9339。

新しいstroke投稿があったときにすぐ送信できる実装自体はあまりスコアに影響がないらしかったですが、触ってみるとレスポンス早くなっていいアプリになった感じはありました。余談です。

strokes countもredisにcache

毎回 stroke を全部拾ってきて scalar で個数だけ拾っている部分があった

$room->{stroke_count} = scalar @{ get_strokes($self->dbh, $room->{id}, 0) };

ので、これもredisに

my $stroke_count = $redis->get('stroke_count'.$room->{id});
unless ( $stroke_count ) {
    $stroke_count = scalar @{ get_strokes($self->dbh, $room->{id}, 0) };
    $redis->set('stroke_count'.$room->{id}, $stroke_count);
}
$room->{stroke_count} = $stroke_count;

stroke 投稿時に incr: $redis->incr('stroke_count'.$room->{id});

というものは書いてはいたものの、時間が差し迫っていたのでmergeしないまま複数台構成作業へ。

17:10~18:00

コードはフリーズして、複数台構成と再起動試験。
fujiwaraさんにお願いしてあとは自分たちは祈るだけという状態に。
最終的には isu01 でnginx, MySQL, Redisを動かしてそれ以外でPerl, Go, Reactをのせました(競技後に教えてもらいました)。

isu02 の再起動試験をしたのち、最終的な構成にしてスコアは36067。最後に isu01 を再起動はしたものの、これ以降スコアが伸びることもなさそうだったのと、時間が終了5分前くらいだったこともあり、これでスコアfixしましょうということに。ベンチは実行しないまま、各自 isu01 につないでちゃんとレスポンスが返ってることだけ確認してそっとコンソールとブラウザを閉じました。

結果

最終スコア 36067 で準優勝でした。

f:id:moshisora:20161022190409j:plain:w320

反省はやはり、コードくらいは2人でがんばるぞ!!で、fujiwaraさんに下回りをお願いするのが理想形だったと思うのですが、結局結構コードも書いてもらってしまったことと、Go慣れしてなくて選択肢も最初から絞り気味だったことでしょうか。お題を聞いた時点でPerlつらいしGoよさそうという話はあったのですが。golang書こう…

結果は嬉しいものでしたが、ブログを書きながら振り返ると、肝心なところは全部先輩頼りでしたし、16時を過ぎたあたりからは何やれば良さそうかも分からなくなってきて、個人的にはやはり全然戦えてないなーという感想で、悔しくも感じた初参戦でした。悔しさ胸に勉強せねば…

出題・運営の皆様、予選も本戦も、楽しい問題と競技を本当にありがとうございました。また協賛の皆様、楽しい懇親会をありがとうございました!
そしてチームの皆様、本当にありがとうございました!すごい体験をさせていただきました。嬉しくも悔しくもありましたがこれを糧にまた頑張ります。

最後にこちらを!