社内インフラチューニング大会の感想を書いてみる

社内エンジニアのインフラチューニング大会があったので感想を書いてみます。

大会の中身

チューニング大会の中身としては以下の様なもの。

  • セットアップされているMediaWikiをとにかく早くする
  • スコアの集計方法は明かされていないが、随時自分のスコアを確認できるページがある
  • AWSのインスタンスを4台を使っていい
  • 既存のプログラムに手を加えてはいけない
  • 全てをキャッシュにして静的化するなどのデータ更新を確認できないのは駄目
  • 他の人間に攻撃して遅くするとかは駄目

この枠組の中でどれだけ早く出来るか?を競っていたのですがインフラエンジニアでも何でもない僕が戦うにはレベルが高すぎました。

やったことを順番に

遠隔地での参加だったので説明をよく聞くこともできずにレギュレーションの資料とサーバの資料(IPとログインID)を渡されたのでとりあえずhttpでアクセスしてみるかとブラウザからアクセスしてみてが404。ルートが違うのかと思ってapacheのコンフィグファイルとか見てルートを合わせてみたけど変わらず404・・・。なんだこれと思ったら、apacheが起動されてないだけだった。ここからスタートなのかとしょっぱなからつまずく先行き不安な展開。。。

起動してブラウザでアクセスしてみるとWikiページが表示された。けどものすごい重い。たしかPHPを激速にするモジュールが合ったなぁと調べたところeAcceleratorが早いってGoogle先生がおっしゃっているのでeAcceleratorを入れてみる。eAcceleratorが入った状態で立ち上げなおしてアクセスしてみるとまさかの500エラー・・・。ちょっと調べてみてわからなさそうだったのでAPCへと変更。こっちはすんなりと起動して大体スコアが倍近くまで上がったのでちょっと満足する。

既存プログラムをいじれないならあとはMySQLだろとSlowQueryを吐き出すように設定してみると3秒近くかかっているクエリがあることがわかる。こいつをどうにかしようとexplain見てindexを確認してみたもののindex的に間違いはなさそう。しょうがないからinnodbのメモリ量とかクエリキャッシュとか入れてみたがスコアは全然変わらない。そういえばCPU使用率を見てなかったなと確認したところ、WikiにアクセスがあるとMySQLが150%ぐらいCPUを使っている。。。MySQLがボトルネックなのはわかったのでこいつをどうにかしようと考えていると、上位のスコア5万とかなっていて僕のスコア(2000ぐらい)と1桁違ってたのでなにがしかの抜け道があるんだろうと推測。

もう一度SlowQueryのログを見てみると重いクエリが30万件ぐらいから絞り込んでいることがわかる。これindexおかしいだろうと片っ端からindexを貼っては消してを繰り返してみるも全然早くならない。調べてみるとGroup ByとOrder Byのdescを指定するとindexはうまくきかないよって記事を見つける。これが原因かとわかったものの解決策はサブクエリを使いましょうとかそんなものなんで既存プログラムをいじれないからどうしようもない。。。やけっぱちでMySQLのバージョンが5.5だったので5.6系にあげてみようと色々試してみるが失敗。ファイルがconflictを起こしてるとエラーが出てうまく上がらない。ファイル消して無理やり上げてやろうかとも考えたが壊れてしまったら復旧できる自信がなかったので諦める。

そういえば4台インスタンスを配られてるってことは負荷分散すべきなんだよなと頭を切り替えてMySQL Clusterを導入してみようと試行錯誤してみるも既存プログラムいじれないならどうしようもないなぁとはたと気付きやめる。全てをキャッシュにして静的コンテンツを返答させるのは禁止と書いてあるけど全てじゃなければいいのかなとApacheのアクセスログを確認してスコアの集計に使っているURLを抽出してみる。アクセスを確認するにPOSTとGETを交互に送っているみたいで、POSTでページを更新してちゃんと更新されているかをGETで確認しているとスコア計測のPGを推測してみたが、それならキャッシュ効かせた時点でアウトじゃね・・・。そうは言うものの、他にやることも思いつかなかったのでapacheのdisk cacheを導入してみたけど全くスコアは変わらない。

