hoge な blog

メモとかtipsとか

CyberAgent 2days サーバーサイド向け 開発型インターンシップ ONLINE 参加記

6/6~6/7の2日間でCyberAgentの「2days サーバーサイド向け 開発型インターンシップ ONLINE」に参加させていただきました.

2日間でISUCONのようなことをやっていきました(ISUCONエアプ並感)

具体的には

みたいなことをインターン参加者全員で競うというものです.

実装言語についてはNode.js, PHP, Java, Goから選ぶことが出来ました. 私はどれもそこまで詳しく知りませんでしたが(どれも1ヶ月も触っていない),なんとなくGoを選択しました.
Goの選択者が8割くらいでした.

インターンの中でどのようなことをしたのか,どう考えるべきだったかなどを振り返って行こうと思います.
メモを取りながらやっていたのですが,どうやら最後の最後でメモを他のファイルで上書きしてしまったようで,正しく書けるか心配です...
commitログを見ながら頑張って思い出します.

1日目

作業環境整理

とりあえず作業環境を整えることを目指しました.

  • ssh鍵登録
  • マシンスペック確認
  • zsh,vimの環境構築
  • 使用されているミドルウェアの設定ファイルやアプリケーションなどをhomeディレクトリにcopy
  • go実装に切り替える
  • nginx.confにaccess logを設定
  • ここでベンチを回す
  • MySQLでslow query logを整理
  • deployスクリプトを整備
  • MySQLの中身をdump
  • VSCode-remoteの設定
  • kataribe, pt-query-digest, pprofなどのインストール

を,大体上から順に行っていきました.

今思い返せば,

  • ベンチを回す
  • homeディレクトリにとりあえず全部移してはじめからgit管理する
  • deployごとにgit commitが走るようにする

などを行えていたらよかったと反省しています(ぶっ壊したときの復旧にめちゃくちゃ時間を掛けてしまったため)

これらを全部終えられたのが大体2時間くらいだと思います.

deployサイクルとしては,

  • homeディレクトリに存在するconfigファイルやアプリケーションをいじる
  • deploy.shを走らせる
  • プロファイルする

という感じなものを目指していたため,deploy.shの中で,

  • slow queryログ,access logのrotate
  • app.goのmakeと,サービスが動作していたディレクトリへのコピーとバックアップ

の2つをやりました.これもプロファイル情報を加工してすぐに簡単に見えるようにする工夫をすればよかったのかなと思います.

あとは,N+1あるぞ,と思って改善したり,COUNT(*)が色んなとこで呼ばれてて遅そうだな?とか思ってゴニョゴニョしていましたが,全くスコアは上昇しませんでした.
後で原因は判明したのですが,deploy.shを走らせたときに正しくデプロイされていなかったのが原因です.
「スコアが上がったときにcommitすればいいか〜」などと楽観的に考えていたため,あとで絶望することになりました.こういう人為的なミスを引き起こしそうなところはどんどんスクリプトに落としていくべきだったと後悔しています.

中間説明

中間説明では,

  1. プロファイラの設定
  2. 最初のボトルネック特定
  3. ボトルネック解消

のサイクルを一通り簡単に説明していただきました.
それを元にボトルネックを解消することにしました.
正直中間説明前までは何も改善作業をできておらず,スコアも最下位でした.プロファイラを見て何処のエンドポイントが悪さをしているのかがわかっても,何が悪さをしているのかが分からず途方に暮れていたので,中間説明に非常に感謝しています:bow:

indexを張る

2つくらい確かindexを張るところがあったので張りました.

GET / を改善しようと頑張る

めっちゃ重いクエリがgetPopularArticle()の中にあることが分かり,Countの結果でsortしているためこのクエリのまま改善することは難しそうだと分かりました.
とりあえず,articlesテーブルの中にカウンタを用意しておき,その値を読むようにしようと変更を試みましたが,実装が重くてこれでめちゃくちゃ時間を食ってしまいました.

1日目終了

以上で1日目に作業した作業は終わりです. 今振り返ってみると,この時点ではDBの変更しかスコアに反映されていないため,どの言語でやっても同じくらいのスコアだった感がありますね. (一応Goのコードも触っていたのですが,デプロイされていないためスコアには反映されていません)

