今日 2014/11/24 に放送された Rebuild.fm の ep. 68 で ISUCON の話題があって、興味を持ったので解いてみようという気になりました。
ISUCON についてはもともと知っていたし、全く興味がないというわけではなかったけど、忙しかったり得意分野とか意識の問題とかでなんとなく足が遠のいていて、参加経験はありません。
AMI も公開されていますが、ベンチマークとか動かしてものすごい額課金されたら辛いので、とりあえずローカルで動かしたりベンチマーク取ることを目標にしてみます。
過去問の取得
GitHub 上に公開されていたので適当な場所に clone します。
データベースの準備
先ほど取得したプログラム中にデータベースをセットアップするスクリプトが含まれているようです。
データベースは 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 でアクセスできます。
データベースセットアップ時点でユーザデータもインポート済みで、予選当日マニュアル に記載された手順でログインの動作確認ができます。
例として、ここで求められる「お客様ご契約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 となったようです。
とりあえずここから始めて、何をやったらスコアがどれぐらい変わるのか、試してみようと思います。