ISUCON9 Finalのベンチマークを動かして遊んでみた

ISUCONとは

ISUCONとはWebアプリケーションの高速化を競うコンテストです。2011年から毎回開催されていまして、現在(2019年時点)で9回コンテストが開催されています。詳しい説明は公式ページがあるので、そちらを見ればよいかと思います。コンテストで動かすソースはgithubで公開されています。 https://github.com/isucon コンテストで扱う言語はgo, ruby, python, php, perlと良くWeb系で扱われる言語は含まれているようです。

ISUCON9 Finalの環境構築

ISUCON9の本選で扱われたソースは以下になります。 https://github.com/isucon/isucon9-final

環境はdockerで構築するようですが、ビルドにMakefileを使っており、また以下のベンチマークビルド用のMakefileを見るとMac, Linux向けでビルドを実行しているのが確認できます。 https://github.com/isucon/isucon9-final/blob/master/bench/Makefile

自分はWindowsの環境で確認しようと思ったのですが、そのままではうまく動かなそうだったのでHyper-v上のubuntuで環境を構築することにしました。

環境の構築方法はREADMEにのっていた以下のコマンドを実行しました。

git clone git@github.com:chibiegg/isucon9-final.git
cd isucon9-final
(cd webapp/frontend && make)
export LANGUAGE=go
docker-compose -f webapp/docker-compose.yml -f webapp/docker-compose.${LANGUAGE}.yml build
docker-compose -f webapp/docker-compose.yml -f webapp/docker-compose.${LANGUAGE}.yml up

これで以下のようにdockerが起動していることが確認できました。

~$ docker ps
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS                                          NAMES
262db77c105b        phpmyadmin/phpmyadmin   "/docker-entrypoint.…"   10 hours ago        Up 31 minutes       0.0.0.0:8082->80/tcp                           webapp_phpmyadmin_1
7b5e3c09392a        nginx:1.17              "nginx -g 'daemon of…"   10 hours ago        Up 31 minutes       0.0.0.0:5000->5000/tcp, 0.0.0.0:8080->80/tcp   webapp_nginx_1
901cc941f8e7        webapp_webapp           "go run main.go util…"   10 hours ago        Up 31 minutes       127.0.0.1:8000->8000/tcp                       webapp_webapp_1
1d80f0847562        mysql:8                 "docker-entrypoint.s…"   10 hours ago        Up 31 minutes       33060/tcp, 0.0.0.0:13306->3306/tcp             webapp_mysql_1
7ab922f48244        golang:1.12             "go run main.go"         10 hours ago        Up 31 minutes                                                      webapp_payment_1

次にREADMEREADMEにのっている通り以下のURLにアクセスしフロントエンドを表示し、それからログインするためのユーザ登録を実行してみました。

http://127.0.0.1:8080

フロントエンドのページは表示できたのですが、ユーザの登録時にMySQL周りでエラーが発生することが確認できました。

docker-compose.ymlMySQL周りの設定を確認してみたところ、MySQLのデータは mysql:/var/lib/mysql となっておりdockerで共通のvolumeを使っていることが確認でき、./sql:/docker-entrypoint-initdb.d とあるのでソース上にあるSQL/docker-entrypoint-initdb.d にマウントし、 ./mysql/conf.d:/etc/mysql/conf.dMySQLの設定ファイルをマウントしていることが確認できます。

  mysql:
    image: mysql:8
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    environment:
      - "TZ=Asia/Tokyo"
    env_file:
      - ".env"
    volumes:
      - mysql:/var/lib/mysql
      - ./sql:/docker-entrypoint-initdb.d
      - ./mysql/conf.d:/etc/mysql/conf.d
    # development only
    ports:
      - "13306:3306"

気になるのは ./sqlディレクトリ内にあるSQLが実行されているかですが、以下のように直接確認しに行ってもDBが作られていなかったので初期化SQLが実行されていなかったようです。

$ docker exec -it MySQLのコンポーネントID mysql -uroot -proot
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 97
Server version: 8.0.18 MySQL Community Server - GPL

Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.02 sec)

DBおよびユーザの作成が必要なのですが、以下のソース内では環境変数がなければデフォルトの設定を使うようになっているようで、初期では特に環境変数も指定されていないのでデフォルトのDB、ユーザで接続できるようにしておきます。

user := os.Getenv("MYSQL_USER")
if user == "" {
    user = "isutrain"
}
dbname := os.Getenv("MYSQL_DATABASE")
if dbname == "" {
    dbname = "isutrain"
}
password := os.Getenv("MYSQL_PASSWORD")
if password == "" {
    password = "isutrain"
}

それからwebapp/sql内のSQLを実行したら、MySQL周りのエラーが発生しなくなりユーザの登録ができるようになったことが確認できました。

ベンチマークの実行

次にベンチマークを動かしてみます。READMEにのっている通り、以下のコマンドを実行します。

cd isucon9-final
(cd bench && make)

それから、ubuntuの環境で動かしていたので以下のコマンドでベンチマークを実行します。

bench/bin/bench_linux run --payment=http://localhost:5000 --target=http://localhost:8080 --assetdir=webapp/frontend/dist

結果は以下になり、初期では1316のスコアになりました。

{"pass":true,"score":1316,"messages":["GET /api/train/seats: リクエストに失敗しました (タイムアウトしました)","POST /api/auth/signup: リクエストに失敗しました (タイムアウトしました)","エンドポイント成功回数: 298","スコア: 1331","ペナルティ: 15"],"available_days":10,"language":"golang"}

ここからパフォーマンスを改善することでスコアを伸ばせるのですが、公式のページの方に解説がありますので、これを参考に修正していけばよいかと思います。

GET /api/train/searchの改善

解説の記事では列車の検索や空席の検索でクエリの実行回数が多いとのことと、あとインデックスを張る場合はorder byを適切に指定する必要があるとのことだったのでそれらを踏まえて修正しました。 github.com 結果として初期コストの1300くらいから2500くらいまでは上がりました。

dockerでの環境構築の部分を含めてコンテストのソースを見るのは慣れてないと大変ですが勉強になりそうで、動くところまで行ったら解説をもとに修正するのが面白くなりそうでした。また、普段触らない言語であってもサンプルの動くコードを見ることで覚えるきっかけによさそうでした。