個人的なメモ帳

個人的なメモを書いていきます。

ISUCON13で41位(最終スコア39,243)でした

結果

メンチカツというチームで1人参加して41位でした。
言語はGoで最終スコアは39,243点でした。
ISUCON13 受賞チームおよび全チームスコア : ISUCON公式Blog

感想

  • 予選通過のラインにすら入ってないので悔しい
  • 1人チームで上位目指すには明らかに手を動かすスピードが足りないし、守備範囲が狭すぎる
    • PowerDNS周りさっぱりわからなくてDBをサーバにする+TTL設定してみるくらいしかできなかった
    • 全体的にミスが多く、調査して解消するまで無駄な時間がかかっていた
  • 次々に改善したい場所が見つけられる+改善したら素直にスコアが上がってくれる解いてて楽しい問題だった
  • ベンチマークがWaitingとAbort多すぎて競技体験がちょっと良くなかった
    • ベンチマークが混雑するのを込みで競技準備しても良いんだけれど、それに対応するのは本質じゃない気がするのであまり気が進まない

やったこと

以下にやったこと並べます

いつもの

  • githubへのコードや設定ファイルのバックアップ
  • 計測用ツール(pprof、pt-query-digest、alp)の導入
    • ついでにpprofで見やすいようにmain.goに統合
  • サーバスペックの確認

改善内容

スコアをメモってなかったのでそれぞれどれくらい効いたか覚えてません。

1. slow_queryが出ているところにindexを貼っていく
  • 後で改善して消すかもしれないけど、徐々に改善してとりあえず早くしていく
2. icon_hashで参照時に都度SHA-256でhash計算してるところを無くす
  • DBにicon_hashというカラムを追加
  • 参照時ではなく登録時にicon_hashも計算しておくように変更
  • このときdefault_imageのhash化忘れていてFailする(1敗)
  • ついでにbuild用のmakefileの記載をミスっててビルドできてなかった(2敗)
3. icon取得の所にvarnish挟む
  • 変更が2秒以内に反映と書いてあったので何も考えずキャッシュ挟んで良いかなと挟む
  • 後でiconをfile化して直接返すように変更したので使わなくなる(3敗)
4. N+1を気づいたタイミングでなくしていく
  • slowquery見て上位に来たタイミングでN+1になってるものを順次対応
5. AdminPrepareを無くす
  • slowqueryの上位にAdminPrepareが出ていたのでInterpolateParams=trueにして無くす
6. PowerDNSで利用してるMySQLのCPU使用率が高いので別サーバに切り出す
  • 本当はPowerDNS+MySQLごと別サーバに切り出したかったけど調べる時間惜しかったのでとりあえずMySQLだけ移動
  • この時点の構成
    • Server1:App+Varnish+Nginx+MySQL(isupipe)
    • Server2:MySQL(isudns)
  • ついでにPowerDNSのTTL設定を追加
7. NGワード登録処理の無駄なNGワード判定なくす
  • コメント投稿時にNGワード判定でブロックされているのに、新規でNGワード追加したときに登録済みのNGワードでもチェックしていたので新規登録のNGワードのみでチェックするように変更
  • ついでにMySQLの負荷下げたかったのでApp側でNGワード含むかチェックするように変更(コメント投稿部分も)
8. UserのStatistics取得の負荷下げるためにreactionsとtipsを事前計算する
  • userテーブルにtipsとreactionsカラムを追加して、tipsとreactionsの登録時に更新するように変更
  • ここでSQLでtipsと書くところをtipと書いて取得できなくなる(4敗)
  • ついでにstructで小文字始まりにしててデータ取得できなかったりをする(5敗)
  • 初期データにreactionsがあることに気づかずに初期データ変更し忘れる(6敗)
  • 変更した初期データ(init_users.sql)にthemesテーブルの情報も入っていることに気づかずにthemesテーブルの初期化無くしてしまう(8敗)
9. 画像をファイルにしてNginxから返すように変更
  • ここでもdefault_imageのことを忘れていてFailする(9敗)
  • 304で返せるようにetag設定するも改善しなかったので多分うまく行ってなかった(10敗)
10. isupipeのMySQLのCPU使用率が高いので別サーバに切り出す
  • これ入れたタイミングで40,000点近くになる
  • この時点の構成
    • Server1:App+Nginx
    • Server2:MySQL(isudns)
    • Server3:MySQL(isupipe)
11. MySQLやNginxの細かい設定を追加
  • Nginxのkeepalive設定追加したり、MySQLのbuffer設定追加したり

ISUCON10本選をBestスコア37,501点でFailしました

