ISUCON9 に出て Ruby で予選通過した

ISUCON9 の予選にチーム「ソレイユ」で参加して 28 位でぎりぎり予選通過してきた。チームメンバーは osYoYu (@osyoyu) | Twitter霧矢あおい (@KOBA789) | Twitter で、なんだかんだ ISUCON5 の頃から参加している。ちなみに予選通過は初めて。

カッコ良いところは他の二人が書いてくれるとして、 自分は事前準備で Provisioning 周りとインスタ映えする Grafana ダッシュボード作成とアプリケーション周りのツール予習、 本番はドメイン知識貯めつつアプリケーションの調整をやっていたのでその辺を書く。 参加言語は Ruby で、最終得点は 10,090 点だった。

事前準備

参加 5 回目にしてチームで初めて準備と素振りというものをやった。勝因の 95% はそのおかげだと思う。

itamae 業

今回は周辺ツールのインストールに時間をかけないため、予め必要になりそうなものは全て itamae の cookbook を書いておいた。 後述するダッシュボードのための {node,mysql}_exporter とか、nginx, alp といった雑多なプロファイラなど、過去の参加記憶を掘り起こして書いてた。 あとはメンバーが適当なパッケージ名を issue に書いてくれるのでそれらのバイナリをポンするものも書いた。

github.com

事前に ubuntu(bionic)を使うと書いてあったので、その環境で動作するように Systemd Unit File も勝手に置くようにしてた。 素振り時に mtail の設定で正規表現のミスが見つかった意外に不具合もなく、本番も itamae apply してもらって完動してた気がする。

可視化業

チームメンバー全員可視化に関心があるおかげで、お家 Prometheus とお家 Grafana を持っており、 特に抵抗もないことから数値監視系は全て Grafana に寄せる話になった。

node_exporter を適当に入れて、 Node Exporter Full という既存のダッシュボードを持ってきて可視化したときは感動したと同時に情報量に惑わされてた。

grafana.com

最終的に過去必要になった Cpu, Memory, Network(receive, transmit), DiskIO だけに絞って PromQL を書いたら映えるダッシュボードが生まれた。

f:id:everysick:20190911002458p:plain
isucon monitor - 通称 isumon

ちなみに MySQL QPS は mysqld_exporter を、Nginx RPS は mtail で Nginx のアクセスログから HTTP Status Code ごとにサマリをカウントする設定を書いた。

正直 Nginx RPS が一番便利だった。403 Forbidden や 5XX 系の数がぼーっとしているだけで見えるので、そこから逆算して例えば出品者が自分の商品を買っていたり、売り切れ商品を買っていたりしそうだという目星をつけることができた。

本番

