kavo’s diary

備忘録

ISUCON10予選問題の疑似環境をGCP上に作る(初期スコア400前後)

概要

ISUCON10予選をなるべく当時と同じように解きたいので、疑似環境を作るためのメモ。

初期スコアが456だったので、近い値が出るようにしたい。

結論

N1の1vCPU/2GBメモリ、ゾーン永続ディスク20GBで作ったインスタンスでは(ベンチマーカー内包で測って)初期スコア400前後になる。 cloudshellですぐ起動したい場合以下のスクリプトインスタンス作成可能。

gcloud beta compute instances create isucon10q --zone=asia-northeast1-b --machine-type=custom-1-2048 --subnet=default --network-tier=PREMIUM --maintenance-policy=MIGRATE --scopes=https://www.googleapis.com/auth/devstorage.read_only,https://www.googleapis.com/auth/logging.write,https://www.googleapis.com/auth/monitoring.write,https://www.googleapis.com/auth/servicecontrol,https://www.googleapis.com/auth/service.management.readonly,https://www.googleapis.com/auth/trace.append --tags=http-server --image=ubuntu-1804-bionic-v20200923 --image-project=ubuntu-os-cloud --boot-disk-size=20GB --boot-disk-type=pd-standard --boot-disk-device-name=isucon10q --no-shielded-secure-boot --shielded-vtpm --shielded-integrity-monitoring --reservation-affinity=any

サーバスペック

GitHub - isucon/isucon10-qualify: ISUCON10予選より基本スペックは以下の通り。

  • 問題用 (3台)
    • CPU: 1 Core (AMD EPYC 7352)
    • Memory: 2 GiB
    • IO throughput: 100 Mbps
    • IOPS limit: 200 (Read / Write)
    • Interface: 1 Gbps
  • ベンチマーカ用 (1台)
    • CPU: 1 Core (AMD EPYC 7352)
    • Memory: 16 GiB
    • IO throughput: 100 Mbps
    • IOPS limit: 200 (Read / Write)
    • Interface: 100 Mbps

OSはUbuntu 18.04.5 LTSだった。

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.5 LTS
Release:        18.04
Codename:       bionic

GCPで再現する

スペック選定

CPU/Memory/Interface

GCPマシンタイプ  |  Compute Engine ドキュメント  |  Google Cloudから似ているスペックの構成を探すと、 共有ではないコアで1vCPUが選べるのが汎用(N1)だけのようである。 汎用(N1)のカスタムで1vCPU2GBにできる。 カスタム時のネットワーク下り(外向き)帯域幅(Gbps)は書いてない気がするが、近いn1-standard-1の帯域幅は2Gbps。

インスタンス自体が1コアでなくても、maxcpus=1を指定する方法もあるらしい。

#「vCPU」と「Core」の対応関係が調べてもいまいちよくわからない

IO throughput/IOPS

Storage options  |  Compute Engine Documentation  |  Google Cloudによると、ゾーン永続ディスクの最大持続 IOPS、最大持続スループット(MB/秒)は

ゾーンバランス永続ディスクの最大持続 IOPS、最大持続スループット(MB/秒)は

らしい。とはいっても、この最大持続 IOPS、最大持続スループットが本番インスタンスのIO throughput/IOPS制限と近い意味の数値なのかは不明。

IOPS(Input/Output Per Second)とは - IT用語辞典 e-Words

装置の特性により、読み込み(リード)か書き込み(ライト)か、シーケンシャルアクセス(連続した領域にアクセス)かランダムアクセス(飛び飛びに様々な領域にアクセス)か、転送するデータの量がどれくらいかによって1回の動作に要する時間が異なる。

このため、実用上は「4KBランダムライトIOPS」のように計測条件を明示することが多い。この例では、4キロバイトのデータを装置内の不連続な箇所に飛び飛びに書き込む動作を行った際のIOPSを表している。

例1:ゾーン永続ディスク20GB

カタログ上以下のはず。

