💪

ISUCON13にチームOL001として参加しました(インフラ編)

2023/12/13に公開

Introduction

この記事は、OPENLOGI Advent Calendar 2023 の13日目の記事です。

ISUCON13にチームOL001のインフラ担当として参加してきました。 最高スコアは20,572、最終スコアは19,151で、ISUCON13 受賞チームおよび全チームスコア 全694チーム中108位でした。

当記事ではインフラ担当としての下準備と当日対応したことについて自分のメモも兼ねて書いていきます。

アプリケーション担当の記事は こちら です。

https://zenn.dev/nokoy/articles/baed50e2a28bfa

チームメンバー

チケット争奪戦に勝った同僚の @nokoに誘われて初心者ながら挑みました。

ISUCONには長年出てみたかったんですが、チームメンバーが集まらなかったり出場する機会に恵まれなかったので、今回誘われた時2つ返事で了承しました。

@yshirと自分は完全初心者だったので @nokoに教えてもらいながら練習していました。

名前 役割 参加経験
@noko アプリケーション 2回目
@yshir アプリケーション 初参加
@takeokunn インフラ 初参加

目標

初参加するにあたって自分なりの目標を2つ掲げました。

どのような形であれ確実に提出し失格にならないようにする

ISUCON13 レギュレーション を満たしたうえで、当日マニュアル に記載されている「最終スコア」の手順をすべて通過したチームのみランキングに載ることができます。 どんなに途中良いスコアを出そうと最終で失格になっては何の意味もありません。

たとえアプリケーション担当が何1つ改善をしなかったとしても、確実にproduction用の設定をしたうえで提出をするというのを徹底しました。 初参加かつそもそもISUCONがどういうものなのか知るために参加していたので、正直これさえ満たせていれば及第点かなと考えていました。

アプリケーション担当が完全にアプリケーションに集中できる環境作りをする

自分のチームは初心者チームであり、インフラ側からの改善をガッツリするだけの練度が足りていませんでした。 大して練習をしてない状態で凝ったことをするのは危険であり、チームメンバーに迷惑をかける可能性があります。

アプリケーション担当の2人はどう考えていたのかは分かりませんが、確実にN+1を潰したりINDEXを貼ったり、ISUCON本 にあるような基礎的なチューニングを確実にこなしていければ良いと自分としては考えていました。 なのでアプリケーションの改善を確実に回せるような運用フローを整えるのが自分の役割だと考えていました。

事前練習

練習時間

2023/10/13(Fri)から2023/11/25(Sat)まで約1ヵ月、80時間程度練習に費しました。

本来ならほぼ毎日21〜23時の2時間の練習があったのですが、朝型の自分的には少々厳しくサボりがちになってしまいました。

  • 合同練習: 2時間 × 15回 = 30時間
    • 毎日のように練習があったけどかなりサボってしまった
  • 通し練習: 8時間 × 2回 = 16時間
  • 個人練習: 30時間程度

そもそもISUCONの知識がまったくない状態からだったので最初はかなり苦戦しましたが徐々に流れをつかんできました。

準備に対しての考え方

競技時間全8時間を以下のような時間割で対応する想定をしていました。

  • 改善を回せるように環境構築 2〜3時間
    • どんな問題だろうと Nginx, MySQL, Go の構成にするという決めをしていた
  • nginxチューニングや細かい修正など 2〜3時間
    • 簡単な修正のみに抑える
  • 提出用の環境構築 2時間
    • 確実に提出できるようにする
  • 予備 1時間

本番は何が起こるか分からない以上、準備できるものはできる限り準備してミスを減らすという方針で進めていました。 自分が点数を伸ばすというよりはアプリケーション担当に点数を稼いでもらうという方針にして、アプリケーション担当が開発しやすいフローを用意してあげるのに全力を尽くしました。

手順書

2回の通し練習でいかに手順書というものが大事なのか痛感させられたので詳細に作りました。

私はorg-modeユーザーなのでorg fileを用意していて、本番でもタスク管理も兼ねて使っていました。 実際のファイルはこちらです。

https://raw.githubusercontent.com/OL001-isucon/isucon13/main/isucon13.org

あらかじめssh keyを用意してGitHubに登録しておいたり、ansibleを流すタイミングやmigrationフローを作成するタイミングなどを明確に書くことによって、本番で焦らずに構築することが可能になりました。

DB Migration運用

アプリケーション担当が2人いるので作業コンフリクトを起こさない為にもDB Migrationを作る必要がありました。 Laravelのように動的にMigrationを定義するものではなく、Ridgepoleのように宣言的にスキーマを定義できるしくみのほうがチーム内でしっくりくるという結論になったので、sqldefを選定しました。

