読者です 読者をやめる 読者になる 読者になる

ISUCON3予選に参加してきました。 #isucon

isucon node.js

チームNoderとして @hokaccha さんと @hakobera さんの二人と一緒にISUCON3の予選に参加してきました!!

当日やったことと反省点をまとめていきます。

結果どうだったか。

9286点で無事上位20位以内(全体で17位くらいかな?)に入り、(暫定ですが)予選を突破したチームとして名前が載りました!!

awsのami-idがweb画面から見つからず、完全にテンパイ。
この辺りは hakobera さんが詳しいので完全にお任せ。僕とhokacchaさんはその時まだ何もできないので、aws-cliツールを入れてみたりしてた。

30-40分後くらいには環境を構築してくれたので、sshでログイン。
初期アプリ(perl)に対してひとまずベンチを実行。大体1000位のスコアだった。

そこからひとまずnode.jsに切り替える。

Node.js版が全然動かない。。

Node.js版はかなり地雷があったらしく、動いてもfailばかり返す。

一つ目の地雷は、tmpファイルの問題で、

以下の様なエラーが出る。

fs.js:513
  binding.write(fd, buffer, offset, length, position, wrapper);
          ^
TypeError: Bad argument
    at Object.fs.write (fs.js:513:11)
    at /home/isucon/webapp/nodejs/routes/index.js:12:12
    at /home/isucon/webapp/nodejs/node_modules/temp/lib/temp.js:151:7
    at Object.oncomplete (fs.js:107:15)
worker undefined died. restart…

ちなみにこれは、

Node.js実装についてはsupervisordではなくstandaloneで動かすとtmpfile()が $HOME/tmp にテンポラリファイル作ろうとするため、コンソールから直接npm start等で起動した場合はそのようなフォルダがなくエラーとなる問題が1日目のAMIにありました。この点でハマってしまった方、申し訳ありません。

ということだったらしい。

他には、セッションのmemcached storeがJSONのparseに必ず失敗してて、うーむ、、、という感じになってた。

そもそも動かないということで話しにならないので、 @hakobera さんにRedisのインストールをお任せし、 @hokacchaさんと僕で↑のアプリの修正を実施。

セッションストア先をRedisに変更、落ちてもworkerが死なないようにuncaughtExceptionをキャッチして無理矢理死なないようにした。

この時点で僕のログによれば、

1000から1631に向上

序盤やったことまとめ

  • AWS入れるように
  • セッションストア先をREDISに変更 (これはかなり効いてた、実際セッションストア先は罠だったらしい。)
  • worker数調整、workerが死なないようにした

中盤

アプリは動いたし、大分ミドルウェア周りをもっとしっかりしようという事で色々とやってた。

hakobera さんにNginxを入れてもらって静的ファイルをnode.jsに流さないようにしたり、
僕もリソース監視ツールとしてdstatを入れて監視できるようにしたりとしてた。

dstatのリソース見て、かなりメモリ遊んでるなーとか思ってたけど、これが後々聞いてくるとは。。

本当はmysqlでslow queryログを取ろうと思ってたんだけど、my.cnfが見つからない。。。
あれ~とか思っている内に @hokaccha さんがSQLを改善してくれたので一旦諦め。

本当は /usr/my.cnf にあったのね。。 mysql --help で出てくる場所に無いからどうなってんだろと思ってた。。。

あとは hokaccha さんが marked っていうライブラリを使って外部プロセスを呼び出しているところを書き換えてくれてた。

あと、expressをproductionモードにしたりと地道なものを実行。

この時点で僕のログによれば、

1631から2130に向上

中盤やったことまとめ

  • ApacheをやめてNginxから実行するように変更。
  • SQL改善 index付与。
  • markedを使う事でmarkdownの外部呼び出しをやめる
  • Expressをproductionモードに。
  • dstatでリソース監視とか。
  • node-mysqlのバージョンが古いのでバージョンアップ

終盤

正直このスコアだとまだまだで、全然入賞できない感じ。。。
一旦初心に立ち返ってログからアプリでどこをチューニングするか絞れるようにする。

hakobera さんがnginxのログをexcelに落としてくれたやつを見て、 / /recent にリクエストが集中してるし、そこが遅いことを突き止めたので、

カウントを取っているところをRedisのincrとgetに変えるようにするのと、usersをインメモリに持つように修正。

この時点で 4353点くらい。大分改善したけど、まだまだ10位に届いてなかった。

いやーどうしようー、あー、後何しようー、時間無いし、後は全データをRedisにするとかかな~とか思ってたら、
@hokaccha さんからusersだけじゃなく、memosもインメモリに載せるアイデアを実装。

時間がないからworkersは1つだけにして、複数workerプロセス間での共有は無視して実装。
実際はこれがかなり効いていきなりスコアがジャンプアップ!!

4353 -> 8394.7 になった!

後はnginxのworkerプロセスも1つだけに減らして極力nginxとアプリはCPUを使わないようにして、データはインメモリで持つ、という作戦にした。

最終的に最高ポイントは

9268.6 で 9位をマーク!!

終盤やったことまとめ

  • nginxのログで解析
  • countをRedisに(最終的に使わず。)
  • memoのデータを全てインメモリに。
  • workerの数を1つに。
  • nginxのworker数も1つに。

反省点

これは、Noderの皆が気づいてなかったんだけど、ベンチマークツールのworkloadを何も使ってなかった。
workloadを上げれば並列度が上がるので、リクエスト数が上がるため、もう少しアプリの限界を引き出した上でスコアを上げることに成功できる可能性はあった。

README.mdに書いてある事を読んだ時に、workloadは負荷をかけたいテストの時に使う程度で、ベンチマークはworkload 1でやった時のデータを採用するのかなと思い込んでしまい、全く疑問に思わずに使わないでやってた。

もちろん時間がないから試せなかったというのもある。こういうのは試さないとなぁ。。。

個人的には自分でやるといったmy.cnfが見つけられなかったり、Redisの実装に手間取ったりしてかなり迷惑を書けることもあって反省。。。

でもすごく勉強になった。

hokaccha さんもブログに書いてあるとおり、「意味のないオプションなど無い」という教訓は本戦で絶対に活かします!!

御礼

一緒に参加した @hakobera さん、 @hokaccha さんのお陰で物凄く良い成績が納められました。まだ暫定ですし、反省点も多いですが、本戦に頭を切り替えて次も頑張りましょう!!

運営した kayac 、 LINE の皆さん非常に良いお題ですごく勉強になりました。次回もよろしくお願いいたします!!!!