fioを使ってストレージの性能を計測してみた - Qiitaを参考にfioというツールで測ってみる。fioのドキュメントは1. fio - Flexible I/O tester rev. 3.23 — fio 3.23-28-g7064-dirty documentation

IOPS/スループット測定目的×シーケンシャル/ランダム×R/Wの8パターン。

sudo apt update
sudo apt-get install -y fio
head -c 10G /dev/urandom > /tmp/test2g
ls -l /tmp/test2g 

fio -filename=/tmp/test2g -direct=1 -rw=read -bs=4k -size=2G -numjobs=64 -runtime=10 -group_reporting -name=file1           > 1--4k-seq--r.log
fio -filename=/tmp/test2g -direct=1 -rw=write -bs=4k -size=2G -numjobs=64 -runtime=10 -group_reporting -name=file1          > 2--4k-seq--w.log
fio -filename=/tmp/test2g -direct=1 -rw=randread -bs=4k -size=2G -numjobs=64 -runtime=10 -group_reporting -name=file1       > 3--4k-rand-r.log
fio -filename=/tmp/test2g -direct=1 -rw=randwrite -bs=4k -size=2G -numjobs=64 -runtime=10 -group_reporting -name=file1      > 4--4k-rand-w.log
fio -filename=/tmp/test2g -direct=1 -rw=read -bs=32m -size=2G -numjobs=16 -runtime=10 -group_reporting -name=file1          > 5-32m-seq--r.log
fio -filename=/tmp/test2g -direct=1 -rw=write -bs=32m -size=2G -numjobs=16 -runtime=10 -group_reporting -name=file1         > 6-32m-seq--w.log
fio -filename=/tmp/test2g -direct=1 -rw=randread -bs=32m -size=2G -numjobs=16 -runtime=10 -group_reporting -name=file1      > 7-32m-rand-r.log
fio -filename=/tmp/test2g -direct=1 -rw=randwrite -bs=32m -size=2G -numjobs=16 -runtime=10 -group_reporting -name=file1     > 8-32m-rand-w.log

$ grep IOPS *
1--4k-seq--r.log:   read: IOPS=15.0k, BW=58.7MiB/s (61.5MB/s)(3521MiB/60018msec)
2--4k-seq--w.log:  write: IOPS=15.1k, BW=59.1MiB/s (61.9MB/s)(3546MiB/60017msec)
3--4k-rand-r.log:   read: IOPS=145, BW=600KiB/s (614kB/s)(36.2MiB/61714msec)
4--4k-rand-w.log:  write: IOPS=296, BW=1201KiB/s (1230kB/s)(71.4MiB/60849msec)
5-32m-seq--r.log:   read: IOPS=3, BW=111MiB/s (116MB/s)(6688MiB/60238msec)
6-32m-seq--w.log:  write: IOPS=2, BW=71.9MiB/s (75.4MB/s)(4320MiB/60096msec)
7-32m-rand-r.log:   read: IOPS=2, BW=79.9MiB/s (83.8MB/s)(4960MiB/62094msec)
8-32m-rand-w.log:  write: IOPS=0, BW=11.9MiB/s (12.4MB/s)(736MiB/62051msec)

値が多いが、おそらくカタログのIOPSと比べる対象は4KBランダムリードライトの3,4だと思う。R/Wで145/296とカタログから想定した値の10倍くらいになっている。 スループットは32Mのシーケンシャルなのかランダムなのかわからないけどシーケンシャルだとすれば、R/Wで111/71.9MiB/sとカタログから想定した値の30~40倍くらいか。

例2:ゾーンバランス永続ディスク30GB

カタログ上以下のはず。

同様に計測する。

