dorivenの日記

気がついたら社会人。気になる技術的なことについて少しずつ書いていけたらと思っております。

isucon7予選(敗退)と戦略と途中の思考過程とか

isucon5に出て圧倒的な敗北をした後に間隔あけてisucon7に出ました。
この記事ではNewRelicやMackerelのログを見つつどんな感じでパフォーマンスが改善されたか見ていく。(と言っても上位の方と比べると大したことないんだろうけど)

言語はPHPで参加で作業ログとか実装を残しているリポジトリが以下。

github.com

ちなみにこの記事は 足りない点・結論 という部分以外は記憶や認識の曲解を避けるために解説などを見る前(Twitterで流れてきた情報はちょっと見てしまったが)に書いています。

結論

Score : 15000 程度で敗退しました。
やはりisuconは難しいし、普段自分が触らない部分の知識が出てきたときに事前に予習出来てないと当然そこで終わるんだなと思いました。
レギュレーションの理解不足や一台のwebサーバで戦っていたのも色々とひどかった。

やったこと

得点や時系列での施策はリポジトリのログに記載している

とりあえずベンチ

この時のScoreが 6271 だった。

初期設定

ここで2時間近く使ってしまった。

一応環境を下記に記載する。

  • Editor : PHPStorm
  • Deploy : 同上(Deploymentの設定でSFTPを使って同期させていた)
  • パフォーマンス解析 : NewRelic APM
  • リソース監視 : Mackerel

パフォーマンス・リソース監視の導入

先人の方を見るとボトルネックの特定にalpを使っているものが多かったが自分NewRelic APMを使った。

理由として個人的に楽だったから。

  • ある程度のFWならルーティングをいい感じでまとめてくれるので見やすい
  • SQLのパフォーマンスも合わせて見れるのでslowlogの設定とか不要
  • メトリクスに起こしてくれるので後から振り返りやすい
  • 業務で使っている
  • APMは無料

Mackrelをなんで使ったかって?
使ったことなくて使いたかったからだよ!(というのがメインだけどマネージドな監視サービスで無料で使えたからというのもある)

監視サービス入れた後に再度Score図ったら 4000 付近まで低下していた。

NginxのUnixドメインソケット化

定番らしいので事前に予習してやった。
Scoreが 6443 と元に戻る。
なおこのときにisucon6予選の練習で起きなかったパーミッション周りで躓いて時間を喰うことに。

このタイミングで測ったときのAPMのグラフはこんな感じ。

f:id:doriven:20171025004646p:plain

imageにインデックス貼った (/iconsの改修)

上記の画像を見ると分かるがiconの取得に相当の時間がかかっていたのでまずはここから手を付けていくことに。

f:id:doriven:20171025005014p:plain

どこがボトルネックになっているかを見てDBの参照にやけに時間を使っていたのでそこを改善していった。
まずはインデックスを張ってみたけど行数が少ないのかあまり効果がなかった。
このときのScore 6526

iconsのキャッシュ化 (/iconsの改修)

というのであまり改善されなかったのでここでキャッシュに手を付けることに。
redisを導入してアプリケーションでもライブラリを入れた。

一度参照した画像のバイナリを参照時に保存してRedisにキャッシュすること。

この時点でDBの中に画像を入れるのはいずれ直せそうとは思っていたし結論そうなったので、後から見るとこの施策は無駄に終わったorz

f:id:doriven:20171025005748p:plain

結果、iconsのDBの参照部分は相当削れて上記のようになった。

havereadのキャッシュ化(fetchの改修)

iconsの改修が終わりったので次にfetchの改善。
どうやら見た感じでチャットのチャンネルごとの未読数を参照しているっぽく、DBに haveread という最終取得メッセージの状態を保存する3つのテーブルと関連を持ったテーブルをリクエストされる度にチャンネル毎に SELCET COUNT(*) していて見るからにヤバそう。
まずは簡単に手を付けられそうなhavereadの参照をなくすことから始めた。

初期状態でhavereadが存在しないという条件を元にINSERT時にRedisに追加、参照時はRedisから参照という策を取った。
が、これは駄目でScoreは下がるしこの後結局このコードでやったことは使われなくなった。  

この時点でのScoreは 7046 (すまん、画像は撮り忘れた)

未読数をキャッシュに載せることに(fetchの改修)

PHPの処理がメインで重く、次にmessageの参照が重いのでこいつをいかにはやくするかを考えた。
結果、fetchは未読数が欲しいので 未読数自体をキャッシュに載せる という手法で対応することに。 キャッシュは ユーザ x チャンネル で持つことに。
未読数の状態が更新される下記3パターンの更新が発生したときにRedisの状態を更新するようにした。

  • ユーザの新規追加
    • 登録されたユーザだけ、全てのチャンネルのメッセージ数 = 未読数としてキャシュにのせる
  • チャンネルの新規追加
    • 全てのユーザに、追加されたチャンネルのメッセージ数(つまりは0)をキャッシュにのせる
  • メッセージの新規追加
    • 全てのユーザに、追加されたチャンネルのメッセージ数のキャッシュを+1にする

