ISUCON5予選を全体1位で通過しました

ISUCON5 の予選1日目にチーム「fujiwara組」(@fujiwara, @songmu, @sugyan) として参加して、全体通して1位のスコアで通過しました。

isucon.net

今回は ISUCON 1 の時の優勝チームを再結成という形になったわけですが、最初はISUCON 4の時と同じ社内のチームででようかと思ってたんですよね。ところが昨年優勝チームだった「LINE選抜 生ハム原木」が今回参戦できないということで、sugyanがチームどうしよう、と困っていたのでつい…*1

準備

今回はOSは Ubuntu(バージョン非公開)なのが事前にレギュレーションで公開されていたので(前年まではCentOS, Amazon LinuxなどのRedHatディストリビューションでした)、これはきっとWebアプリケーションの起動を systemd でやりたいんだろうなあと予測をして、Ubuntu 14.04LTS, 14.10, 15.04で最低限の初期設定とオペレーションができるように Chef cookbook を整備しておきました。

といっても、使いそうなパッケージをあらかじめ入れておくのと、各自のアカウント設定 (ユーザを作成、sudoできるようにする、githubの公開鍵をauthorized_keysに設置する) ぐらいです。kernelパラメータのチューニングなどは特に何もしていません。また、以下のツールも便利なので Chef でインストールするようにしておきました。

あとは作業用 Slack と github のプライベートリポジトリを用意ですね。

当日

メンバー全員の所属がばらばらなので、検討の結果、はてなさんの表参道オフィスの会議室を借りました。LINEさんのカフェも検討しましたが、おそらく参加者多数になるので落ち着かないのと、窓際のソファー席を確保できない場合椅子に不安が…ということで。

写真に写っているサブディスプレイは On-Lap のもので、現行品だと以下のようなやつになるでしょうか。持ち運びに便利なので ISUCON 本選でも活躍します。おすすめです。

13.3インチモバイル液晶モニター On-Lap 1303H

13.3インチモバイル液晶モニター On-Lap 1303H

やったこと

スコアの推移は以下のようになりました。

timestamp    score   
11:41:25    305 
12:03:33    0   FAIL: 
12:08:14    587 
12:18:27    1333       < indexを2個足した (fujiwara)
12:24:22    0   FAIL: 
12:27:22    1888       < my.cnf調整(fujiwara)
12:31:27    1672    
12:52:11    2008       < Gazelleに入れ替え (fujiwara)
13:47:38    2832       < entriesからtitleカラムを分離(sugyan, fujiwara)
13:49:24    2867    
13:52:20    5206       < entriesから1000件取ってるのをなくす(sugyan)
13:58:35    5500    
14:03:53    9353   < relationsから or を削った(songmu)
14:37:46    9593    
15:05:11    20  FAIL: 
15:08:00    10009   
15:15:17    10092   
15:25:29    10137   
15:30:49    0   FAIL: 
15:36:53    10138   
16:27:23    11247   < comments_for_meをredisのlistに(sugyan)
16:43:50    17  FAIL: 
16:48:45    17  FAIL: 
16:58:10    17  FAIL: 
17:04:20    17  FAIL: 
17:39:28    12389   < footprintをredisのsorted setに(songmu)
17:47:06    0   FAIL: 
18:01:09    17039   < userをmysqlではなくredisから読む(sugyan)、3回相手の属性調べてたのを一発に(songmu)
18:11:22    16402   
18:16:07    18193   < / でコメント10件毎回entriesを取得していたのをwhere inに(songmu)
18:23:54    26338   < redisから引いたuserをプロセスのメモリにcache(sugyan)
18:30:44    26153   < initializeでaofからredisを初期化(fujiwara)  
18:40:11    26694
18:42:59    27232   < 再起動試験後の最終提出スコア

基本的な作業の流れとしては

  • 自分がログ (主に 0.01 sec閾値で出力したMySQLのslow query logとalpでの集計結果) をみながら改善ポイントを指摘
  • songmu, sugyanがコードを読んで効率が悪いところを発見
  • 3人で対応方針を検討
  • songmu, sugyan両名がそれぞれ並列でコード修正
    • その間 fujiwara はインフラ周りの細かいこと(あんまりやることなかった)
  • 修正が終わったものをmergeしてベンチ

をひたすら繰り返していく、という感じでした。 途中 footprint の Redis 化の実装が難航して苦しい時間帯もありましたが、基本的には手戻りなしで一直線にスコアを上げていけたのでいい流れでしたね。

entriesからtitleカラムを分離

entries.bodyが巨大でtitleを表示するためだけに取得するのが重かったので、セオリー通りにカラムを分離しようとしました。ところが1.8GBあるentriesテーブルのALTERが、diskのスループットが全然でなくてなかなか終わらず。(1MB/secぐらいしかでてなかった)

結局snapshotからSSDインスタンスを用意してそっちで作業して戻す、ということをして乗り切りました。

alter table entries add title varchar(191) not null default '';
UPDATE entries SET title=SUBSTRING_INDEX(body, '\n', 1);

/initializeでのRedisデータ初期化

一部のデータはRedisに移す実装をしています。

ベンチ開始時に /initialize にアクセスが来て、そこで初期データ以外のデータは削除されるのですが、そのタイミングでRedisのデータも整合性を取って初期化する必要があります。

そこで普通にMySQLを読んでRedisに書き込みをすると30秒の時間制限を超えてしまうので、MySQLの初期データに対応するRedisの初期データセットを作った上で redis-cli config set appendoonly yes にしてaofファイルを吐き出しておき、initalize時にはそれを redis-cli で読み込んで一気にロードしました。2秒で終わります。

$redis->flushall();
system("/usr/bin/redis-cli --pipe < /home/isucon/appendonly.aof")
    if -e "/home/isucon/appendonly.aof";

感想

とにかく問題のボリュームが大きくてやることがいろいろあって、リモートベンチも快適に掛かって、tagomorisさんが「これがISUCONだ、という予選にしたい」といっていたのが実現できていて素晴らしい出題だったと思います。 運営の皆様、本当にありがとうございました。

本選でも3年ぶり3回目の優勝を勝ち取れるように頑張りたいと思いますので、よろしくお願いします!

*1:昨年のチームに不満があったとかでは全くないのであしからず……