2日目

N+1の改善

getUser

GET / は諦めて,他のプロファイルを見てみます. articlesが遅いというのがでてきたので,そこを見てみます. すると,getUserでauthorを取得した後にauthorが書いたarticleのupdated_atを取得するクエリをN回叩いていそうです. そこを改善しました. テーブルを結合してそのまま突っ込んであげれば良さそうでした.

( {{hogeFunc}} みたいにevalっぽい動きをするやつは,<!-- -->で囲ってコメントアウトしても呼ばれてしまうことに気づかずにベンチを回してしまい,スコアが伸びなくて焦りました)

getArticleTagNames

これもN+1でした. LEFT JOIN してあげればスコアが少しだけ上がりました.

再び GET / を改善しようと頑張る

さて,やはり GET / が遅いということで,slow queryを確認して見ると, getPopularArticle()が重いことが分かります. カウンタの実装は流石にきつそうだったので,どうしようかと思っていたところ, チームメンバーに「メモリ全然使われていないから,それを使うといいよ」というアドバイスを頂いたので,オンメモリで動かしてみようと思いました.

1回目

まずはじめに,DBへのアクセスを減らせばいいよな,と思い,iine_drity_flagのようなものを用意しました. これは,iineが作成,削除などを行われたことを管理するもので,flagがtrueであるときにgetPopularArticle()が呼ばれた時にだけDBにアクセスするようにしました. これにより,スコアが劇的に改善されました.(2000点台にまでスコアが伸ばせました)

しかしプロファイラはまだDBアクセスが律速だと言っています.

2回目

dirty_flagを消し去り,全てのiineCntをメモリで管理することを試みました.
具体的には,初期状態のcntをapp起動時にmap[article_id] = iineCntの形で初期化しておき,iineに変更が加えられたときに値を上下させ,sortさせることを考えました.
ただ,うまく初期化する方法が思いつかず,残り時間があと僅かだったため,この作業を停止しました.

最後のベンチマークを走らせる

以前のコミットに戻ってプロファイラを全て切ってVSCode-remoteも切ってスコアを計算しようとしました.ここで大ポカをやらかします. N+1を改善したときのcommit以降コミットを忘れていたせいで,スコアが大幅に落ちます. おそらく,getUser, getArticleTagNames, iine_dirty_flagの全ての作業を消し飛ばしてしまったのだと思います. iine_dirty_flagの作業は覚えていたためそれは復旧させましたが,getUser, getArticleTagNamesの変更を消し飛ばしてしまったことに気づいたのはベンチマークサーバ停止後でした.

最終スコア

  • index修正✕2
  • getPopularArticle()の改善
  • (何か他に忘れている作業)

のスコアで1885でした.

f:id:AkkyOrz:20200608154500p:plain
最終スコア

ただ,奇跡的に過去のスコアが残っていたため,スコアは2121でした.

f:id:AkkyOrz:20200608161438p:plain
最高スコア
(スコアサーバはインターン終了後に少しの間動かしておくということだったので,最終スコアは別の値となっています.)

その後

結果発表後,解説を聞いたり,社員さんのパネルディスカッションを聞いたり,社員さんと直接お話をしたりしました. それぞれで一番面白かったのは以下のとおりです.

  • スコアを上げるにはいくつか壁があり,高い点数を目指すならHandler側からHTMLファイルを生成して埋め込むなどの手法があるという話
  • ISUCON優勝者の方がプライベートクラウドの開発をしてるよ〜というお話
  • サービスインの前は非常に気を使って負荷テストを行っているという話(ゲーム開発の方)

最後に

2000点台に乗せることができてとても嬉しかったです. 全体では5位ということでしたが,同チームの人のアドバイスやメンターの方のアドバイスのおかげだと思います. 基本的に初期ではDBに律速が生じるため,まずはINDEXを確認したり,クエリを確認したりしないと,大きな律速を解除することができずスコアが伸びないことが分かりました. 計測→改善というパフォーマンスチューニングの姿勢に少しは身につけられたではないかなと思います.非常に有意義なインターンでした.

(P.S. MacWindowsが推奨されていましたが,Linuxマシンでも全然大丈夫そうでした.)