やっぱりMySQLがボトルネックだよなぁとMySQL Clusterを導入すべきなのか?とか考えながらも時間がねぇよなぁとちょろちょろと他の設定をいじっている間にタイムアップ。結果としては中の下ぐらいのポジションで最初のAPCを入れてからほぼスコアが変わっていないという散々な結果。。。

総評

インフラエンジニアってすげーなぁと思いつつも、勉強不足を痛感しました。スコアが1桁違う人達はどうもnginxのproxy機能を使ってほぼキャッシュで返していたらしい。それってデータの更新ってどうなの?って思ったけど、expireの時間を短くすることで対応したのかなぁとか想像。さらに終わったあとに既存プログラムは触っちゃいけないけどMediaWikiの設定ファイルであれば触ってよかったってことがわかり、それならもうちょっとやることあったのかなぁとか思いました。遠隔地での参加だったので会場の雰囲気を味わえなかったのはちょっと残念ではありましたが、終わった感想としては面白かったの一言に尽きますね。自分の勉強不足も分かりますし、社会人になってきっちりと順位が出るような試験ってなかなか無いので定期的にこういうコンテストがあると面白いと思いました。

node.js + mongodbで気をつけたほうがいいこと

今まで主にクライアントサイドのプログラミングをしていたのですが新規サービスでサーバサイドを任されたため、その時に多少ノウハウがあったnodeとnode使うならmongodbだろと安直な考えでサービスを作成して2ヶ月半が経った。備忘録も兼ねて気をつけたほうが良かったと思うことをまとめておこうかと思う。

ちなみに以下のサービス。android版もリリースされたので良かったら使ってみてね♡

タップル誕生 – https://tapple.me

mongodbでindex設計は超重要

サービス初期段階ではデータ数が少なくて問題なかったがデータが多くなるにつれてindexがついていないクエリがどんどん重くなった。100万レコードを超えたあたりからindexを使わずに引いてしまうとかなり遅くなってしまう。しかし安直にindexを貼ってしまうとupdate/insertで時間がかかってしまう。なので、indexは極力貼らずに_idのみで引っ張ってこれるようにデータをいろいろな場所に置いておくのが一番良いと今のところ思っている。具体的にどういうことかというと、userが記事を投稿するとuserは記事idを、記事はuseridを持つように設計する。こうすることで、userが投稿した記事一覧を取得するときにはユーザが持っている記事idを$inでmongodbに渡してあげればkey検索となり激速で取得することが出来る。ソート等も$inでid一覧を渡してあげればそこまで性能劣化は起こらなかった。当たり前っちゃー当たり前の話。

大量のデータを書き込む場合は小分けにする

mongodbのクソ仕様としてwrite lock中はすべての処理が止まるというのがある。あるcollectionにデータを書込していれば他のcollectionのreadだろうがなんだろうがwriteが終わるまで待たされる。なんでそんな設計なのか小一時間説教したいが仕様なので置いとくとして、この動作のせいで大量のデータを数msで書き込もうとするとwrite lockが雪だるま式に膨らみmongodbが固まることになってしまう。今のサービスでは数千件のレコードを更新する処理を一気に流していたため、その処理が走ると数分サービスの全てのAPIが待たされるという事になってしまった。対策としては単純で分割してゆっくり書き込むようにする。write/readで一過性がないと問題がある処理は除くとしてそれ以外の処理は書き込みは遅いものと割り切ってしまいゆっくりと書き込んでいくほうが全体的なパフォーマンスは上がる。

nodeで重い処理はしない

nodeはsigle threadのイベント駆動で動いている。なので、1つのeventで重い処理をしてしまうと他の処理がすべて止まる。数千件のデータをforでsortとかしてしまうとその処理がnodeを専有してしまうことになり、その後に控えている軽い処理も全て遅くなる。なのでnodeで重い処理はやってはいけない。やるならasyncモジュール等を使用してeventを阻害しない程度の小さなループを大量に行うようにすればいい。そうすると処理と処理の切れ目で他の軽い処理が走るので全体的に見るとパフォーマンスは上がることになる。

まとめ

こうやって書いていくとあたりまえのことが多い。けど、最初の舵がずれているとどんどんずれていって軌道修正するのが時間が経てば経つほど難しくなるので最初から気をつけておくといいのかと思う。