SEがISUCON6予選にrubyで参加して敗退してきた

![ISUCON](http://isucon.net/parts/logo.png "ISUCON")

今年で2年目の参加となるISUCON6予選で敗退してきたので来年に向けてメモ書きを残します。

## ISUCONとは([公式サイト](http://isucon.net/archives/46239949.html)より)
お題となるWebサービスを決められたレギュレーションの中で限界まで高速化を図るチューニングバトル、それがISUCONです。過去の実績も所属している会社も全く関係ない、結果が全てのガチンコバトルです。

## 参加の経緯
とあるSIerでインフラ系SEをやっている同僚3人で昨年のISUCON5に参加したところ、予選の結果は[惨敗](http://rsooo.blogspot.jp/2015/09/isucon5.html)だったので、同じチームでリベンジ。

* さいとぅ @rsooo 金融系のプロジェクトで商用UNIXのお世話をしてた
* ぎんじ @ginjih WebセキュリティのASPの維持管理してた
* あんどぅ @yuki549 社内向けのプライベートクラウドを作っていた

普段の仕事はExcelの設計書や手順書を作ったりレビューすることなので「Excel職人」というチーム名で参加しました。

## 役割分担・方針
前述のとおり、業務で使っているなど得意があるわけではないので、3人でアプリの動作環境を把握し、コードを読みながら仕様を確認して適宜分担しました。

チームで事前に決めていたこととして、

* Rubyで解く(3人ともがそれなりに慣れている言語をチョイス)
* 本番機1台+各自の開発機3台をAzureにデプロイして作業
* ソースコードや設定ファイルはGitHubのPrivateで管理
* 開発機でベンチを実行後、スコアが上がったらmasterブランチに直接pushし、本番機でpull
* slackのチャンネルをGitHubのリポジトリと連携しておき、masterへのプッシュを共有
* 本番機にはMackerelで監視を入れておき、サービスが落ちたらslackに通知させる
* 仕様の確認とプロファイリングをしっかりやって、最重要ボトルネックを優先して解決する(重要)
　⇒去年は目についたところからちょこちょこ修正しているだけでスコアが大して上がらなかった。。

[こちらのサイト](http://blog.yuuk.io/entry/web-operations-isucon)を参考にさせていただき練習しました。

## やったこと・結果
今回のお題はIsudaというはてなダイアリーのようにキーワードリンクが自動付与される記事投稿サイト。
後から投稿された記事に対して既存の記事からも自動的にリンクがつくというのが肝。
IsutarというStarを付与・取得するAPIが別途サブアプリとして外出しされており、最近流行りのマイクロサービス的なアーキテクチャか～と、馴染みのないSEとしては感心して眺めていました。

| 時間 | やったこと | スコア 
|:-----------|:-----------|:-----------|
|09:00|予選会場のLINEオフィス@ヒカリエに向かうため渋谷駅集合<br>コンビニで飲み物やランチの買い出し|-
|09:30|運営の方に案内されカフェスペースへ<br>机と椅子だけでなく、電源タップも貸し出してもらえて快適な作業環境でした(自販機で軽食も買えた)|-
|10:00|本番機1台+各自の開発機3台をAzureにデプロイ開始<br>待っている間に予選マニュアルを読む|-
||デプロイ→10分待つ(この間にDeployされる)→再起動→ようやく使える|-
|10:15|画面確認、サービス起動状況やファイルの在り処、DBの内容の確認|-
|10:50|初期状態で約7000件のキーワードを正規表現のパターンマッチでなにやら激しい処理をしているhtmlifyがやばいと気づく<br>![キャプチャ.JPG](https://qiita-image-store.s3.amazonaws.com/0/94938/ca9c1613-395b-be2a-80dc-00ef7b80dc0e.jpeg)|-
|11:00|初期状態(perl実装)でのスコア計測|3000
|11:10|ruby実装に入れ替えてのスコア計測<br>⇒0点になってしまい焦るが、マニュアルにも不具合ではないと書いてあるので覚悟を決めてrubyで断行|0
|11:15|nginxのログ出力設定を変更し、alpでレスポンス解析<br>Rack-Lineprofを入れてSinatraの各メソッドの処理時間を計測(ここでもhtmlifyが支配的)|-
|11:50|コードをざっと読んだ結果、SQLのwhere句/order句で使われているカラムに対する足りないインデックスを追加<br>`create index keyword on star(keyword);`<br>`create index updated_at on entry(updated_at);`|-
|12:30|nginxでCSS/JS/画像をキャッシュした<br>nginx=>unicornの通信をドメインソケット化したらなぜか遅くなったのでやめた|-
|13:50|[キーワードリンク付与済みエントリをRedisにキャッシュし、POSTがあるまではキャッシュから返すようにした](https://github.com/rsooo/ISUCONwork/commit/872451fa0498abee8e49fd398e7c421ed7f23e1b)(htmlifyの頻度を減らす)|18000
|14:50|[Star付与時にRedisにRPUSHし、Star取得時はLRANGEするよう変更](https://github.com/rsooo/ISUCONwork/commit/da1e4c28960c003114dc601258442c087226d4fe)。Redisへはドメインソケット接続しHTTP廃止|19000
|15:30|robot.txtへの404エラーをnginxから返すようにしたがほとんど変わらず|-
|16:00|["/"でのキーワードリストの取得をエントリごとではなく1回にした](https://github.com/rsooo/ISUCONwork/commit/f5ad6873756937f54fdd0f4b2e1a2dffde323d3e)|20000
|17:00|[ログイン処理のユーザ情報をRedisにキャッシュ](https://github.com/rsooo/ISUCONwork/commit/cef54394367b220d1236f03e2d87e0debe62e30b)|21000
|17:10|再起動試験実施<br>最後の悪あがきでカーネルパラメータ(sysctl.conf)/ファイルディスクリプタ数(limits.conf)/ innodb_buffer_pool_size(my.cnf)の拡張や、ログ出力の停止(nginx)など|23864
|18:00|終了|21510
![kekka.jpg](https://qiita-image-store.s3.amazonaws.com/0/94938/9774765a-5a95-626d-a813-5c91e655e665.jpeg)


## 感想・やりたかったこと

* 解消すべきボトルネック(今回はhtmlify)を早期に発見し、3人で課題認識を合わせて着実に取り組んでスコアにつなげられたのはよかった。慣れないUbuntuにオロオロした後、小手先のチューニングしかできずほとんどスコアが出せなかった去年よりは確実に成長できた。
* GitHubやSlackといったツールはもちろん、SystemdやMySQL,Redisのオペレーションも事前に練習しメモを作っておくことでアプリケーションの把握や修正がスムーズにできた。慣れておくだけで全然違うのでオペレーション習熟の効果は大きいと感じた。デプロイ失敗してベンチを回す前にMackerelがSlackに通知してくれて時間を無駄にせずに済んですげーってなった。
* 14時台のスコアで暫定5位になったように見えたことで油断し、htmlifyの激しい処理と向かい合うことから逃げてしまった。。冷静に処理の中身を見ていれば、[Aho Corasick](https://ja.wikipedia.org/wiki/%E3%82%A8%E3%82%A4%E3%83%9B%E2%80%93%E3%82%B3%E3%83%A9%E3%82%B7%E3%83%83%E3%82%AF%E6%B3%95)の実装まではできなくとも、[gsubで正規表現のコンパイルを10回やっている部分を直す](http://qiita.com/yuki549/items/b2c5f5c398782950a171)ことはできたのに。。。
* POSTが1件でもあったらhtmlifyのキャッシュは雑にフラッシュしていたが、他のチームの投稿を見ると、`WHERE description LIKE '%#{keyword}%';`などで取得したキーワード更新対象エントリだけキャッシュを消すなどうまいことやっていて、この実装で最初に全件キャッシュに載せていればスコアがもっと伸びただろうなーと思った。

## まとめ
いろいろと悔いが残る部分もありましたが、Webサービスとは無縁のSEでも練習すればそこそこできるようになる手応えを感じ更に楽しめました。
ISUCON4⇒5⇒6でチューニングのポイントがインフラからアプリにシフトして来ているように感じるので、コーディング力を強化していきたいです。
ISUCON5の予選(ISUxi)と同じくらい凝った作りのアプリで、実装方法も参考になり、8時間夢中になってチューニングができました。

運営の方々には、無料でこんな貴重な経験(場所提供まで)を用意していただけて感謝の言葉しかありません。
また来年も開催されればぜひ参加して、今度こそ予選突破を目指したいと思います。

## 謎
以下の謎の挙動を確認したのですが、rubyで参加された方でもし原因・対処がわかる方はコメントで教えていただけるとうれしいです。

* キーワードリンクのリンク先URLがlocalhost向けになっているものがあり、辿れないキーワードがあった。(Sinatraのヘルパーメソッドurl()の仕様…？)
* 競技終了後ですが、一定時間経つと`MySQL server has gone away`のエラーが出て、"/"に対するリクエストがずっと『Internal Server Error』になる。(ヘルパーメソッド`def db`の`Thread.current[:db] ||=`という書き方に問題がある…？)