カレーの恩返し

おいしいのでオススメ。

新卒ISUCONで優勝しました

新卒ISUCON(SHISUCON)とは

弊社ではwebアプリケーション開発の基礎となる知識を実践的に吸収するため、新人研修の一環として新卒メンバー+CTOでISUCONを行うことが恒例になっています。
このISUCONをSHISUCON(SHinsotsu ga Iikanjini Speed Up Contest)と呼んでいます。(他の人は新卒ISUCONと呼んでいます)

ルールは以下の2点を除けば本来のISUCONと同じです。

  • 競技時間が11:00~17:00と本番より2時間短い
  • 13:00~13:00, 15:00~15:30の計2回、ISUCON上級者のメンターがアドバイスをしにきてくれる

↓去年、一昨年のSHISUCONの内容

www.wantedly.com

www.wantedly.com

SHISUCONの意義についてはこれがわかりやすい

speakerdeck.com

今回のSHISUCONではYahoo!Y!SUCONを利用しました。
Y!SUCONは1台のサーバで解くことが想定されている問題ですが、今回は3台のサーバ(c4.large)が与えられました。

新卒ISUCONで優勝しました

新卒ペア*3 + CTOおひとりさまの4チームで戦い、自分は @spring1018 とチームを組みました。(新卒ではないけど別業種からの転職&本人の強い参加希望があったので新卒枠としての参加になったらしい?)

自分達のチームは24,550点でフィニッシュしCTOをおさえて優勝することができました。 自分達は結局サーバを1台しか使っていないのでY!SUCONと同じ状況でのscoreとなっています。
今まで行われていたSHISUCONではCTOが毎回優勝していたのでCTOから優勝の座を奪えてよかった✌️

f:id:euglena1215:20190512003038p:plain
青いのが自分のチーム

f:id:euglena1215:20190512005606p:plain
負けて体調が悪化するCTOの図

戦略

まず、自分も@spring1018もインフラエンジニアではないため制限時間内でのmiddlewareの完璧なチューニングは難しいだろうと考えました。 そこで 「競技時間中はインフラのことをなるべく考えずにアプリケーションに集中する、そのために80%のクオリティのチューニングはコピペで終わるような秘伝のタレを作っておく」 と話し合って決めました。

事前準備

過去問を解く

@spring1018は初ISUCONだったということもあり、問題形式に慣れることを目的にISUCON7予選を本選出場ラインのscoreが出るまで解きました。
(ネットワーク帯域の再現はしていないので正確ではない & 本選出場のscore届くまでに2日かかった)

自分はISUCONは参加したことがあったのですが、アプリケーションしか触ってこなかったのでインフラ周りの知識が皆無でした。
そのため個人的にISUCON6予選、ISHOCON2*1を解いてインフラ周りのハマりポイントに一通りハマっておくよう心がけました。

wikiに諸々の情報をまとめる

Home · wantedly/shisucon2019-teppei-haruki Wiki · GitHub

競技開始前からリポジトリを作っておき、wikiを見ればほとんども問題が解決することを目指し充実させました。
普通に便利なのでもうちょっとちゃんとまとめてブログの記事にしたい.

作業フェーズをリマインダーに設定しておく

f:id:euglena1215:20190512181402p:plain:h300
こんな感じ

人間は焦ってしまうと予定を忘れる傾向にあるので複数台使うかどうかの最終判断/再起動試験チェックなど忘れたら困るやつは予めリマインダーに設定しておきました。 これで時間のことを気にせず安心して改善に取り組めました。 作業フェーズもwikiに書いていた。

本番(時系列)

自分のやったことを時系列で書いていく、ペアの@spring1018がやったことは↓に記事のリンクが貼られるはず

spring1018.hatenablog.com

10:50ごろ 競技開始
真っ先にbenchを走らせて初期scoreを確認、確か1500点くらいだった気がする

10:58 ~/をgit管理下に

11:17 nginx, mysql, sysctlをgit管理下に移動

11:28 kataribe, myprofilerが動く状態になる
@spring1018がDB schemaの調査を終わらせてくれていたのでkataribe, myprofilerを見つつbenchを回した。
kataribeはこれ、myprofilerはこれ
kataribeから「この時点では静的ファイルもアプリケーションを介して配信していたのでnginxで配信した方がいいよね、ついでに304つけちゃおうか、ついでにnginxの秘伝のタレ流し込んじゃおうか」と判断。
myprofilerから「tweets系が明らかに遅い、一旦created_atにindex貼ってお茶を濁そう」という判断をした。(確かあんまりscore変わらなかったはず)

11:50 nginxの秘伝のタレを流し込む hiden nginx by euglena1215 · Pull Request #6 · wantedly/shisucon2019-teppei-haruki · GitHub
scoreはほぼ変化なかったけどkataribeががらっと変わって GET /が遅すぎるということがわかった
ISUCON序盤のはずなのに全然CPUを使ってなかったのでおもむろにunicornのworkerを1→2に増やしたら1500→1700になってDBのCPUがボトルネックであることがわかるようになった。

