今更 ISUCON4 の過去問を Mac ローカル上で動かしてみる

今日 2014/11/24 に放送された [Rebuild.fm](http://rebuild.fm) の ep. 68 で ISUCON の話題があって、興味を持ったので解いてみようという気になりました。
ISUCON についてはもともと知っていたし、全く興味がないというわけではなかったけど、忙しかったり得意分野とか意識の問題とかでなんとなく足が遠のいていて、参加経験はありません。

AMI も公開されていますが、ベンチマークとか動かしてものすごい額課金されたら辛いので、とりあえずローカルで動かしたりベンチマーク取ることを目標にしてみます。

# 過去問の取得

GitHub 上に公開されていたので適当な場所に clone します。

https://github.com/isucon/isucon4

# データベースの準備

先ほど取得したプログラム中にデータベースをセットアップするスクリプトが含まれているようです。
データベースは MySQL で、事前にインストールしているものとします。

(ちなみに私の MySQL は適当に `brew install mysql` して `mysql.server start` しただけのものです。)

では init.sh を実行してみましょう。

```
$ cd isucon4/qualifier
$ sh init.sh
+ set -e
++ dirname init.sh
+ cd .
+ myuser=root
+ mydb=isu4_qualifier
+ myhost=127.0.0.1
+ myport=3306
+ mysql -h 127.0.0.1 -P 3306 -u root -e 'DROP DATABASE IF EXISTS isu4_qualifier; CREATE DATABASE isu4_qualifier'
+ mysql -h 127.0.0.1 -P 3306 -u root isu4_qualifier
+ mysql -h 127.0.0.1 -P 3306 -u root isu4_qualifier
ERROR 2006 (HY000) at line 1: MySQL server has gone away
```

コケました...

## `max_allowed_packet` の調整

エラーメッセージから調査したら `max_allowed_packet` 足りない可能性があることがわかりました。
確認してみると 4MB として設定されていました。

```
$ echo "SHOW GLOBAL VARIABLES LIKE 'max_allowed_packet'" | mysql -u root
Variable_name   Value
max_allowed_packet      4194304
```

問題になっている処理を確認すると `dummy_users.sql` SQL ファイルからダミーのユーザデータを INSERT しようとしているものでした。
このファイルが 13MB なので、キリよく 16MB 割り当てて再実行してみます。

```
$ echo "SET GLOBAL max_allowed_packet = 16 * 1024 * 1024" | mysql -u root
$ echo "SHOW GLOBAL VARIABLES LIKE 'max_allowed_packet'" | mysql -u root
Variable_name   Value
max_allowed_packet      16777216
```

正しく割り当てられたことを確認できたのでデータベースのセットアップを再実行してみます。
(なお、このやり方では mysqld 再実行時に設定がリセットされる点に注意が必要です)

```
$ sh init.sh
+ set -e
++ dirname init.sh
+ cd .
+ myuser=root
+ mydb=isu4_qualifier
+ myhost=127.0.0.1
+ myport=3306
+ mysql -h 127.0.0.1 -P 3306 -u root -e 'DROP DATABASE IF EXISTS isu4_qualifier; CREATE DATABASE isu4_qualifier'
+ mysql -h 127.0.0.1 -P 3306 -u root isu4_qualifier
+ mysql -h 127.0.0.1 -P 3306 -u root isu4_qualifier
+ mysql -h 127.0.0.1 -P 3306 -u root isu4_qualifier
ERROR 1292 (22007) at line 1: Incorrect datetime value: '2014-02-22 00:00:00 +0900' for column 'created_at' at row 1
```

またコケました...

## `sql_mode` の変更

エラーメッセージから調べたところ、`sql_mode` に `STRICT_TRANS_TABLES` が含まれている場合、このようなことが起こるようです。

```
$ echo "SHOW GLOBAL VARIABLES LIKE 'sql_mode'" | mysql -u root
Variable_name   Value
sql_mode        STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION
```

これを除外してみます。

```
$ echo "SET GLOBAL sql_mode = 'NO_ENGINE_SUBSTITUTION'" | mysql -u root
$ echo "SHOW GLOBAL VARIABLES LIKE 'sql_mode'" | mysql -u root
Variable_name   Value
sql_mode        NO_ENGINE_SUBSTITUTION
```

正しく設定されていることを確認したところでデータベースのセットアップを再実行します。


```
$ sh init.sh
+ set -e
++ dirname init.sh
+ cd .
+ myuser=root
+ mydb=isu4_qualifier
+ myhost=127.0.0.1
+ myport=3306
+ mysql -h 127.0.0.1 -P 3306 -u root -e 'DROP DATABASE IF EXISTS isu4_qualifier; CREATE DATABASE isu4_qualifier'
+ mysql -h 127.0.0.1 -P 3306 -u root isu4_qualifier
+ mysql -h 127.0.0.1 -P 3306 -u root isu4_qualifier
+ mysql -h 127.0.0.1 -P 3306 -u root isu4_qualifier
$ echo 'SELECT COUNT(1) FROM users' | mysql -u root -Disu4_qualifier
COUNT(1)
200000
```

うまくいったようです。
ついでに確認として `users` テーブルにデータが投入されているということもなんとなく確認しています。

# 参考実装アプリの起動 (Ruby)

ISUCON では参考実装として、競技のルール上正しく動作する Web アプリが複数言語で用意されているようです。
動作としては正しいものの、最適化の余地がいろいろあるので、そこを頑張ってスコアを競おう、という感じのようです。

今回は Ruby 実装を動かしてみます。

```
$ cd isucon4/qualifier/webapp/ruby
$ bundle install
$ bundle exec foreman start
```

デフォルトでは以下の URL でアクセスできます。

http://localhost:8080/

データベースセットアップ時点でユーザデータもインポート済みで、[予選当日マニュアル](https://gist.github.com/mirakui/e394ed543415852d34a6) に記載された手順でログインの動作確認ができます。

> 例として、ここで求められる「お客様ご契約ID」は isucon1、 「パスワード」は isuconpass1 を入力することでログインが行えます。

# ベンチマーカーのセットアップ

ベンチマーカーは ISUCON のベンチマークを行うプログラムで、これを実行することでスコアを自動的に計算するようです。
また、ベンチマークプログラムは Golang で実装されています。

さっそくビルドしてみましょう。
いろいろ苦労したのですが、以下のようにすることでデバッグ用のビルドができました。

## Gomfile に追記

以下のような追記を行わないと、うまくビルドができないようでした。

```
 cat Gomfile
# vim: ft=ruby
itself "github.com/isucon/isucon4/qualifier/benchmarker"
gom "github.com/isucon/isucon4/qualifier/benchmarker/ip" # この行を追記
gom 'github.com/codegangsta/cli'
gom 'github.com/moovweb/gokogiri'
```

## libxml2 のインストール

libxml2 のインストールが必要なので適当にインストールしました。

```
$ brew install libxml2
```

## gondler のインストール

Golang 用の Bundler みたいなものです。

```
$ gem i gondler --no-ri --no-rdoc
$ rbenv rehash # rbenv を使用している場合は忘れずに
```

## ビルドの実行

ビルド実行時に libxml2 のパスを環境変数で持たせないとうまくビルドできなくて、最終的に以下のような形でビルドできました。

```
$ PKG_CONFIG_PATH=/usr/local/Cellar/libxml2/2.9.2/lib/pkgconfig make debug
--> Installing build dependencies
gondler install
Install github.com/isucon/isucon4/qualifier/benchmarker/ip
Install github.com/codegangsta/cli
Install github.com/moovweb/gokogiri
touch .gondler
gondler build -ldflags "-X main.GIT_COMMIT \"720ceb2\" -X github.com/isucon/isucon4/qualifier/benchmarker/user.DummyUsersTSVMD5 \"02b75563229cc2d1bd38dbe824e72da6\" -X github.com/isucon/isucon4/qualifier/benchmarker/user.DummyUsersUsedTSVMD5 \"76360cdffcbc60cc538e3f6e487926e6\" -X github.com/isucon/isucon4/qualifier/benchmarker/user.DebugMode \"true\" -X main.DebugMode \"true\""
! You have enabled DEBUG mode.
```

# ベンチマークの実行

Ruby で起動した参考実装に対して、とりあえず Nginx も何もかませずに直接実行してみます。
`--host` オプションで対象ホストを指定できるようです。

```
$ ./benchmarker bench --host=localhost:8080
22:03:06 type:info      message:!!! DEBUG MODE !!! DEBUGE MODE !!!
22:03:06 type:info      message:launch benchmarker
22:03:06 type:warning   message:Result not sent to server because API key is not set
22:03:06 type:info      message:init environment
22:03:14 type:info      message:run benchmark workload: 1
22:04:15 type:info      message:finish benchmark workload: 1
22:04:20 type:info      message:check banned ips and locked users report
22:04:52 type:report    count:banned ips        value:0
22:04:52 type:report    count:locked users      value:2524
22:04:52 type:info      message:Result not sent to server because API key is not set
22:04:52 type:score     success:3170    fail:0  score:685
```

2 分弱で終了しました。
スコアは 685 となったようです。

とりあえずここから始めて、何をやったらスコアがどれぐらい変わるのか、試してみようと思います。