sqldefはMySQL用のmysqldefだけでなく、SQLiteやpostgresqlにも対応しており、何がミドルウェアとして提供されているか分からないISUCONのようなケースにぴったりでした。 mysqldefで schema.sql を出力してGitHubで管理しつつ、ansibleでMigrationを流せるようにすることによって、一切トラブルなくMigrationを走らせることができました。

mysqldefはtrigger関数をサポートしていないようだったので、 trigger_up.sqltrigger_down.sql を用意して生SQLで管理するという運用にしました。 このあたりはもう少し良い方法を模索したいものです。

tbls運用

ER図を簡易的に確認したいという要望があった為tblsを導入しました。 main branchにschema.sqlの変更が加わったら dbdoc branch にtblsが出力したdbdocを出力するGitHub Actionsを作成しました。(workflowはこちら)

事前にActionsを作っておけば当日何も対応する必要がないので、対応しといて損はなかったんじゃないでしょうか。

Ansible運用

以下の3つのplaybookを作りベンチマークを安定的に回せるようにしていました。

https://github.com/OL001-isucon/isucon13/blob/main/ansible/README.md

基本は手元から流す運用にしていましたが、GitHub Actionsから流せるようにすることによっておま環問題が発生することを防ぐことができました。

https://github.com/OL001-isucon/isucon13/actions

install_tools playbook

このplaybookをたたくと以下のツールと設定ファイルが全サーバに入るようにしています。 たたくのは最初の1回だけですが、何度叩いても問題ないので間違って消した時でも安心設計にしています。

before_bench playbook

ベンチマークを回す上で必要な前処理をまとめたplaybookを準備しました。

  • Run git pull
  • Run go build && restart go server
  • Truncate nginx access/error log and mysql-slow.log
  • Copy nginx.config && Restart nginx
  • Copy my.cnf && Restart mysqld
  • Run migrate by mysqldef
  • Reset sysctl/systemd

以下のようにdev/prod環境とbranchを指定して特定のインスタンスに流せるようにしていました。

$ ansible-playbook -i ./ansible/hosts.yml -l isucon-1 ./ansible/playbook/before_bench.yml --extra-vars "env=dev" --extra-vars "branch=main" --verbose

全インスタンスに流せるコマンドも用意しておきました。

ansible-playbook -i ./ansible/hosts.yml ./ansible/playbook/before_bench.yml --extra-vars "env=prod" --extra-vars "branch=main" --verbose

after_bench playbook

ベンチマークを回した後のalpとpt-query-digestで出力したslow queryの結果をGitHubのissueにタイムスタンプとともに貼りつけるshell scriptを用意しました。 これにより、いつころにどういうスコアだったのか、どこが重いのかというのをGitHubで管理でき、コミュニケーションが円滑になりました。

$ ansible-playbook -i ./ansible/hosts.yml -l isucon-1 ./ansible/playbook/after_bench.yml --verbose
  • Copy alp config
  • Aggregate result && Report to GitHub issue
# for gh command
REPO="OL001-isucon/isucon13"
TITLE=$(date -u -d '+9 hours' +"%Y/%m/%d(%a)%H:%M:%S")
ISSUE_URL=$(gh issue create --repo $REPO --title $TITLE --body "")

# for alp command
echo "alp:" > /tmp/alp
echo "\`\`\`" >> /tmp/alp
sudo cat /var/log/nginx/access.log | alp json --config /etc/alp/config.yml >> /tmp/alp
echo "\`\`\`" >> /tmp/alp
gh issue comment $ISSUE_URL --body-file /tmp/alp

# for pt-query-digest command
echo "pt-query-digest:" > /tmp/pt-query-digest
echo "\`\`\`" >> /tmp/pt-query-digest
sudo pt-query-digest /var/log/mysql/mysql-slow.log | head -n 300 >> /tmp/pt-query-digest
echo "\`\`\`" >> /tmp/pt-query-digest
gh issue comment $ISSUE_URL --body-file /tmp/pt-query-digest

実際のissueはこちら

監視体制

Netdataを使っているチームが多い中、今回練習が足りなくてちゃんと運用できないだろうという判断をした為、htopを雑に使うという運用にしました。 MySQLが支配的なのかAppが支配的なのかさえ最低限分かれば良いという結論になったので、高機能なツールを頑張って運用せずに、シンプルにhtopで確認するので良いだろうという結論になりました。

