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で
まとめ
若者に負けてらんねー!本選がんばるぞ!