この実装が浮かんだ時点で大分疲れが見えたので一度アイス休憩して実装。休憩大事。
これはそこそこ効いたのかScoreが 10758 まで上昇。

f:id:doriven:20171025012459p:plain f:id:doriven:20171025012641p:plain

Redis(のincr)がmessage追加時のredisの処理がボトルネックになり始める。

DBに入っていたデータをファイルにしてnginxから返す (fetchの改修)

時間もなくなっていたので気づいていたDBを経由しない画像の対応をしていくことに。
画像落とす関数を作って initialize 経由でファイルを吐き出した後にnginxの設定に1行追加してiconsをphpを経由せずに返すようにした。

このときのscoreは 14036 まで上昇。

このときにソケット周りで怒られているのかエラーが出ていたのでググってカーネルパラメータをいじったりした。
その後に再度ベンチマーク実行したら 11611 までスコアが下がって???となる。
更に静的ファイルを返す部分でタイムアウトが発生し初めて更に???となる。
終わった後にレギュレーション見直したら帯域制限あったという話だったのだが本番中はわからなかったし、分かっていてもnginxでの設定方法を予習してなかったので何も対応できなかったと思う。
このときは諦めて次の施策に。

predis(Native PHP)からphpredis(C extension)に切り替える

下がったscoreが 15634 まで上昇。
その後は静的ファイルのエラーについて考えようと思ったが時間も1時間を切っていたので再起動とかいらないサービス切ったりして終わった。

isucon7を通してのメトリクス

NewRelic APM

f:id:doriven:20171025014108p:plain

はじめはiconsの処理が重かったのが、その帯がなくなってfetchの部分も減り、 最終的にRedisのincrが重くなってmessageに負荷が寄っているあたりの流れが見える。
Redisに関しては正直どうしていいか分からなかったし、もし改善するならカウンティングをRedisに持たせる以外でいいやり方を考える必要がありそうなんだけど今も思い浮かばない。

Mackrel

f:id:doriven:20171025014202p:plain

徐々にCPUが使われているところやDBネットワークのトラフィックがiconsの対応でがた下がりした後の画像のファイル対応でeth1のトラフィックが徐々に増えている辺りの流れ見える。

足りない点と来年に向けて

上記を書いたあとで解説を読んだ。

isucon.net

レギュレーションちゃんと読もう

なんかもう色々とひどかった。(あのときの自分を殴りたい)

  • 過去の経緯から勝手にサーバ一台だけで捌くものだと思っていて一台でがんばっていた(実際DBが分かれていて実質1台じゃない時点でその点に気づくべきだった)
    • ポータル画面で一台しかチェック入れられないと思い一台でずっと頑張っていたり
  • 帯域制限があることに終了後に気づいたり
  • 304じゃないと点数にならないとかも終了後に気づいたり

やっぱり一人じゃ辛い

今回は結果として一人での参加になったのだけど情報共有がないのでゴリゴリ一人で進められるという気軽さがある分、

  • 上記みたいなレギュレーションの勘違いを起こしても注意してくれる人いない
  • 今回みたいなサーバが複数台なやつだと設定や構成の把握がアプリケーション改修込みでやると手がまわらない
  • チューニングで自分の案の枠を超えられない

効率的に時間を使えてなかった

後から見返すと結局使わない実装をしているのを二回もやってそこに地味に時間を取られた。(終わったあと、実装した後だから気付けるというのもあるのだろうけど)

  • iconsのキャッシュ化
  • havereadのキャッシュ化

最初のサーバの設定も事前に秘伝のタレとか用意しておけばもうちょっとチューニングに時間使えたなーと思ったり。   当初はやらなくていいかなと思っていたSSHの鍵認証も定期的にコネクション切れて面倒になったりとかしたので来年あたりはいい環境を短く作って行けるようにしたいね。

技術的に足りない部分

日頃からDBのパフォーマンスをどう改善していくか、とかは業務でやっている(結果、キャッシュ周りも触る)のでそこらへんにつまずくことはなかったのだが
一方で静的ファイル周りのキャッシュとかnginxの設定をどうしていけばいいのか、
みたいな所は用語としてどうすればいいかは分かっていても
それを具体的にどうやっていくかはこういうコンテスト形式だと引き出しとしては足りなかったのでここらへんの知識は深めて行きたい。

抱負

来年は本戦出場目指します。