来年までに最高の監視体制を整えたいです。

タイムテーブル

前日

初心者がベテランに唯一勝ていることは「体調の良さ」です。 体調だけは万全にして挑もうということで銭湯に行きゆっくり温泉に漬かり、明日の作戦会議を軽くして日付変わる前に寝ました。

07:30〜 起床

集合時間まで2時間くらいあったので、軽く身体を動かして頭が働くように調整していました。

09:30〜 チーム集合

無事全員寝坊せずに集まることができて、今日どんな問題が出るかなーといった雑談をしながらYouTubeの配信を見ていました。

手順書を再度読み頭の中でシミュレーションを繰り返し行いました。

10:00〜 開始

開始の合図直後に @nokoがCloudFormationを流してip addressをslackで共有してもらいました。 速攻で全台にsshできることを確認し、 ~/.ssh/config を共有しました。

Host isucon-1
  HostName <ip address>
  User isucon

Host isucon-2
  HostName <ip address>
  User isucon

Host isucon-3
  HostName <ip address>
  User isucon

10:10〜 リポジトリ初期化

  • .gitconfig を用意する
  • isucon-1 内で git init して必要なソースコードをGitHubに上げる
  • 手元に git clone をする
  • .editorconfig を用意する
  • 事前に用意した ansible/.github/ をcopyする
  • project名を一括置換する

一括置換は以下のようなscriptで雑に置換しました。

$ find ansible/ -type f | xargs sed -i "" -e "s/isucondition/isupipe/g"

10:15〜 Ansible初期作業

GitHub Actions経由でAnsibleを流せるように調整しました。

10:20〜 インフラ確認

  • 動作しているプロセスを確認しておおよその構成を理解する
  • ハードウェア構成を調べる

neofetch を流した結果をREADMEにメモしたりしました。

10:22〜 DB初期作業

あらかじめMigrationのしくみを用意していたので、その為の準備をしました。 ついでに tbls でDBドキュメントが正常に生成されていることを確認した。

https://github.com/OL001-isucon/isucon13/tree/dbdoc

  • 接続情報をREADME.mdに書く
  • レコード数をREADME.mdに書く
  • sqldefで schema.sql を生成してGitHubに上げる
  • before_bench の sqldef の接続情報を修正する
  • trigger.sqlを空で作成する

10:28〜 nginx初期作業

