ISUCON9予選で総合4位になり本選進出を決めました
ISUCON9予選1日目に「いんふらえんじにあー as Code」として参加し、1日目に3位(1位が棄権したため2位)で予選通過を勝ち取りました。
予選通過はISUCON4以来、5年ぶりです。なお、総合順位は4位だった模様です。
メンバー紹介
チーム名 いんふらえんじにあー as Code
あいこん | なまえ | やくわり |
---|---|---|
netmarkjp | 司令塔 | |
ishikawa84g | レギュレーションやコードやログやDiscordを見る情報官 | |
matsuu | バリバリ実装する前衛 |
最終構成
nginx --+-- app(go) --+-- mysql 1台目 | | +-- app(go) --+ 2台目 | | +-- app(go) --+ 3台目
2台目と3台目は /login
のアクセスのみ振り分け(bcryptのハッシュ処理のため)
nginxとmysqlは初期バージョンのまま変更せず
スコアの遷移
事前準備
去年のISUCON8予選で敗退した際に原因分析を行ったところ、自分自身に以下の課題があると感じていました。
- 職業プログラマーではないこともあり実装速度が遅い
- Go言語の基本機能をきちんと把握できておらずドキュメントを見ながら実装していた
- 過去のISUCONの復習を深いところまでできていない
これらの課題改善のため1年かけて以下に取り組みました。
- AtCoderにGo言語で挑戦して基礎に慣れつつ実装速度をあげる
- HighLoad Cupの過去問を解いてGo言語のWebアプリ実装に慣れる
- ISUCON過去問を限界までチューニングしてチューニング力を高める
自分ではHighLoad Cupへの挑戦が一番大きな成果だと思います。 HighLoad Cupはマニュアルを元にアプリケーションを実装するオンライン版ISUCONのような大会です。 2018年と2017年の過去問が公開されており、ていつでもベンチマークを実行できます。みんなやろうぜHighLoad Cup。 ただしHighLoad Cupのマニュアルはロシア語もしくは英語のみです。
HighLoad Cupについては前回のブログ記事にも書きましたのでご確認ください。
方針・戦略
詳しくはnetmarkjpのブログをご確認ください。
matsuuが当日やったこと
文章としてまとめるのが下手くそなので箇条書きで失礼します。 あと細かく記録取ってませんでした。ごめんなさい。
15000までの道のり
- ログイン後に環境構築
- nginx、app、mysqlのログ周り整備
- kataribe、pt-query-digest(percona-toolkit)、dstatのインストール
- net/http/pprofを使ったプロファイル環境整備
- nginxチューニング
- error_logをerrorからinfoに
- keepalive_requestsを1000000に
- http2有効化
- upstreamを使ってバックエンドとHTTP/1.1とkeepalive有効化
- 静的ファイルをnginxから直接配信する設定に
- mysqlチューニング
- itemsに
(status)
,(created_at)
,(seller_id, status)
,(buyer_id)
のインデックスを追加
- itemsに
categories
テーブルが更新されていないことを確認し、アプリのインメモリに保存/users/transactions
のN+1の一部を解消(items取得時にUserSimple相当の内容も取得する)UserSimple
をアプリのインメモリにキャッシュ保存(sync.Map
)- NumCellItemsは更新されるため、更新のたびにキャッシュを更新
- デッドロックが多発するようになる
- appからDBへの接続に失敗するエラーが多発
- appからMySQLの同時接続数を最大4096まで引き上げ
- パスワードのチェック(bcrypt実装)でCPU負荷が高くなっていた
- 同じパスワードをそのまま保存してはいけないレギュレーションがあるため、複数サーバに負荷を分散させることに
- isucariアプリをサーバの2台目、3台目に配置して/loginのみ2台目、3台目にアクセスを振るよう変更
configs
をアプリのインメモリに保存- nginxでToo Many Open Filesのエラーが発生した
/etc/nginx/nginx.conf
にworker_rlimit_nofile
を追加- ついでに
/etc/systemd/system/isucari.golang.service
にLimitNOFILE
を追加
- temp fileの生成を抑制するため
/etc/nginx/nginx.conf
にclient_body_buffer_size
を追加 - 1台目サーバのCPU負荷が落ち着いてきたので
Campaign: 0
=>Campaign: 2
16000までの道のり
GET /users/transactions.json
(getTransactions)でtransaction_evidences
とshippings
のN+1を解消POST /buy
のデッドロック対策としてsync.Map
のLoadOrStore()
を使って楽観ロックもどきを実装した- 1台目サーバのCPU負荷が落ち着いてきたので
Campaign: 2
=>Campaign: 4
19000までの道のり
- http.DefaultTransportのMaxIdleConnsPerHostを4096に引き上げ
26000までの道のり
- 外部APIへのステータス確認(
APIShipmentStatus
)でStatusがdoneになったものをアプリのインメモリでキャッシュ、複数回アクセスが発生しないように
工夫した点
ここらへんを工夫しました
- ベンチマークの事前処理、事後処理を行うシェルスクリプトを作成
- 繰り返しやる作業はas codeだ!
- ベンチマーク中にアプリやミドルウェアのログを
tail -f
などで眺めるの重要- パフォーマンスに直結するログがポロッと出てくることがある
- 500エラーを返す場所が複数あるもののどの500エラーが発生したのかわからなかったのでlog.Print()を挟んで場所を特定
- 複数ある
SELECT * FROM items WHERE id = ? FOR UPDATE
のうちどれが原因で詰まってるか分かりにくかったSELECT /* postBuy */ * FROM items ...
のように書き換えてSHOW PROCESSLIST
で目視判別しやすく
- Alibaba Cloudで事前に過去問を解いて練習した際にredis-serverが正常に起動しない問題に気づく
- (いつもの)sshrcで個人別競技環境をさっとセットアップ
- vim最新版やneovim最新版もインストール
- サーバは基本的に1台目のみ操作、ベンチ直前にrsync転送とssh経由でのsystemd再起動を実施
- ソースコードや設定の変更はサーバ上で直接vim/nvim
- ISUCONは短期決戦。デプロイの仕組みを作ってもデプロイを待ってられない
- VSCodeのRemote Developmentを事前に試したものの安定性に難があり断念
- VSCodeのVim拡張でdd後にカーソルが飛ぶ問題もつらかった
- 同じ場所に集まって横並びに座る
- やりとりは基本口頭で、文字情報の共有はSlackで
まとめ
若者に負けてらんねー!本選がんばるぞ!
AtCoderとHighLoad Cupによるスキルアップ術 - ISUCON予選突破を目指して
こんにちは、ISUCON駆動のmatsuuです。
第1回ISUCONではそこそこ良い順位につけたものの、その後下降の一途をたどりここ数年に至っては予選を突破できてない現実。
この現実を省みて今の自分に足りないものは何かと考えた結果、以下の結論に至った次第。
- プログラミングの実装速度が遅い
- メインの開発言語としているGo言語力が弱い
今の職業はインフラエンジニアなこともあってちょっとしたプログラムを組むことはあってもGo言語をがっつり扱うのはほぼISUCONだけという状態。これはいかんね。
上記2つを改善すべく、2019年からAtCoderとHighLoad Cupを始めてみた。
AtCoder
言わずと知れた競技プログラミング。昔はGo言語に対応していなかったらしいが今は使える*1。
競技として早く解くためにはよくある操作(標準入力処理や文字列変換など)をスニペットとして保存しておくのが良さそうだが、自分の目的は手を速く動かすことなのでそのようなスニペットは用意せず毎回手書きで書いている。
AtCoderの進捗
スキル的にはC問題が解けない時がある程度なのだが11回目にして初の4完。 アルゴリズムに少し詳しくなった気がする。
AtCoderの効能
以前に比べてGo言語に慣れ親しんだ気がする。文字列処理やslice、mapなどを扱う際に都度ググらなくてもよくなったのは大きい。
ただ、まだ手を動かすのが速くなった感触はない。今後もっとスピードアップしていきたい所存。
HighLoad Cup
高負荷システム開発コンテスト。誤解を恐れずにいえば長期戦オンラインISUCONでしょうか。
ロシアのMail.ruグループが主催しており、ロシア語と英語が用意されている。
過去に2回行われており、現在は過去問(Sandbox)が公開されている状態。過去問でもランキング機能があるので競うことができるぞ!
HighLoad Cupの特徴は以下の通り
- 英語(もしくはロシア語)で書かれた仕様があるのでそれを満たすように自力で実装
- リファレンス実装はない
- コンテストの期間は1ヶ月強あることもありボリュームは多め
- HTTPのリクエストとレスポンスのサンプルがダウンロード可能(実際の負荷テスト)
- アプリの提出はDockerコンテナで。コンテナ上で動くならどんな言語/ミドルウェアを使ってもok
HighLoad Cupの進捗
ここ1ヶ月ぐらい細々とSandbox 2018に取り組んだ結果、先週末に初めて完走して末席にランクイン。
英語仕様の行間を読んだり、ロシア語のソート問題に戸惑ったり、公式サポートのロシア語が飛び交うTelegramに英語で突撃したりね。
リファレンス実装がないというのはこんなにも大変なことだったのねと痛感。
引き続き上位ランクインを目指してチューニングしていく所存。
HighLoad Cupの効能
まとめ
*1:goのバージョンは1.6だけど
ISUCON8予選1日目にチーム「SELinuxはEnforcing以外あり得ない」で参加して最終スコアは26,221でした
もはやこのブログはISUCON参加記に成り下がってしまってますが。
今年も@ishikawa84g 、@netmarkjp、@matsuuの3人でISUCON8に挑んできました。このスコアじゃ今年も本選進出は難しそうですね。がっくし。
チーム名のとおり、DisabledだったSELinuxはEnforcingにしてフィニッシュです。
やったこと
3人で作業分担をしてたので、基本的に自分がやったことをまとめます
- ベンチ前の準備スクリプトとベンチ後の集計スクリプト(kataribe, pt-query-digest)を用意
- getEvent内のN+1をやっつける
- ORDER BY RAND()を消してみたもののランダムじゃないと怒られるので差し戻し
- sheetsは増減しない上にsheet_idから算出できるので、できるだけ利用しないように実装変更(sheetsひっぺがし)
- /api/users/:id のN+1をやっつける
- 残席数(remain)はeventsにカラムを追加して格納するように変更したものの、デッドロックが発生するのでremainsテーブルを別途作成して格納するように(しかし最終的にfailになるので差し戻し)
- /api/events/:id/actions/reserve 内の ORDER BY RAND() をやらないために全sheetに乱数のカラムとインデックスを追加してソートするように(しかし最終的にfailになるので差し戻し)
- 1台目のh2oとwebappとmariadbはそのまま、2台目と3台目にwebappを稼働させ、/initializeのアクセス以外は2台目と3台目に振るように実装変更(しかし最終的にfailになるので差し戻し)
まとめ
上記スクリーンショットにもあるとおり、直前でもsuccess/failしておりなかなか厳しい内容でした。
今回はトランザクションをきちんと意識した上で改修できるかが肝だったとの認識なのですが、ある程度性能が上がってくるとfailが発生してしまい、結局古いコミットにまで差し戻して(それでもたまにfailが出る)無難なスコアでフィニッシュせざるを得ない苦しい展開でした。つらい。
自分で変更した実装に問題があった可能性が9割以上あるのですが、もしかしたらGo実装でgetEvents内のgetEvent呼び出しが同一トランザクションになっていなかったのがある種の罠になっていたのではないかと考えています。もしかしたらgetEventsのN+1を退治すれば結果として解消したのではと思うと、うーん悔しい。問題が公開されたら追試しようと思います。
手を抜く実装にしたらしっかりベンチマーカーが検知しててムムムやるな、と思わず唸る良い実装だったと思います。すべてのサーバーサイドプログラマーはトランザクション処理とはどういうことかというのを学ぶ良い教材になるのではないでしょうか。みんな過去問やろうな。
ISUCON7予選1日目にチーム「ババウ」で参加して最終スコアは205148でした
Webサービスをいい感じにパフォーマンスチューニングするコンテスト
ISUCON7予選1日目に @netmarkjp, @ishikawa84g, @matsuu でチーム「ババウ」にとして参加しました。最終スコアは 205148 でした。
考察
netmarkjp
- 例年通りの役割分担がしっかり機能して気持ちよくできた
- 視点を変えたり休憩とったりがいい感じにできた
- 去年の何もできなかった無念は多少供養できた
- 練習をきちんと活かせた
- ベンチが安定しててすごくよかった
- BGMは東京スカパラダイスオーケストラでした
matsuu
- トラフィックがボトルネックになる問題をなかなか解決できずにいたが、Cache-Controlにpublicを入れることを思いつけた
- 304応答が安定して発生しない理由が生成される画像の更新日時がサーバ毎に異なるためであることに気づけた自分を褒めてあげたい
- Pixiv社内ISUCONを使ってじっくりチューニングしたときの経験がとても役に立った。ありがとうPixiv
- Discordチャットが結構よかった。ゲームじゃなくてもこういったイベントにとても向いていると思うのでオススメ
- NEW GAME!!を初めて見たがエンジニアあるあるでグッときた。なるほど。
ishikawa84g
最終構成
[nginx -- (varnish) -- gunicorn]x2 -- [mysql]x1
事前準備
チーム内体制の構築
環境構築用レシピを用意
itamaeを使って以下のプロビジョニングを自動化
過去問を解いて予行演習
事前公開のレギュレーションを熟読
大事
当日作業
11:30 昼食
落ち着いて美味しい昼食。ハンバーガー画像を参加者チャットに投下
12:00 - 13:00 ※開始前
ISUCON参加者専用チャットでNEW GAME!!の話題が盛り上がったためAmazonビデオで視聴開始
13:00 開始予定時刻
ポータルサイトが...オープンしない
13:10 運営よりDiscordで緊急連絡
methane - 今日 午後1時10分 もうちょっと、ごめん!
13:30 ログイン後
予め用意していた環境構築レシピを実行
並行して、各マシンのスペックと、使われているミドルウェアを確認
サービスの画面を見たりコードを読んで、どんなアプリなのかざっくり把握
ポータル上でサーバが降順で並んでて、「先頭2台」がどれなのかちょっと混乱した
MySQLのチューニングで/dev/shmに格納しようとしたところAppArmorに引っかかったため、AppArmorを調整
/etc/apparmor.d/usr.sbin.mysqld
に以下を追記
# ISUCON2017 /dev/shm/ r, /dev/shm/** rw, /proc/** r, /sys/devices/system/node/ r, /sys/devices/system/node/** r,
変更後、Profileをリロードし、MySQLを再起動する。
sudo apparmor_parser -r /etc/apparmor.d/usr.sbin.mysqld sudo systemctl restart mysql.service
14:08 インデックス追加
スロークエリーから遅いリクエストを調べてインデックス追加
ALTER TABLE image ADD INDEX (name); ALTER TABLE message ADD INDEX (channel_id);
14:30
一瞬だけ1位獲得
https://twitter.com/matsuu/status/921609614881202178
netdata見たのはこの頃までで、あとはdstatとtop
tmuxで3台分並べて表示。操作はtmuxのsynchronized-paneで楽々実施
14:33 /icons/の静的ファイル化
DBに格納されている画像データを静的ファイルとして出力する実装を行う
def get_images(): cur = dbh().cursor() cur.execute("SELECT name, data FROM image") for row in cur.fetchall(): output = "/home/isucon/images/{}".format(row['name']) with open(output, 'wb') as file: file.write(row['data'])
出力した画像はnginxから直接応答するように変更
location /icons/ { expires 60s; alias /home/isucon/images/; try_files $uri @upstream; } location @upstream { proxy_set_header Host $http_host; proxy_pass http://127.0.0.1:5000; }
14:34頃 DBとアプリ間のトラフィック改善
dstatで見ているとDBとWEBの間のトラフィックが多いのにベンチマーカーとWEBの間のトラフィックが少ないことに気づく。
同じ内容の画像が同じファイル名で重複登録されており無駄にDBからの取得が発生していることに気づいたので1行だけ取得するよう修正
cur.execute("SELECT * FROM image WHERE name = %s", (file_name,)) cur.execute("SELECT * FROM image WHERE name = %s LIMIT 1", (file_name,))
14:57 静的ファイルのgzip_static化
静的ファイルを予めgzipして保存しておき、nginxの gzip_static
で応答するように
gzip_static on; gzip_types image/svg+xml text/css text/javascript application/javascript application/font-wof f application/vnd.ms-fontobject;
15:21頃 画像を生成する
画像をINSERT時やSELECT時に出力するよう変更。
しかしサーバごとに画像を生成する実装のためこの後しばらくスコアが伸び悩む
- 画像にexpiresを付けてみる→多少改善するものの根本解決にはならず
- /fetchの1秒待機を外してみる→効果なし
- 206レスポンスを返してRangeヘッダーで返せばいいのでは?→206応答を簡単に実装する方法がわからず
- limit_rateで代用したらタイムアウト
- 302リダイレクトで応答を遅らせればいい?→302リダイレクトはエラーになった
17:24頃 /fetchのチューニング
SQLを見直してN+1を解消
SELECT channel.id AS channel_id, CASE WHEN message_id IS NULL THEN (SELECT COUNT(*) FROM message WHERE channel_id = channel.id) ELSE (SELECT COUNT(*) FROM message WHERE channel_id = channel.id AND message_id < id) END AS unread FROM channel LEFT JOIN haveread ON channel.id = haveread.channel_id AND user_id = ?
18:17頃 画像ファイル生成時に更新日時を合わせる
ベンチマークからのHTTPリクエストをtcpdumpで記録してどのようなリクエストが届いている確認してみる
tcpdump -w dump tcp port 80
その結果をWiresharkに食わせて確認していたところ、If-Modified-Sinceヘッダーを見つけて思いついた。
「画像ファイルの更新日時を合わせてないから304応答を返せていないのだ」と。エウレカ!
def write_image(data, name): output = "/home/isucon/images/{}".format(name) with open(output, 'wb') as file: file.write(data) os.utime(output, (1508559193, 1508559193))
1508559193
は 2017/10/21 13:13:13
、ISUCON7予選1日目の開始日時。
18:33
帯域が多少マシになったのでwebサーバのCPU負荷を下げるために gzip off
18:51頃 /messageのチューニング
SQLを見直してN+1を解消
if last_message_id > 0: cur.execute("SELECT message.id, name, display_name, avatar_icon, message.created_at, content FROM message JOIN user ON message.user_id = user.id WHERE message.id > %s AND channel_id = %s ORDER BY message.id DESC LIMIT 100", (last_message_id, channel_id)) else: cur.execute("SELECT message.id, name, display_name, avatar_icon, message.created_at, content FROM message JOIN user ON message.user_id = user.id WHERE channel_id = %s ORDER BY message.id DESC LIMIT 100", (channel_id, ))
19:00頃 画像ファイル生成時の出力先を/dev/shm/にする
with tempfile.TemporaryFile(dir="/dev/shm/") as f:
19:10 PythonプロファイラWerkzeugの導入
打つ手が少なくなってきたのでWerkzeugでのプロファイルを実施。
from werkzeug.contrib.profiler import ProfilerMiddleware app.wsgi_app = ProfilerMiddleware(app.wsgi_app, profile_dir="/tmp/profile")
gprof2dotを使ってdotファイルを生成し、graphvizを使ってpngファイルを生成。
gprof2dot -f pstats --colour-nodes-by-selftime --show-samples /tmp/profile/* > profile.dot dot -Tpng profile.dot > profile.png
20:27
sleep対応でgunicornのワーカーを減らしてスレッドを大幅にUP
/etc/systemd/system/isubata.python.service
で--workers=1 --threads=1000
と設定
20:31 MySQLパラメータ調整
アプリ側で Too many connections
が出ていたのでMySQL設定変更
max_connections = 8192
と open_files_limit を設定- /lib/systemd/system/mysql.service に
LimitNOFILE=65535
を追記
20:35頃 テンプレートエンジンJinja2のチューニング
jinja2のbytecode_cacheを有効に
app.jinja_options = app.jinja_options.copy()
app.jinja_options['bytecode_cache'] = jinja2.FileSystemBytecodeCache()
20:49 画像データの設置周りを修正
アップロードされた画像をtmpファイルに出力した際に最終的な設置場所にハードリンク
os.link(f.name, output)
20:50過ぎ 再起動テスト
再起動テスト実施
再起動後にベンチ実行したらスコアが乱高下したのでいい感じになるまで繰り返しベンチ実行
与負荷側が安定してて高速で助かった
21:07 ベストスコアがでるまでベンチマークを繰り返す
本日のベストスコア 205148
が出て歓声
21:13 終了
お疲れ様でした。
書籍「Linuxステップアップラーニング」
著者である @ryosuke927 さんからご恵贈に与りました。感謝。
- 作者: 沓名亮典
- 出版社/メーカー: 技術評論社
- 発売日: 2017/04/11
- メディア: 大型本
- この商品を含むブログを見る
Ubuntu 16.04 LTSをベースにLinuxの基本操作を学べる初心者向け書籍ですが、この本の素晴らしいところはほぼすべてシェルでの操作方法をまとめ上げてるところ。現場主義の一冊として素晴らしい。
環境構築としてVirtualBoxを紹介するのも理にかなってるんですよね。ドライバなど本筋とは異なる部分で躓くのは実にもったいないので現実的で良いと思います。
ただ、この書籍の1つ残念なところは、pp.12-13の「主なLinuxディストリビューション」にGentooがないところですね。マジか。今やChromebook*1やDockerホスト*2、Pepper君*3などで世界を席巻しつつあるGentoo系OSが紹介されないなんて、私は悲しい。
4/11発売です。季節柄、新人教育にも最適だと思います。是非ともご検討ください。
ISUCON6本選で名誉運営としてお手伝いしてきました
@matsuu #isucon ご協力いただき誠にありがとうございました!チームとしては残念な結果となりましたが、予選において多大なるご尽力をいただきましたのでmatsuuさんを名誉運営として本選にご招待したいと思います(本選出場ではなく運営としての参加です)ご検討くださいませ
— ISUCON公式 (@isucon_official) 2016年9月18日
この話を頂いた時は正直「名誉運営とは」と思ったのですが、本選に進むつもりで予定も開けてたしと二つ返事で引き受けることにしました。
本選運営のお手伝いとしてやったことは以下のとおり。
Azure関連
Azureの特性を踏まえた上での構成検討やワンクリックデプロイのためのテンプレート作成などを行いました。
予選ではデプロイの仕組みにcustomData使われていましたが、customDataは実行スクリプトをbase64に変換する必要があり、変更のたびに手間がかかるので、カスタムスクリプト拡張機能を用いてデプロイすることにしました。
customDataはUbuntuなど一部のLinuxでしか動作しないため、カスタムスクリプト拡張を利用するのがオススメです。
また、せっかくだからAzureのサービスを有効活用しようとの流れから、AzureのLog Analyticsを用いて競技者の各サーバにメトリックス情報を収集する拡張機能もインストールするようにしました。
ISUCON用のazureインスタンスでOMS Agent(==fluentd)が動いてるぞ……!
— tagomoris (@tagomoris) 2016年10月22日
そうそれそれ。でも実は私のミスでOMS側がうまく動かなかったんですけどね…。
CIの構築
本選の準備はGitHubのプライベートリポジトリで進めていたのですが、Azureのワンクリックデプロイを実現するためには公開領域にテンプレートを置く必要があることと、デプロイ用のファイル一式を固めたファイルを生成する必要があったため、CI環境を作成しました。
CIサービスはプライベートリポジトリでも無料で使えるWerckerを採用しました。
また、今回の本選はDocker環境だったのですが、Dockerのビルドおよび動作テストを行うために別途Jenkinsサーバも用意しました。DockerのテストにWerckerを使用しなかったのは、現状Dockerのビルド環境としてWerckerは使えないためです*1。
Python実装
本選のPython実装は私が担当させていただきました。
私が本職プログラマーだった頃は主にPerl/PHPを生業としており、インフラエンジニアな今はちょっとしたツール作成にPythonを書くことが多いもののPythonでWebアプリケーションを実装した経験はほぼゼロ。とても不安でしたがなんとか実装することができました。
当初はTwistedベースの実装を依頼されたのですが、着手してみたところ自分の力量では無理だと判断し、Flaskで実装しなおしています。
また、Python3+Gunicorn(sync)だとServer Side Eventsの実装でyield周りがうまく動かず、ギリギリになって急遽Python2+Gunicornに置き換えたりしました。
残念ながらPythonで予選を通過したチームはいなかったようなので誰からも試されていない可能性があります。もし良かったらPython実装で問題を解いてみてくださいね。
自分の知見に基づくアドバイス
その他、自分の知見に基づいたアドバイスをいくつかさせていただきました。
途中からの運営参加で色々と口を挟むと快く思わない人もいるかもしれないと思い、あまり出しゃばらないように心がけてました。
でも結果として出しゃばってましたね。ごめんなさいごめんなさい。
いやぁ運営ってこんなに大変だったんですね
私はただのお手伝い要員でしたが、それでも結構大変でした。ISUCON運営ってこんなに大変だったんですね。
ただのお手伝いでこれだけ大変なのに、メインの運営の皆様がどれだけ大変かは想像もつきません。
皆さん運営の方々に感謝しましょう。ありがとうありがとう。🙏
*1:できなくはない。QiitaにWerckerでDockerビルドをやる記事あります