12:00すぎ アプリケーションの概要をきちんと把握する
ようやくボトルネックの箇所がわかるようになってきたので二人でアプリケーションの概要の整理をした

  • isuwitterとisutomoが2つに分かれている(DBも別)
  • tweetsのオーダーが10万なので重そう、GET /ではフォロワーのツイートだけでいいのに全員のツイートを取得している。無駄っぽい。
  • GET /でusers.nameを取得するためにN+1が発生している、直したい

12:30ごろ DBを1つにくっつける
初期実装ではisuwitterとisutomoの2つにDBが分かれていたのをisuwitter DBにfriendsテーブルをもってくることによって1つにくっつけた。
理由は以下の通り。

  • 今後2つのサービス(isuwitterとisutomo)をくっつける可能性がある、そうなったときには2つのDBとのconnectionを張る必要がでてきて2倍のconnectionが必要になる
  • 後半にテーブルの移し替えを行うとindexを貼り忘れるといった可能性がでてくる、なら何も手をつけていない今のうちにやっちゃえばよさそう

結局http通信による遅延がボトルネックになるところまでチューニングが進まなかったのでほとんど意味はなかった気がする。

15:03 GET /でフォロワーの最新50件のツイートだけを取得するようにする 1700→3800 where friend ids by euglena1215 · Pull Request #14 · wantedly/shisucon2019-teppei-haruki · GitHub
この修正に2時間かけてしまい、申し訳ねえ申し訳ねえと呻きながら修正したら3800に点数が上がってホッとした。

kataribeを見るとこんな感じにGET /が若干早くなってGET /hashtagGET /searchと同じくらいになった。
なので次はGET /のN+1改善とdef searchで実行しているクエリを絞るのをやっていこうという話になった。

↓は申し訳ねえ申し訳ねえと呻きながらbinding.pryしているときに送られてきた自分の部署のリーダーからのDM。絶対に許さない。

f:id:euglena1215:20190513011018p:plain
自分のscoreが伸びないことを喜ぶチームリーダーの図, 一番下が自分のチーム

15:28 GET /のN+1改善 3800→3941 global user hash by euglena1215 · Pull Request #16 · wantedly/shisucon2019-teppei-haruki · GitHub
全然伸びなくて :thinking_face: という感じだった。SHISUCON終了後にキャッシュをインスタンス変数からThread.currentに持ち替えたら50000→59000とガッと上がったので最初からThread.currentで保存するようにしていたらもっと伸びていたかもしれない。

15:32 def searchで実行しているクエリの取得数を絞る by @spring1018 3941→13311 change get_all_tweets by spring1018 · Pull Request #17 · wantedly/shisucon2019-teppei-haruki · GitHub
これが効いてかなり点数が伸びた。
kataribeを見るとこんな感じにまたGET /ボトルネックが戻ってきた。

この辺りから複数台構成を意識し始めて@spring1018に2,3台目のセットアップをお願いした気がする。

16:00 GET /でisutomoにアクセスしていたのを直接実行するように変更した 13311→14865 join GET / by euglena1215 · Pull Request #11 · wantedly/shisucon2019-teppei-haruki · GitHub

この辺りで@spring1018が2台目のサーバの/etcを全消去するというアクシデントが発生し、2台目は復旧不能になってめちゃくちゃ面白かった。
一応3台目もセットアップするようお願いしたけど、さすがに時間が間に合わず最後まで1台のサーバで動かすことになった。

16:15 tweets tableにuser_id, created_atの複合indexを貼った 14865→23914 myprofilerでスロークエリを確認するとSELECT * FROM tweets WHERE user_id = ? ORDER BY created_at DESCが一番多かったのでindexを貼るとすごい点数が上がった。わーい。

16:36 erbをerubisに変更 23914→24951 add erubis by euglena1215 · Pull Request #20 · wantedly/shisucon2019-teppei-haruki · GitHub 定番のやつ

この辺で「もうこれ以上触ると壊れそうだし何もしない方がいいよね*2」ということで片付けて撤収した。

その後結果発表で優勝したと聞いてよかったねーとなった。

感想

今まで参加していたISUCONではインフラ周りを全く触っていなかったのでとても良い勉強になってよかった。
本番のISUCONも本選出場できるよう頑張りたいです。


延長戦

競技終了後、雑談をしているとその間に他のチームが47000点を叩き出していて悔しかったのでもう少し頑張ることにした。

最終的に65000点で終了した

*1:本来はSHISUCONではこの問題を使う予定だったけど自分が解いていることを知って急遽SHISUCON2日前に問題変更したらしい。ごめんなさい

*2:と言いつつも競技終了5分前に「とはいえSELECT *くらいは消せるのでは?」と修正してしまいエラーがめちゃくちゃ出て死ぬかと思った。触っちゃダメ絶対