結果

メンチカツという1人チームで参加しました。
ベストスコアは37,501点でしたが、最終チェックの3回目でFailしました。

最終的な構成

今回の本選はサーバ3台でそれぞれスペックが異なる構成でした。

  1. 2コア、1G
  2. 2コア、2G
  3. 4コア、1G

最終的にはこんな感じの構成にしました。

  1. DBサーバ(notificationテーブルのみ)
  2. 未使用
  3. APP、envoy、DBサーバ(notificationテーブル以外)

競技中に2台目のサーバにApp移したりしましたが、ベンチでFailすることが増えたため泣く泣く2台構成で終えました。
感想戦でサーバ入ってチェックして気づいたのですが、この原因と最終チェックでFailした原因がおそらく同じもので、envoyのLimitNOFILEを設定するの忘れてて、そのまま負荷上がるとenvoyがお亡くなりになってしまっていたのが原因でした。

やったこと(順不同)

SetMaxConnsの増加

初期値が10でしょっぱかったので脳死で増加させました

メイン機を3番目のサーバに変更

メモリあまり使っていなかったので一番コア数が多い3番をメインに

インデックスの設定

slow queryを見て効きそうなIndexを設定し、初期にあったindexで微妙そうなindexを削除
ここらへんはmatsuuさんが予選後にTweetしていたSQLパフォーマンス詳解を本選前日に流し読みしたおかげで結構迷わずに設定できました。

go-cacheの導入

audience向けのダッシュボードは1秒猶予があったのでgo-cache使ってキャッシュ
この対応でMySQLへの負荷が減りスコアがだいぶ伸びました。 本来はvarnish入れたかったけど、1から導入したことがなかったため本選中はチキってやめました。

DBの分割

DB負荷がずっと高かったので簡単にできそうなnotificationテーブルだけサーバ1に分割して移動
notificationDBサーバのCPU使用率が結果的にそんな高くなかったので、この分け方はリソースの使い方として微妙だったかなーと思いつつ、go-cacheと合わせて3万点超えたのでそのままに
本選中にこの謎分割やってた人他にも居たんだろうか……

チーム数の調整

100付近をうろちょろさせてました。
最終的に100が安定してスコア出せてたので100に。