時系列に自信がそんなに無いので箇条書きで覚えていること書く。

  • 悠長にレギュレーション読んでいたら Alibaba ECS 完売してた
    • インスタンス生えるまで やれることがない スタンプを送りつつレギュレーション2周くらいした
  • 10:50 くらいで Grafana に node_exporter の状況がやってきて競技開始
  • koba789 や osyoyu はそれぞれかっこいいことやってた
    • alp や pt-query-digest の結果をベンチ後自動で GitHub issue に post してくれるやつとか(koba789)
    • rsync で 3 台一気に設定を同期するスクリプトの設定や mysql8 に移行するとか(osyoyu)
  • その間自分は
    • アプリケーション触ったり
    • APPLICATION_SPEC.mdEXTERNAL_SERVICE_SPEC.md を読み込んだり
    • エンドポイントや Table の schema 一覧引っ張ってきたり
    • /initialize 後のレコード数引っ張ってきたり
    • N+1 にコメント書いたり
    • index.html を nginx から返すようにしたり
  • 改善の最初は alp 等の結果を見つつユーザーをどうやって購買まで導くかチームで話していた
    • ベンチ直後の APPLICATION_SPEC.md にある isucari ステータス遷移表 ごとにレコード数を見て、どこの数値遷移で詰まっていてどうすれば購買フローをスムーズに流せるかなど
    • というのも過去の ISUCON と比べて初期の負荷が低くてリソースが余るなどしていたから
  • とりあえず売れる商品はあるのものの /buy が叩かれないことをどうにかしようとなる
  • 買うために必ず通る新着一覧やカテゴリごとの新着一覧で 403 が多い
    • コード上で 403 を返すのは「自分の商品を買う」「買えない(もう売れている)商品を買おうとしている」が原因だとわかる*1
    • レギュレーション曰くそこまで新着一覧の制約がきつくないのでそれらを非表示にするなどをした
    • 100イスコイン の椅子非表示とか値段順でソートとかやったけど全部怒られた。それはそう。
  • index.html を sinatra が返していたのを Nginx で返すようにした
  • この辺で 3 台構成になったので事前準備していた puma の設定を突っ込んだり調整したりした
    • 突っ込んだら Nginx が 502 を喋り始めたので SOMAXCONN や Backlog の設定もしてもらう(osyoyu)
    • 最終的な構成は server1 が LB と app, server2 が DB, server3 が app (without 画像配信) という感じ
  • categories をインメモリにしてたりした(koba789)
  • 403 問題がある程度解決したあたりから N+1 潰しが始まる
    • データ一般に強い氏(koba789)が殺めていくのを横で typo 指摘してた
  • N+1 が潰れたあたりで index の検討が始まる(koba789)
  • /buy が微妙に重いので rack-lineprof の結果見ながらなるほどって言ってる
  • server3 の Disk が調子悪い問題が出現する
    • server4 のセットアップが始まる(osyoyu)
    • 発覚から20分くらいでしれっとサービスインさせる(osyoyu)
  • transactions あたりの N+1 をとってる(koba789)
  • puma や campaign の調整してたら puma がスタックする問題が発生し始める(なぜ?)
    • 各サーバーで puma を restart して回る
  • スコア 9,210
  • 再起動試験(17:30 くらい)
  • 何故かスコア 1/3 になる
    • server2 で golang 実装が動いてたのを止めたら治る(なぜ?)
  • この時点で 8,530 なので予選通過ライン達してない
  • puma がスタックする(なぜ?)
    • 各サーバーで puma を restart して回る
  • スコア 9,510
  • ログ系と exporter 系全部切る
  • メモリ余ってるので雑な index どんどん貼る(koba789)
  • ベンチマーカーの cancelled 祭りが始まる
    • exporter を停止したことで isumon 上で経過が見れなくなり不安になる
    • 部屋内でボレロを流して治安悪くする
  • スコア 10,090
  • 競技終了

所感

予選は通過することができた。最後のスコアはガチャのように思えるが、再起動試験後のスコアも予選通過ラインに達していたのでそこまで運が良かったわけではないと思う。ログを切って本当に良かった。

問題の構成や点数配分、レギュレーションの匂わせ方まで、過去一で面白い ISUCON だった。運営の皆さんありがとうございます。本選も面白いの期待しています。

心残り

今回は外部 API のリクエスト並列化をやりそこねたので、それがあればスコアはまだまだ上がったと思う。 一応外部 API リクエストは rack-lineprof を使ってローカルからアクセスしたログを見た。そのときは複数回送っても気にするほど遅くなかったので気に留めてなかったんだけど、解説・講評*2見たら

2つ目の課題のとなる配送サービスAPIですが、APIのレスポンスにかかるレイテンシがベンチマーク走行中のみ遅延を入れて 0.8秒かかるようにしてあります。アクセスログの分析をしたチームは気づいたかと思いますが、取引中の商品が多くなればレスポンスがかなり遅くなります。

と書いてあった。うーん。気付かんよ。 ということでこういうのへの対策として stackprof などの使用を検討し始めている。

golang で出ていたチームは bcrypt で苦戦していたけど Ruby は気になるほどではなかった。むしろ支配的になってくるのは MySQL のロックだと思う。非 golang 勢の参加記いっぱい読みたいので待ってます。

チーム方針として git を使わないでやってきた(サーバー上に開発環境整えている)のだけれど、事前準備をしたことによって当日手が空いてしまって(同時編集できないがために)作業が詰まる現象が起きた。後半はアプリケーションコードがボトルネックになったためにもどかしい感じが残る。本選はファイル分割とかしていこういうのを避けていこうとチームで話している。

感情が無いエントリになってしまった。予選通過はちゃんと嬉しいです。本選も準備して頑張るぞ〜!


これは isumon を部屋の壁に投影していたやつ。見てると精神が安定する。