いよいよ、isucon5 予選当日。
isuconの説明は、以下のsiteでどうぞ。
簡単にいうと、インフラ&プログラマ運動会。
結論からいうと、スコアは最高9,000超で予選落ち。
以降、予選当日のタイムライン。
09:30前 - 10:00
MacBook Air 3台、外部モニタ2台、ホワイトボード、iPadのモニタ化1台を、某社の会議室でsetup。当日のファイル管理用bitbucketをclone。
もちろん、お菓子も十分。
準備万端で10:00を迎えようといたら、予選開始が、11:00 になることが、告知される。
10:00 - 11:00
idobataや、twitterを見ながら、だらだら過ごす。
気持ちはわくわくしてるので、テンション高め。
11:00 - 12:00
isucon5 予選のGCE用imageが解禁。
当初、imageのschemeが、Cloud Storageのgs://ではなく、http:// だったので、とりあえずdownloadする。
ああ、これだとまず自分のCloudStorageにuploadしてからやるんだよね。
GCEをあまり知らない人だと、わからないんじゃないかと心配(にやにや)しながら、
isucon5のレギュレーションページを3人でじっくり読み合わせする。
その間に、ポータルのimageファイルのscheme変更され、がgs:// になったけど気が付かず。
10分ほどでidobataから気が付いて、おもむろにinstanceをレギュレーション通りに起動する。
スタートダッシュする作戦ではないので、問題なし。
担当Bは、Ansibleで開発環境の構築。
ここで、問題発生。途中から、instanceに接続できなくなる!
いろいろ試してみると、自分達の会議室の無線LAN(某社の来客用)から、GCEのisucon5用マシンにつながらない!
ということで、それぞれテザリングや、自分の持ちserver、社員用無線LAN経由で接続することに。
この問題は、12:00くらいに自然解消。なんだったんだ。。
担当A,Cは、instanceのWebにアクセスして、mixiによく似たISUxiを動かしてみる。
そして、その仕様書を作ろうと、全画面のrouteと概要、画面項目をA4用紙に手書きしてみる。
12:00 - 13:00
仕様調査。
予選ポータル画面から、benchmarkを依頼してみる。実行!
「Fail」
いきなり、スコアがでないほど、エラーとなる。
「なにもしてないのに!」
と、お客様のマネをするが、内心は焦っていない。
「きっと、benchmarkにまだバグあるんだよ、もしかしたら、お題の元sourceはバグ入りでリリースされたんじゃないの?」
この余裕は間違いだったかもしれない。Rubyは元sourceにバグはなかったし、他のチームはこの時点でスコアを伸ばしていた。
twitterで一部話題となっていた、インスタンスガチャでハズレを引いたのかもしれない。この時点でinstanceを替えていたら。。。
また、mysql serverのデータはisucon4のように、元データがないので入れ直しができない。思う存分indexの張り替えやalter tableできるようにバックアップを取ろうとするが、entries が巨大(2G)でとても終わらない。
並行作業とはいえ、あきらめがつくまで時間を無駄にしたような気がする。
その後もbenchmarkの結果に、ネットワークエラーが頻発して、だれが悪いのかわからない状態になる。
が、Webブラウザの動作確認などで、プログラムが悪いわけではないのは確信していたので、途中のスコアは無視することにした。
数度試して、やっとbenchmarkが正常に走ったので、アクセスログを解析し、先ほど手書きした仕様書にそれぞれのアクセス数を記述し、改善のポイントを把握する。
ただし、この時点でアクセス回数は1 routeにつき、最大3回。
最終的には数百回いくことになるが、いったい。。。
13:00 - 13:30
昼飯。それぞれお弁当を購入して、だらだら話しながら食べる。
休憩も大事。
13:30 - 15:00
改善案の検討。
mysqlのテーブルと、そのDDL、レコード数を確認して、書き出す。
そして、お待ちかねのsource code review。
改善項目は、以下の通り。
1. 片思いでも友達
友達関係(relations)は
「これって、片思いでも友達じゃね?」
と気が付く。relationsのoneとanotherのテレコは必要ない。
いそいそとsqlを直しはじめる。
SELECT COUNT(1) AS cnt FROM relations WHERE (one = ? AND another = ?) OR (one = ? AND another = ?)'
上記だと、ORから後ろがいらない。
2. 足跡のredis化
footprintsをRedis化する設計をする。
ユーザIDをkeyにして、footprints(足跡)を残した他ユーザをhash構造で保持する。
SQLにすると、こんな感じ。
SELECT user_id, owner_id, MAX(created_at) AS date FROM footprints GROUP BY user_id, owner_id, DATE(created_at)
3. 友達のメモリ展開
一番重い「/」では、とりあえず友達リスト配列に展開し、「/」の処理ではその配列を参照する。
ということで、担当Aのredis化の実装と並行して、担当Cが「片思いでも友達」の実データ検証。
担当Bは、uniconのunix domain socket化など足回りをこつこつ直して、mysqldumpslowやkataribeをsetupしつつ、SQLのEXPLAIN作業を進める。
15:00 - 17:00
実装の反映始まる。
修正したcodeをreviewしつつ、benchmarkでスコアを確認する。
NetworkエラーでFAILすることもあるが、まだ、焦るとこじゃない。
この時点で、やっと3000点超える。
スコアが伸びないのは、テーブル entriesのアクセスが遅いこと。秒単位で重い。
でも、entriesはレコード数150万、SQL文でJOINも多く、全てRedisに載せるわけにもいかない。
ということで、entriesのbody(text項目)が足を引っ張っているとあたりをつけ、
カラムbodyを除外した 新規テーブル s_entries を作成する。
JOINの際は、s_entries を利用し、結果(たいてい10件以下)がでたら、
entriesのid指定で、entries 1件をselectする。
これが当たり。スコアがぐっと上がる。
調子にのって、できるだけentries -> s_entriesに変更する。
17:00 - 19:00
チューニング中心で作業。
新規ロジックの追加は、もうしない。ログなどから、SQLを中心に改善項目を探す。
- kataribeで重いroute「/」を中心にSQLを改善する。
- rubyのprofilerで「/」がSQLを1600回以上叩いていることが判明し、 該当箇所(index.erb、entries.erb)の N+1問題を改善する。
スコアが、9000点を超える。
さらに改善して、1万超えを期待しつつ、最後のbenchmarkが走る。
7300点超。。。。。
終わった。
19:00 -
反省会。
会議室を片づける。ホワイトボードをキレイにし、ゴミはゴミ捨て場へ。
モニタを戻す際、仕事場へ顔を出したために、ちょっと仕事をしなくてはいけない羽目になった担当Bを残して、居酒屋へGO。
15分後、担当Bも合流して、あれこれ楽しく話しながら、段取りはよかったものの、ペースが遅かったなどと、反省会。
共通するのは、「楽しかった!」ってこと。
仕事だといろんなしがらみでできないことに、自由にチャレンジできるは素晴らしい。
1日をこんなに充実してがんばれるなんて、なかなかない。
ありがとう、運営さん。来年もチャレンジします。
後日
ベンチマークの仕組みが公開されたので、ベンチマーク用に「n1-highcpu-2(vCPU x 2、メモリ 1.8 GB)」を1台借りて試したら、スコアは 10002.8点。
1万点超えてた。予選突破はできないけど、ちょっとうれしい。
コメント