nginxのdev/prod用の設定を両方用意してansibleで流し分けられるようにしていました。

  • nginx.confsites-enabled/* =の初期値を =git commit する
  • nginx.dev.conf のlog_formatを修正する
  • sites-enabled/*.dev.confsites-enabled/*.prod.conf にcopyする
  • before_bench の nginxの設定を修正する

alpはcopilotで生成できるので @nokoにあらかじめ対応してもらいました。

10:31〜 Go初期作業

後述しますが、今回の場合envをcommitしてはいけなかったです。

  • envをenv.devとenv.prodを用意する
  • webapp/go/Makefile を作成する
  • before_benchのgoの実行パス修正

10:33〜 before_bench/after_bench実行環境準備

複数台構成用に env.devenv.prod を用意していたのですが、EC2起動時にenvを書き換える処理があったようなのでベンチマークが回らないという問題が起きてしまいました。

再起動すると治るということは序盤に分かったのでアプリケーション担当にはベンチ前にrebootしてくれというお願いをして応急対応し、ちゃんと調査をした結果、envをそもそもcommitしないという方針にしました。

これのせいで2時間程度潰れてしまいました。

  • isucon-2/isucon-3に入って git pull できる状態にする
  • GitHub Actions上でisucon-3に .github/workflows/before_bench_specific.yml を実行
  • isucon-3でベンチマークを回す
  • GitHub Actions上で .github/workflows/after_bench.yml を実行

12:20〜 ちょっとした変更をする

  • go-jsonライブラリの差し替え
  • golangのconnection option設定

12:26〜 Local DB環境構築

  • docker-compose.yml を用意して docker compose up する
  • 本番DBからmysqldumpしてscpで持ってくる
  • docker dbに流し込む
  • 手順書を README.md にまとめる

12:30〜 改善作業

  • 静的コンテンツをnginxから返す
  • 細かいパラメータチューニング
  • アプリケーション担当のヘルプ

ヘルプ作業は「ansibleが謎に動かない」や「migrationが謎に落ちる」など細かい詰まった時、アプリケーションの改善をやめない為に代わりに調査をしたりしました。

13:30〜14:03 MySQLサーバを別インスタンスからアクセスできるように権限付与

ベンチの結果次第では複数台構成で提出するべく準備しました。

  • isucon-3に権限付与
  • isucon-1/isucon-2から疏通ができるか確認

14:03〜14:23 昼食

ベンチマーカーが障害を起こしていてどうしようもなかったので昼食にしました。

14:23〜 改善作業

  • nginxにgzip追加
  • 細かいパラメータチューニング
  • アプリケーション担当ヘルプ

この時間帯はベンチマーカーの障害で中々改善が進まなかったです。

15:34〜 production用の設定で正常に動くか確認

  • nginx.prod.confの準備
  • main.goのLog Middlewareを削除する
  • my.prod.cnfの準備
  • 再起動した時の手順書を作成

以下のように2台構成を想定した手順書を作成しました。

1. rebootする

$ ssh isucon-1 "sudo reboot"
$ ssh isucon-3 "sudo reboot"

2. ansible before_bench prodを流す

$ ansible-playbook -i ./ansible/hosts.yml -l isucon-1 ./ansible/playbook/before_bench.yml --extra-vars "env=prod" --extra-vars "branch=main" --verbose
$ ansible-playbook -i ./ansible/hosts.yml -l isucon-3 ./ansible/playbook/before_bench.yml --extra-vars "env=prod" --extra-vars "branch=main" --verbose

3. isucon-1に入ってenv.shを以下のように変更する

diff --git a/env.sh.prod b/env.sh.prod
index 48772c1..1ee25eb 100755
--- a/env.sh
+++ b/env.sh
@@ -1,5 +1,5 @@
 ISUCON13_MYSQL_DIALCONFIG_NET="tcp"
-ISUCON13_MYSQL_DIALCONFIG_ADDRESS="127.0.0.1"
+ISUCON13_MYSQL_DIALCONFIG_ADDRESS="192.168.0.13"
 ISUCON13_MYSQL_DIALCONFIG_PORT="3306"
 ISUCON13_MYSQL_DIALCONFIG_USER="isucon"
 ISUCON13_MYSQL_DIALCONFIG_DATABASE="isupipe"

4. isucon-1に対してbenchを回す

16:58〜17:30 設定に見落しがないか全体的に確認

ベンチマーカーをアプリケーション担当が占有してほしかったので祈りの時間。

17:30〜18:00 1台構成と2台構成の両方をベンチ回してスコアの高い方を提出

結局1台構成で提出しました。

振り返り

良かったこと

事前に立てた目標をクリアできた

事前に掲げていた2つの目標をちゃんとクリアできて、無事ISUCONを終了できて良かったです。

  • どのような形であれ確実に提出し失格にならないようにする
  • アプリケーション担当が完全にアプリケーションに集中できる環境作りをする

アプリケーション担当の2人との役割分担は相当ちゃんとできていて、トラブルシューティングは自分がやるという取り決めがうまく回ってよかったです。 普段一緒に仕事しているのもあって、コミュニケーション面は最高にうまくいきました。

事前に練習したことを100%生かせた

事前に準備していたansibleや開発フローを滞りなく運用できました。 また、手順書をちゃんと作れていたので初期作業を滞りなく行うことができました。

途中env回りで詰まってしまったが、チーム全員で連携して筋道立て解決できたのは本当に良かったです。

反省点

純粋に練習時間が足りなかった

ISUCONがこんなに楽しいものなのかと心から理解したのが11月中旬だったので、もっと早く楽しいイベントだと気がつきたかったです。 初心者が一番初めにやるべきなのは過去問の通し練習がお勧めです。

お題がどういうものなのか何を改善すべきなのか何も分からずに終わってしまった

上述のタイムテーブルを見て分かる通り、自分は全然アプリケーション面に触れておらず、いったい何がどうなっているのか一切分からないまま時間が過ぎてしまいました。 本来ならPowerDNSをどうにかしたり、複数台構成の検証をもっとやるべきだったはずなのですが、実力不足で何もできなかったです。

ツールの整備や監視体制をもっと整えたい

before_bench/anfter_bench playbook運用は非常に良いしくみだったが、ansibleがそもそも重すぎて改善が回しにくかったり、もっといろいろなメトリクスを見られるようにしたい等の課題が見えてきました。

ISUCON13にツールの力で勝ちたかった(mazrean) と同等の環境をアプリケーション担当に提供してあげたかったという反省があるので、来年は最高の体験を提供できるように整えていきたいです。

終わりに

本当に楽しかったので来年もまたこのメンバーで出たい。青春でした。

GitHubで編集を提案

Discussion