$ grep IOPS *
1--4k-seq--r.log:   read: IOPS=445, BW=1797KiB/s (1840kB/s)(107MiB/60851msec)
2--4k-seq--w.log:  write: IOPS=294, BW=1196KiB/s (1224kB/s)(71.1MiB/60853msec)
3--4k-rand-r.log:   read: IOPS=295, BW=1197KiB/s (1226kB/s)(71.2MiB/60852msec)
4--4k-rand-w.log:  write: IOPS=294, BW=1196KiB/s (1224kB/s)(71.0MiB/60853msec)
5-32m-seq--r.log:   read: IOPS=0, BW=16.9MiB/s (17.7MB/s)(1024MiB/60527msec)
6-32m-seq--w.log:  write: IOPS=0, BW=15.6MiB/s (16.4MB/s)(960MiB/61391msec)
7-32m-rand-r.log:   read: IOPS=0, BW=16.9MiB/s (17.7MB/s)(1024MiB/60515msec)
8-32m-rand-w.log:  write: IOPS=0, BW=15.6MiB/s (16.4MB/s)(960MiB/61404msec)

4KBランダムリードライトの3,4をみるとR/Wで295/294とカタログから想定した値の1.5倍くらいになっている。 32Mランダムリードライトのスループットは、R/Wで16.9/15.6MiB/sとカタログから想定した値の2倍くらい。 RWで揃うのは想定どおりだけど、普通の永続ディスクよりもスループットが下回ったのが想定外だった。単にfio実行パラメータが適切ではないからな気もする。

まあ結局よくわからないのでゾーン永続ディスク20GBで試してスコアを見ることにする。

構築

公式のansibleを使ってみる。普通にaptインストールすると古い2.5.1が入ってしまうので以下のようにする。

$ sudo apt update
$ sudo apt install software-properties-common
$ sudo apt-add-repository --yes --update ppa:ansible/ansible
$ sudo apt install -y ansible
$ ansible --version
ansible 2.9.13
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/hogehoge/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.17 (default, Jul 20 2020, 15:37:01) [GCC 7.5.0]

Ansible のインストール — Ansible Documentationより

127.0.0.1宛で1回試してみるが、ssh鍵設定していなかったので怒られる。

git clone https://github.com/isucon/isucon10-qualify.git
cd isucon10-qualify/provisioning/ansible
vi inventory/hosts
(127.0.0.1記載)

ansible-playbook allinone.yaml -i inventory/hosts

fatal: [127.0.0.1]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: Warning: Permanently added '127.0.0.1' (ECDSA) to the list of known hosts.\r\nno such identity: /home/hogehoge/.ssh/id_rsa: No such file or directory\r\nhogehoge@127.0.0.1: Permission denied (publickey).", "unreachable": true}

鍵追加

touch .ssh/authorized_keys
cat isucon10.pub >> .ssh/authorized_keys
cat .ssh/authorized_keys

chmod 600 .ssh/authorized_keys
ls -l .ssh/authorized_keys

cp isu10 .ssh/id_rsa
chmod 600 .ssh/id_rsa 
ls .ssh/id_rsa -l

#疎通
ssh 34.84.152.xx -i .ssh/id_rsa -l hogehoge

再実行してfailなく完了。 やった後に気づいたけどphpとかrubyのインストールが時間食うので使わないやつは外したほうがよい。

$ ansible-playbook allinone.yaml -i inventory/hosts
...
PLAY RECAP ********************************************************************************************************
127.0.0.1                  : ok=76   changed=72   unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   