JOINしまくってるクエリの修正(感想戦

本選中は余裕なかったので感想戦で。
teamsにbest, latestをフリーズ前と実際のものを突っ込むことで対応しました。
アクセス数が少なめのエントリポイントだったせいかあまりスコアは上がらず。

N+1クエリの修正(感想戦

あまり重たい場所でもなかったのでスコア影響なし

Webpushの追加(感想戦

本選中はここだけマニュアルの分量多すぎてネタ感あったのと、1人じゃ対応しきれなさそうだったのでスルー
感想戦で実装したら選手からのアクセスが増えたためかMySQLの負荷が高くなりスコアが下がったので、本選中はやらなくて正解だったかなと思いました。

Varnish導入(感想戦

使ったことはあったけど実際に自分で1から導入したことがなかったので、こちらも本選中はできませんでした。
実際にサクッとインストールから設定まで出来た上に、いれたらスコアめちゃくちゃ上がったので、ここらへん入れられたら良かったなーと後悔。

本選中ハマったこと

サーバ3でMySQLを立ち上げたままgoのbuild走らせたらめちゃくちゃ時間がかかるというのを数十回繰り返したあとに、リソース不足ということに気づきました。
気づいてからはMySQLをStopさせてからgoのbuild走らせるようにしたら一瞬でbuildできるようになったので、ここらへんは時間的にだいぶ無駄にした感じがあります。
途中で30分くらいサーバ応答しないこともあって、このまま復活せずに終了したらどうしようと思って大分焦りました。

感想

本選参加前は1人だけベストスコア低いまま終わったらどうしようかと思っていたので、フリーズ時点で1位表示になれたのはとても嬉しかったです。

f:id:buchy12:20201013213258j:plain
フリーズ時点

本選中は結構スコアの伸びが良くてある程度出来た感じがあったものの、感想戦で行ったVarnish導入やJOINしまくってるクエリの修正など本選中はチキってできなかったこと結構あったなー思い、これが本選のプレッシャーかと実感しました。
一方で、感想戦で追加で行った対応の中にはスコアが伸びないものが多かったので、本選中は結構クリティカルなところを優先度高く対応出来てスコアを効率よく伸ばせていたっぽかったのでそこら辺は良かったなーと思いました。

個人的には同じ1人チームで本選出場していたtakonomuraさんに、最終スコアで1位とFailという天と地の差を見せつけられたのが悔しいので来年もチャレンジしたいです。

ISUCON10予選を19位で通過しました

結果

メンチカツという1人チームで参加し、19位で予選通過しました。
言語はgoで最終スコアは2338点でした。

やったこと

初期準備

  • githubへのコードと設定ファイルのPush
  • mysqlのバックアップ
  • pprofの導入
  • pt-query-digestの導入
  • alpの導入
  • appやmysql, nginxをまとめて再起動するスクリプトやprofileコマンド用のスクリプトの用意
  • サーバのスペックの確認

改善内容

1. nginxでbotに対して503返す
  • これはマニュアルに書いてあったのでとりあえず対応。特にスコアに効いてなさそう?
2. SQLの修正
  • DBのCPU使用率高かったのでSQLから着手
  • ST_Containsで領域内かどうかの判定をすればよいだけのところでestateを取得しにいってたのでestate取得部分を削除
  • countで十分なところをcountに修正
3. indexの追加
  • SQL修正してもSlowquery出まくっていたので対応
  • Slowqueryに対して雑に追加していて、orderのASC/DESC全く見てなかったため効いてなかった気が
4. DBを専用サーバに移行
  • pprofやslowquery、topの結果を見ると明らかにDB負荷が高かったのでDB専用サーバを作成しそちらに移行
5. nginxとmysqlに雑な設定追加
  • 毎回isuconで使ってる設定があったので雑に追加
  • 駄目だったら細かく見て直せばいいやという考え
6. DB専用サーバを2台に変更
  • DB専用サーバ作っても相変わらずCPU使用率が高い
  • appのコードを読んだ感じ、chairとestateは特にJOINする必要もなさそうだったので、それぞれ別DBとして別のサーバに構築
7. gis専用のDBを追加
  • この時点でapp兼nginxサーバ、Chair用DBサーバ、Estate用DBサーバという構成
  • topを見てもDBのCPU使用率が相変わらず高く、それに比べてnginxとappのCPUに余裕がある
  • 上記の様な状態だったので少しでもDB負荷を軽くするためにST_Containsの計算をさせるだけのDBをapp兼nginxサーバに追加
    • 下手な実装よりDBで提供されてる方が性能良いかなという意味で行ったが、ここらへんはgoにあるライブラリ使ったほう性能良かったかも
  • これの完了が18時頃でここでスコアが2000点へ到達

8. ログ周り設定の削除
  • 良い解決方法が全然浮かばなかったので諦めてお掃除ターン
    • mysqlのslowlog設定の削除
    • pprof設定の削除
    • nginxのログ設定削除
    • go実装に入っていたmiddleware.Loggerの削除
9. 虚無
  • 毎年ギリギリでのベンチは失敗する事が多いので、20時に再起動試験やった後は他に対応できるか調査しつつ20:30頃にはベンチ回してフィニッシュ
  • 結果的に18時から3時間は虚無だったので、ここでDB内のデータを見ていじれてればよかった……

反省点

感想部屋見てここらへんやっておけばよかったなと。
視野が狭くなりすぎていたので、気づけていない点はまだまだありそう。

  • DB内のデータを実際に確認して何かしらの傾向がないかの調査
  • index効くようpopularityを負の値に
  • popularityとidをまとめてsort用のカラムに
  • select for updateの削除
    • where stock > 0 でupdateすればよいだけだった……
  • アプリの実装意図考える
    • ちょっとした動作確認とコード見るだけで何をしたいコードなのか細かく見れてなかった

その他

チームについて

毎年3人チームで参加していたのですが、今年は1人チームで参加しました。
自分が1人チーム、3人チームで感じた良さはそれぞれこのような感じでした。

1人チームの方が良かったこと
  • 1人で対応したもの全部把握できているので、複数人作業によって発生するバグなどの原因調査に時間を使わなくて良い
  • 1人1台で検証して後半で3台構成にして……という形ではなく、最初から3台丸々使えることによって本質的なボトルネックの調査が可能
    • 毎年、結果的に複数台構成に出来ずに敗退していたので特に……
  • コミュニケーションコストがかからないので試したいと思ったことをすぐに試せる
3人チームの方が良かったこと
  • 3人分の視野があるのと、ラバーダッキング的な意味で視野が広くなる
  • 始まる前のやり取りや、終わった後に感想や反省点言ったりする楽しさ

作業環境について

今年は1人だったので自宅から参加しました。

  • 使い慣れたキーボード
    • ErgoDoxはいいぞ
  • 使い慣れたデスクトップPC
  • ディスプレイ2枚
  • 座り慣れた椅子
  • ひと目を気にしなくて良い

という環境のおかげでストレスなく参加することができました。
ひきこもりばんざい。

スコア履歴

f:id:buchy12:20200913075011j:plainf:id:buchy12:20200913075006j:plain

感想

開始が遅れたり、ポータルが落ちたりと運営の方たちは大変だったと思いますが、個人的にはそこも含めてうまく進まないのがリアルな感じがあってとても楽しめました。

運営の皆様ありがとうございました。本戦でもよろしくおねがいします。

Visual Studio Codeの個人的な設定

概要

Visual Studio Code使っていると、ちょくちょく設定変えたくなったところがあったのでメモ

MarkdownのTabがデフォルトではスペース4個なので、スペース2個に

  1. Ctrl-Shift-P押して「Open Setting」と入力したら出てくる「Preferences: Open Settings (JSON)」を選択
  2. settings.jsonが開くので、以下を追記
"[markdown]": {
    "editor.tabSize": 2,
},

参考:追記前から追記後はこんな感じ

変更前

{
    "window.zoomLevel": 3,
    "git.autofetch": true,
    "explorer.confirmDragAndDrop": false
}


変更後

{
    "window.zoomLevel": 3,
    "[markdown]": {
        "editor.tabSize": 2,
    },
    "git.autofetch": true,
    "explorer.confirmDragAndDrop": false
}

キーボードショートカットの挙動を変更(vimプラグイン入れている場合)

 Windowsの時にvimプラグイン入れていると、ショートカットがvimプラグイン側に取られるので、一部変更する。(MacだとCmdとCtrlが別れているのであまり気にならない。)
 vimゆるふわ勢なので、もう少しvimのショートカット活用するようになったら変えるかも

  1. Ctrl-Shift-P押して「Open Keyboard」と入力したら出てくる「Preferences: Open Keyboard Shortcuts (JSON)」を選択
  2. keybindings.jsonが開くので、以下を追記
{ "key": "ctrl+f",  "command": "actions.find",
                        "when": "editorTextFocus && !inDebugRepl" },
{ "key": "ctrl+a",  "command": "editor.action.selectAll",
                        "when": "editorTextFocus && !inDebugRepl" },
{ "key": "ctrl+c",   "command": "editor.action.clipboardCopyAction",
                        "when": "editorTextFocus && !inDebugRepl" },
{ "key": "ctrl+n",  "command": "workbench.action.files.newUntitledFile",
                        "when": "editorTextFocus && !inDebugRepl" },
{ "key": "ctrl+v",  "command": "editor.action.clipboardPasteAction",
                        "when": "editorTextFocus && !inDebugRepl && vim.mode == 'Insert'" },
{ "key": "ctrl+w",  "command": "workbench.action.closeActiveEditor",
                        "when": "editorTextFocus && !inDebugRepl" },
{ "key": "ctrl+x",  "command": "editor.action.clipboardCutAction",
                        "when": "editorTextFocus && !inDebugRepl" }

設定項目

ショートカット 挙動
ctrl+f 検索
ctrl+a 全選択
ctrl+c コピー
ctrl+n 新規ファイル
ctrl+v vimがInsertモードのときのみ貼り付け
(NormalモードではVisualモードとして使用したいので)
ctrl+w タブを閉じる
ctrl+x 切り取り

参考:追記前から追記後はこんな感じ

変更前

// Place your key bindings in this file to override the defaults
[
]


変更後

// Place your key bindings in this file to override the defaults
[
    { "key": "ctrl+f",  "command": "actions.find",
                           "when": "editorTextFocus && !inDebugRepl" },
    { "key": "ctrl+a",  "command": "editor.action.selectAll",
                           "when": "editorTextFocus && !inDebugRepl" },
    { "key": "ctrl+c",   "command": "editor.action.clipboardCopyAction",
                           "when": "editorTextFocus && !inDebugRepl" },
    { "key": "ctrl+n",  "command": "workbench.action.files.newUntitledFile",
                           "when": "editorTextFocus && !inDebugRepl" },
    { "key": "ctrl+v",  "command": "editor.action.clipboardPasteAction",
                           "when": "editorTextFocus && !inDebugRepl && vim.mode == 'Insert'" },
    { "key": "ctrl+w",  "command": "workbench.action.closeActiveEditor",
                           "when": "editorTextFocus && !inDebugRepl" },
    { "key": "ctrl+x",  "command": "editor.action.clipboardCutAction",
                           "when": "editorTextFocus && !inDebugRepl" 
]