新卒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の内容
SHISUCONの意義についてはこれがわかりやすい
今回の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から優勝の座を奪えてよかった✌️
戦略
まず、自分も@spring1018もインフラエンジニアではないため制限時間内でのmiddlewareの完璧なチューニングは難しいだろうと考えました。 そこで 「競技時間中はインフラのことをなるべく考えずにアプリケーションに集中する、そのために80%のクオリティのチューニングはコピペで終わるような秘伝のタレを作っておく」 と話し合って決めました。
事前準備
過去問を解く
@spring1018は初ISUCONだったということもあり、問題形式に慣れることを目的にISUCON7予選を本選出場ラインのscoreが出るまで解きました。
(ネットワーク帯域の再現はしていないので正確ではない & 本選出場のscore届くまでに2日かかった)
自分はISUCONは参加したことがあったのですが、アプリケーションしか触ってこなかったのでインフラ周りの知識が皆無でした。
そのため個人的にISUCON6予選、ISHOCON2*1を解いてインフラ周りのハマりポイントに一通りハマっておくよう心がけました。
wikiに諸々の情報をまとめる
Home · wantedly/shisucon2019-teppei-haruki Wiki · GitHub
競技開始前からリポジトリを作っておき、wikiを見ればほとんども問題が解決することを目指し充実させました。
普通に便利なのでもうちょっとちゃんとまとめてブログの記事にしたい.
作業フェーズをリマインダーに設定しておく
人間は焦ってしまうと予定を忘れる傾向にあるので複数台使うかどうかの最終判断/再起動試験チェックなど忘れたら困るやつは予めリマインダーに設定しておきました。 これで時間のことを気にせず安心して改善に取り組めました。 作業フェーズもwikiに書いていた。
本番(時系列)
自分のやったことを時系列で書いていく、ペアの@spring1018がやったことは↓に記事のリンクが貼られるはず
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 /hashtag
とGET /search
と同じくらいになった。
なので次はGET /
のN+1改善とdef search
で実行しているクエリを絞るのをやっていこうという話になった。
↓は申し訳ねえ申し訳ねえと呻きながらbinding.pry
しているときに送られてきた自分の部署のリーダーからのDM。絶対に許さない。
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点を叩き出していて悔しかったのでもう少し頑張ることにした。
- きちんと3台使う 24951→30044(DB1台+App1台)→37984(DB1台→App2台)
more server by euglena1215 · Pull Request #23 · wantedly/shisucon2019-teppei-haruki · GitHub GET /
のN+1改善のときに作ったuserのキャッシュを他のところでも使うよう変更した 37984→41251
Use user_id_to_name by euglena1215 · Pull Request #21 · wantedly/shisucon2019-teppei-haruki · GitHub- いろいろ修正してみた 41251→47076
Awesome refact by euglena1215 · Pull Request #25 · wantedly/shisucon2019-teppei-haruki · GitHub - mysqlの秘伝のタレを入れた 47076→50669
mysql hiden inject by euglena1215 · Pull Request #19 · wantedly/shisucon2019-teppei-haruki · GitHub - unicornのworker_processを増やした 変化なし
moremore worker process by euglena1215 · Pull Request #26 · wantedly/shisucon2019-teppei-haruki · GitHub - unicornからpumaにした 変化なし
Use puma by euglena1215 · Pull Request #29 · wantedly/shisucon2019-teppei-haruki · GitHub - userのキャッシュをインスタンス変数から
Thread.current
に持ち替えた 50669→59748
use thread current by euglena1215 · Pull Request #33 · wantedly/shisucon2019-teppei-haruki · GitHub - 事前にhtmlifyするようにした 59748→61137
Pre htmlify by euglena1215 · Pull Request #34 · wantedly/shisucon2019-teppei-haruki · GitHub - nginxのkeepaliveを設定 61137→65473
add keepalive by euglena1215 · Pull Request #24 · wantedly/shisucon2019-teppei-haruki · GitHub
最終的に65000点で終了した