大会運営者の皆様ありがとうございました。 本戦もよろしくお願いいたします。
まとめ
決勝に行くことはできたけど、打倒組長は達成できず...
- 勝因:
/icons/*
と/fetch
をなんとかできたこと - 敗因: nginxを2台にしていたこととswap対策できなかったこと
参加経緯
ISUCONの存在は同僚の組長が無双していたこともあり最初から当然知っていて、 自分も出たら結構いいところまでいけそうだなあみたいな気持ちはあったのだが、 惨敗したときの恐怖が勝って、あれこれ言い訳をして出てこなかった。
今年も、本番当日がつくばマラソンとかぶっているので「参加できないわー残念!!」と言いながら心のなかでホッとしていたけど、 うっかりつくばマラソンのエントリーをし忘れてしまい、本番当日が空いてしまったということで言い訳ができなくなってしまった。。。
そんななかここ2年くらい業務で一緒にやっているGoの実装がクソ早い @mizukei が新卒の@jet_zousanと残り1名のメンバーを探しているという情報をつかんだので、それに食いついたというのが参加した経緯。
結果的にかなりいいメンバー構成でチームが組めてラッキーだった。
チーム編成
- @jet_zousan 弊社インフラの期待の新人 あの組長の弟子
- インフラを一任
- 最初は環境構築を担当する
- @mizkei11 Goを愛しGoに愛された男
- アプリ担当(主にGoの言語依存の問題を潰す)
- 最初はコードリーディングを担当する
- @ken39arg 10年戦士
- アプリ担当(主にDBの使い方の問題を潰す)
- 最初はslowlogの解析とIndexチューニングを担当する
とにかく自分と @mizkei11 はコードを書くのが早いというのが強み
インフラ面について@jet_zousanの知識は申し分ないけど業務経験が少ないのが不安要素 (経験的に俺がインフラ補えれば良いのですが最近さっぱりで…)
そういうチームなんで、とにかく当たり前の事を当たり前に積み上げて、手数でぶん殴ろうというのが唯一の作戦。
事前準備
- チーム組んだ瞬間に Slackとgithubのprivate repoを作った
- 1週間前に集まって、決勝に絶対に行くこと、打倒組長を誓いあった
- issueにsnipetsとTODOを各自書き込んでおいた
- infra担当の @jet_zousan がなんかchefで準備してた(よくわからん)
- 自分たちのいつものフィールドで戦うことを決めておいた(nginx,golang,mysql,redis)
- apache→nginxやpostgresql→mysqlの移行ブログなどのリンクをブクマ
当日競技前
開始が遅れたので、作戦とマニュアルの確認をクドいほど行った。
- ホワイトボードに「おちつく」とでかく書いた
- ホワイトボードに「チャレンジしない」とでかく書いた
- 事前に決めたチェックポイントでタイマーのアラートを設定した。内容は記憶が曖昧だがこんな感じ
- 30分 達成指標
- いじるファイルのgit管理
- alp用nginx-logの設定
- slowlogの設定
- アプリをgoに切り替える
- 最初のベンチが終了
- pt-query-digestとalpでボトルネックを立て作戦を立て直す
- 2時間 達成指標
- 自分たちの当たり前を終わらせる
- Index
- N+1を解消
- 言語依存の基本的な改善
- 自分たちの当たり前を終わらせる
- 7時間 達成指標
- 基本これより先はいじらない
- 再起動テストなどを行う
- 30分 達成指標
競技開始
13:13 ~ 14:15頃
- まず全員で当日マニュアルを読み込んだ。
- とりあえず1人1台サーバーに入り構成を確認
- nginxの設定を手で書き換えてalpで集計できるようにlogformatを変更
- mysqlはクライアントでログインし
set global
でslowlogを出力するように変更 - systemdでアプリをgoに切り替える
- 1回目ベンチ
- githubにgoのコードをコミット
- alp, pt-query-digestを実行
- pprofを仕込む
- buildを1台でできるようにする
ここまでで当初の想定より30分ほど送れていたけど、「おちつこう」と声を掛け合って冷静になれていた
ここで、/icons/をどうにかしないとslowlogすらまともに見れないという事になり、一度立ち上がって作戦会議
(ちなみにこの後最後までpt-query-digest は見れなかった...)
たてた作戦
下記をそれぞれサクッとやってしまおうということになる
- @mizkei11
- 画像を静的ファイルに保存する
- 初期画像を取り出す
- @jet_zousan
- DBサーバーでアプリを動かし各appサーバーから
POST /profile
とGET /icons
をDBサーバーに回し画像サーバーを1台にする
- DBサーバーでアプリを動かし各appサーバーから
- @ken39arg
- slowlogがまともに見れないのでコードリーディングをして自明なIndexを張る
14:15頃〜19:30
僕に割り振ったMySQLにIndexは、必要なのが少なくてすぐに貼り終えてしまった(messageとimageのみ)
暇になったのでやることリストを確認しながら静的ファイルをgzにしgzip_staicで配信するなどをやった。
そうこうするうちに、15:30くらいに画像の静的ファイル化が完了してスコアが上がりだしたと思う。
ぼくは簡単に出来そうな最適化が見当たらないので/fetch
の修正に取り掛かることにした。
この時たしか16時前で17時までにキメるぜ!と宣言して16:30くらいに書き終えた。
ただそこから、くだらないタイポとかでなんやかんやと時間が過ぎて結局投入したのが17時過ぎになってしまった。(今回僕に限らず3人とも普段しないようなくだらないミスが目立った。パスを間違えたり、タイポしたり、、、)
ここでスコアが一段階跳ねたところで、インフラ担当の@jet_zousanが、cacheヘッダーを入れたら、スコアが爆上げして、超盛り上がった。
この結果、DBサーバーが完全なるボトルネックになって、isu1と2でtry_file&proxyでプロキシしあう構成(通称クロス)に移行するが、またしてもくだらない設定ミスとかでなかなかすんなりいかない時間を過ごしてしまい実投入は少し先になった。
nginxの設定に手間取っている間に jsonyfy
みたいな名前でに隠された /messages
と /history
のループクエリの解消が@mizkei11 により投入されて確かトップにたったと思う。
そしてクロス構成が決まったところで46マンまで伸びて2位以下とダブルスコアがついてきたので、予選突破の安全策を取りに行くことにした。
そう、僕たちの作戦は「チャレンジしない」
19:30
ここまで手をつけていなかったmysqlのチューニングに取り掛かろうととりあえずinnodb_buffer-pool-size
の調整をしたところ、スコアが半分くらいになってしまった。
さらにベンチを回すとさらに半分くらいに。。。
ここで設定を戻したりしたが、一向によくならない。
何をしたのか振り返るが、よりにもよってmysqlの設定に関してのみコミットしておらずdiffが不明。
絶対にinnodb-buffer-pool-size
しかいじってないとのことだが@jet_zousanを信じきれずDBサーバーばかりをガン見するという状態がしばらく続いたところで、ようやくアプリがswapしていることに気づいた。
画像ファイルの名前を画像ののsha1で生成していたのだが、readallで画像を一度に読み込んでいたのだ。
よく考えてみると、今までベンチを回す時は必ずコード修正後でアプリをビルドし直していたからメモリが解放されていたのだが、DBのチューニングの時はDBしか再起動しなかったため、メモリを食いつぶしたままのアプリでベンチがかかっていたため性能が著しく劣化してしまったと予想。
ようやくそこに気づいたので、アプリを再起動すれば少なくとも30万はスコアが出るということが確認できたので、再起動後はちゃんとスコアが出るだろうと判断し、対策しないことにした。
20時の時点で30万まで行っていたのはチームは他にいなかったので3位以内には入れるという判断だった。
そういうわけで、余計なことはやめておこうと、ログもそのまま、欲を出さずに20:30には競技をやめて反省モードに入った。
結果として、この判断は正しく、ベストスコアとの隔たりは大きかったものの1日目1位をキープしたまま決勝の切符を手に入れることができた。
めでたしめでたし。
感想
判断は正しかった。ただ、本当にいつも通りできていたらあと2手くらいは打てたし、冷静になっていたら最後チャレンジではなく当たり前の判断でswapの解消ができたと思う。
いや、そもそもモニタリングしていたらもっとはるかに早くswapに気づけたはず、、、
決勝は同じミスをしないように、そして決勝はチャレンジします。勝つだけなので