Hateburo: kazeburo hatenablog

Operations Engineer / 運用系小姑 / Perl Monger

Re: 結局、Go言語をやめる理由はなかった件

すみません、ISUCONのアレに火がついてしまったので..。 Advent Calendarとはとくに関係がありません。

qiita.com

qiita.com

こちらの記事をみて、気になってしまったので interpolateParams の追加とMySQLチューニングをしてベンチマークを回してみました。

ベンチマークの環境が公開されているのは素晴らしいですね。

github.com

8vCPU/16GB Memのc5a.2xlargeなEC2のインスタンスを起動して、以下の手順にしたがってdockerとdocker-composeをインストールしました。OSはUbuntu 20.04を使いました。

docs.docker.com

docs.docker.com

上記のbenchmarkのrepositoryをgit cloneし、compose build && compose upします。

$ git clone https://github.com/okdyy75/bench-docker.git
$ sudo docker-compose build
$ sudo docker-compose up -d

まず何も変更しない状態でのスコア結果は、

Python

$ sudo docker-compose run --rm python sh -c 'cd python; pip install -r ./requirements.txt && time python bench.py'
平均秒数:9.670429
real    1m 36.73s
user    0m 6.25s
sys     0m 2.79s

Go言語

$ sudo docker-compose run --rm golang sh -c 'cd go; go build . && time ./go'
平均秒数:11.70815524 (0x0,0x0)
real    1m 57.08s
user    0m 3.84s
sys     0m 10.83s

たしかに、Go言語の結果の方が少し悪くなっています。

ここで元記事にてmethaneさんから指摘のあった interpolateParams の設定をいれます。

diff --git a/www/go/bench.go b/www/go/bench.go
index 5c56714..a4b86d0 100644
--- a/www/go/bench.go
+++ b/www/go/bench.go
@@ -37,7 +37,7 @@ func getDB() *sql.DB {
        }
 
        dsn := fmt.Sprintf(
-               "%s%s@tcp(%s:%s)/%s?charset=utf8&parseTime=true",
+               "%s%s@tcp(%s:%s)/%s?charset=utf8&parseTime=true&interpolateParams=true",
                user, password, host, port, name)
 
        db, err := sql.Open("mysql", dsn)

そうすると、結果は

平均秒数:10.540287
real    1m 45.40s
user    0m 2.99s
sys     0m 6.26s

となり、pythonに追いつかないまでも、改善はしています。

ところで、ベンチマークを何回か動かしながら気になったのは、スコアが振れが大きく、iowaitがそれなりでているとところです。(Maki-Daisukeさんはioの影響を抑えるためNVMe SSDのついたインスタンスを使われておりました)

$ vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 0  1      0 11964048 140004 3405756    0    0     0  7872 8115 13468  1  2 95  2  0
 0  0      0 11964048 140004 3405932    0    0     0  7656 6939 11857  1  2 93  4  0
 0  1      0 11956236 140004 3414672    0    0     0 16284 7068 12074  1  1 94  4  0
 0  1      0 11956236 140004 3414672    0    0     0  7768 8375 14349  1  2 94  3  0
 1  0      0 11956236 140004 3414672    0    0     0  7464 7463 12275  2  1 96  2  0
 0  1      0 11955480 140004 3415720    0    0     0  8780 7971 12701  2  1 97  0  0
 0  1      0 11955480 140004 3415720    0    0     0  7712 8561 13845  1  2 97  0  0
 0  1      0 11955480 140004 3415720    0    0     0  7780 7966 13032  1  1 96  1  0
 0  1      0 11954472 140004 3416496    0    0     0  8788 7051 12944  2  1 90  7  0
 0  1      0 11964536 140004 3405576    0    0     0  9592 9176 16006  2  1 92  4  0
 2  0      0 11964284 140004 3405832    0    0     0  8220 7737 13731  1  1 92  5  0
 1  0      0 11964252 140004 3405928    0    0     0  8056 8078 12893  1  1 97  0  0
 0  1      0 11955416 140004 3414756    0    0     0 16132 6565 11829  1  1 91  7  0
 2  0      0 11955416 140004 3414756    0    0     0  7056 7333 12482  1  1 94  3  0

また、CPUも全然使えてません。

これはMySQL側がボトルネックになっている可能性が高いとみて、対ISUCON用秘伝のタレを投入して、再度実行してみました。

my.cnf を作成。4つのパラメータを追加

$ cat docker/mysql/my.cnf
[mysqld]
innodb_doublewrite = 0
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT_NO_FSYNC
innodb_adaptive_hash_index = 0

compose.ymlに追記して、mysqlを再起動

$ git diff
diff --git a/docker-compose.yml b/docker-compose.yml
index 94b3dc7..e39de9b 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -15,6 +15,7 @@ services:
     volumes:
       - "./.data/mysql:/var/lib/mysql"
       - "./log/mysql:/var/log/mysql"