Saturday 26 September 2020  22:33:40 +0000 (0:00:05.664)       1:05:45.373 **** 
=============================================================================== 
web-rust : Build Web Application Rust ------------------------------------------------------------------- 1215.58s
langs : Install PHP -------------------------------------------------------------------------------------- 986.44s
langs : Install Ruby v2.7.1 ------------------------------------------------------------------------------ 452.84s
langs : Install perl v5.32.0 ----------------------------------------------------------------------------- 399.46s
langs : Install python v3.8.5 ---------------------------------------------------------------------------- 256.83s
web-perl : Install isuumo.perl Pacakges ------------------------------------------------------------------ 141.91s
common : Install Package(Build) --------------------------------------------------------------------------- 74.59s
web-bootstrap : Build frontend application ---------------------------------------------------------------- 51.67s
web-ruby : Build Web Application ruby --------------------------------------------------------------------- 38.82s
web-bootstrap : Install Package(MYSQL) -------------------------------------------------------------------- 34.19s
web-bootstrap : Make initial data ------------------------------------------------------------------------- 31.82s
web-bootstrap : Install frontend packages ----------------------------------------------------------------- 29.90s
bench : Snapshot isucon10 --------------------------------------------------------------------------------- 25.66s
web-php : Install isuumo.php Pacakges --------------------------------------------------------------------- 20.89s
web-bootstrap : Export frontend application --------------------------------------------------------------- 20.45s
langs : Install Rust -------------------------------------------------------------------------------------- 18.64s
web-deno : Build Web Application deno --------------------------------------------------------------------- 18.59s
web-go : Build Web Application Go ------------------------------------------------------------------------- 14.70s
langs : Install Go 1.14.7 --------------------------------------------------------------------------------- 10.38s
web-python : Install isuumo.python Pacakges ---------------------------------------------------------------- 9.95s

動作確認

インスタンスの外部IPをブラウザで開いてアプリの動作を確認(本番のような転送は不要)。 f:id:kavohtn:20200920045956p:plain

isuconユーザに切り替えてベンチ実行。

su isucon #passはisucon
cd isuumo/bench

$ ./bench --target-url http://localhost
2020/09/26 23:13:24 bench.go:86: === initialize ===
2020/09/26 23:13:27 bench.go:98: === verify ===
2020/09/26 23:13:28 bench.go:108: === validation ===
2020/09/26 23:14:12 load.go:181: 負荷レベルが上昇しました。
2020/09/26 23:14:26 fails.go:105: [client.(*Client).SearchEstatesNazotte] /home/isucon/isuumo/bench/client/webapp.go:367
    message("POST /api/estate/nazotte: リクエストに失敗しました")
[client.(*Client).Do] /home/isucon/isuumo/bench/client/client.go:136
    code(error timeout)
    *url.Error("Post \"http://localhost/api/estate/nazotte\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)")
    *http.httpError("context deadline exceeded (Client.Timeout exceeded while awaiting headers)")
[CallStack]
    [client.(*Client).Do] /home/isucon/isuumo/bench/client/client.go:136
    [client.(*Client).SearchEstatesNazotte] /home/isucon/isuumo/bench/client/webapp.go:361
    [scenario.estateNazotteSearchScenario] /home/isucon/isuumo/bench/scenario/estateNazotteSearchScenario.go:214
    [scenario.runEstateNazotteSearchWorker] /home/isucon/isuumo/bench/scenario/load.go:100
    [runtime.goexit] /home/isucon/local/go/src/runtime/asm_amd64.s:1373
2020/09/26 23:14:28 bench.go:110: 最終的な負荷レベル: 1
{"pass":true,"score":406,"messages":[{"text":"POST /api/estate/nazotte: リクエストに失敗しました (タイムアウトしました)","count":1}],"reason":"OK","language":"go"}

成功。もう少しベンチを実行してブレを確認。

2020/09/26 23:17:05 bench.go:110: 最終的な負荷レベル: 1
{"pass":true,"score":389,"messages":[{"text":"POST /api/estate/nazotte: リクエストに失敗しました (タイムアウトしました)","count":5}],"reason":"OK","language":"go"}

{"pass":true,"score":398,"messages":[{"text":"POST /api/estate/nazotte: リクエストに失敗しました (タイムアウトしました)","count":6}],"reason":"OK","language":"go"}

{"pass":true,"score":389,"messages":[{"text":"POST /api/estate/nazotte: リクエストに失敗しました (タイムアウトしました)","count":3}],"reason":"OK","language":"go"}

大体400前後になった。本番の初回実行が456だったので少し低いが、WSL2上に構築して初期で1310になったときよりはまあ近い感じになったかも。 ベンチとアプリを一緒のサーバに入れてしまってる分は性能が落ちていると思われる。ネットワーク的にも影響あるかもしれない。