+      - "./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf"
     ports:
       - "3306:3306"
   # php-fpm ################################################

$ sudo docker-compose restart mysql
Restarting bench-docker_mysql_1 ... done

結果は

Python

平均秒数:1.518821
real    0m 15.21s
user    0m 6.12s
sys     0m 1.69s

Go言語(interpolateParams=false)

平均秒数:1.770360
real    0m 17.70s
user    0m 3.04s
sys     0m 7.85s

Go言語(interpolateParams=true)

平均秒数:1.196632
real    0m 11.96s
user    0m 2.26s
sys     0m 4.95s

interpolateParamsが有効のGo言語が最速になりました。最初の10倍近い高速化ですね。

MySQLが高速化したことで順位が入れ替わるところはもう少し深堀してもいいかと思いますが、Pythonにしたってかなり速くなってますので、これくらいなら正直どの言語でやってもいいのではないかと思います。仲良くしましょう

Go言語を実行中のvmstatは以下のようになりました。

 vmstat 1
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 1  0      0 12075152 140028 3363636    0    0    10   453  441   22  1  1 98  1  0
 0  0      0 12075152 140028 3363636    0    0     0     0  139  257  0  0 100  0  0
 0  1      0 12015176 140028 3387952    0    0     0 25772 1950 2773  5  1 93  1  0
 1  0      0 11968016 140028 3391408    0    0     0 12732 31447 60372  9  7 84  1  0
 1  0      0 11967732 140028 3391376    0    0     0  4660 42484 86361  7  8 85  0  0
 1  0      0 11967748 140028 3391360    0    0     0 14592 35933 73675  6  6 87  1  0
 1  0      0 11967256 140028 3391228    0    0     0  6964 38245 77879  7  7 86  0  0
 2  0      0 11966928 140028 3391416    0    0     0 13028 36928 75262  6  7 87  0  0
 2  0      0 11966816 140028 3391440    0    0     0  7352 37506 76638  7  7 86  0  0
 1  0      0 11968984 140028 3391440    0    0     0 14120 32657 71858  8  7 85  0  0
 1  0      0 11969976 140028 3391468    0    0     0 15564 33490 69433  7  7 86  1  0
 2  0      0 11969976 140028 3391468    0    0     0  3392 40593 82689  7  8 85  0  0
 1  1      0 11971740 140028 3391440    0    0     0 15568 35473 72960  6  7 86  1  0
 2  0      0 11973520 140028 3391476    0    0     0  5420 36698 76628  8  7 85  0  0
 1  0      0 11973344 140028 3391476    0    0     0 13000 37315 77527  6  7 87  0  0
 1  0      0 11973392 140028 3391536    0    0     0  8284 34950 71476  6  8 86  0  0
 2  0      0 11973408 140028 3391536    0    0     0 13840 38778 79834  6  7 86  1  0
 1  0      0 11974132 140028 3391544    0    0     0  8692 37283 74883  7  7 86  0  0
 3  0      0 11974132 140028 3391544    0    0     0 10444 37224 77928  6  8 86  0  0
 2  0      0 11973108 140028 3391608    0    0     0 15688 34599 71052  6  7 86  1  0
 0  0      0 11980788 140028 3391728    0    0     0  6868 33311 72294  7  8 84  0  0
 0  0      0 12069692 140028 3363668    0    0     0  2588 2979 4393  1  1 98  0  0

iowaitが小さくなり、user, systemが以前より高くあがるようになりました。

topだと

top - 07:43:15 up  2:16,  2 users,  load average: 0.43, 0.11, 0.05
Tasks: 169 total,   1 running, 168 sleeping,   0 stopped,   0 zombie
%Cpu(s):  6.2 us,  3.9 sy,  0.0 ni, 85.9 id,  0.4 wa,  0.0 hi,  3.6 si,  0.0 st
MiB Mem :  15827.2 total,  11689.1 free,    689.4 used,   3448.7 buff/cache
MiB Swap:      0.0 total,      0.0 free,      0.0 used.  14833.8 avail Mem 

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                          
  44962 systemd+  20   0 1377796 216128  20612 S  77.4   1.3   1:29.73 mysqld                                                                                                           
  47046 root      20   0  710064  10828   3836 S  60.5   0.1   0:06.25 go   

ようになり、1vCPU 使い切りそうなぐらいまできてました。

ちなみに、MySQLのデータディレクトリをみんな大好きtmpfsにおくと、

Go言語(interpolateParams=true)

平均秒数:1.106469
real    0m 11.06s
user    0m 2.45s
sys     0m 5.07s

Python

平均秒数:1.394543
real    0m 13.97s
user    0m 6.15s
sys     0m 1.70s

もう少しだけ速くなります。

まとめ

ミドルウェア含めたベンチマークを行う時は、ミドルウェアやOSの設定なども気を配るのがいいですね。広い視点を保つためもにもISUCONは役に立つ。