ISUCON5予選でスコア19450出しました

サーバチューニングの勉強になればとISUCON5の予選に参加してみたところ、予想外に予選通過できてしまいました。 初めてISUCON参加してみての感想を一言で言うと、めちゃくちゃ疲れるけど楽しかったです。 参考までに、僕が行った作業内容を書いてみます。

ベンチマークの実行を成功させる

まずベンチマークを実行できるところまで持っていきます。ここまではなんとかがんばるしかないですね。

mysql, nginx,unicornの設定チューニングする

前回参加者のブログを参考に、あらかじめ準備していたチートシート通りに設定しました。 systemdの使い方がわからなくてサービスを再起動方法をググったりして、わりと手間がかかりました。

設定内容は以下のとおり。

my.cnf
innodb_buffer_pool_size = 2G
innodb_flush_log_at_trx_commit = 2
nginx.conf
worker_processes  5;
worker_rlimit_nofile  4096;

  log_format  ltsv  'host:$remote_addr\t'
                    'vhost:$http_host\t'
                    'port:$server_port\t'
                    'time:$time_iso8601\t'
                    'method:$request_method\t'
                    'uri:$request_uri\t'
                    'protocol:$server_protocol\t'
                    'status:$status\t'
                    'size:$body_bytes_sent\t'
                    'referer:$http_referer\t'
                    'ua:$http_user_agent\t'
                    'forwardedfor:$http_x_forwarded_for\t'
                    'forwardedproto:$http_x_forwarded_proto\t'
                    'apptime:$upstream_response_time\t'
                    'reqtime:$request_time';

  #access_log off;
  access_log /var/log/nginx/access.log ltsv;
unicorn

プロセス数を5に

プロセス数はあとで増やすなど試してみたんですが、効果がなかったので5に戻しました。 ログ解析をするために、ltsv形式で出力しようというのは事前に決めてました。

ここまでである程度スコアが上がったのを確認して、次に戦略を立てるためにログ解析の準備をしました。

ログ解析の準備をしてログ解析する

sonotsさんが前回のISUCONでltsvのログをスクリプトで整形しと書いてたので、このアイデアをそのまま使わせてもらいました。

https://gist.github.com/sonots/9ae0167d31d7f4b42c9a

実際の作業ではボトルネックになってる箇所を見やすいように、URLをグループごとに見れるように、スクリプトを調整しました。

あとはアクセスログを退避してクリアするスクリプトも作っておきました。 これでベンチマークを動かすごとに、詳細なログ解析が得られるようになりました。 これを見てると / へのアクセスの処理にほとんどの時間がかかってて、静的ファイルへのアクセスはほとんど影響してないことがわかりました。なので今回の問題は、静的ファイルを素早くさばくことよりアプリケーションの処理を高速化するのが効果的だろうと判断しました。 このログ解析の仕組みは最後までチューニング方針の決定に役立ってくれたので、準備しておいたかいがありました。

アプリケーションのチューニング内容

アプリケーションのチューニングでやったことは、とくに大きな構成の変更はせず、参考実装のボトルネックを潰していった感じです。 N+1問題を潰したり、mysqlのindexを追加して行ったりしたら順調にスコアが上がっていってくれました。 今回の課題はアプリケーションのボトルネックを潰すのがメインだったので、アプリケーションエンジニアの自分にとっては普段やってる仕事と同じような感じで作業していけました。

反省点

  • shellやエディタが普段の環境とちがうまま作業してしまって非効率でした。dotfileをgithubに置いておいて適用したほうがよかった。
  • N+1問題が起こってるところをgrepで見つけていたのが良くなかったので、N+1問題を検出する仕組みを準備しておくべきだった。
  • kazeburoの術によるとnginx設定はまだ足りないものがある感じ。( https://gist.github.com/sonots/62179703d429d36b1a57 )本選までに勉強しておこう。

良かった点

  • ログ解析ツールが役に立った stat.rbは役に立った。実行結果を分析できるので、効率的にボトルネックに対処できた。
  • mysql, nginxのチートシートは役に立った
  • 予習したおかげで、設定ファイルの場所などで戸惑わずにすんだ。

作業ごとのスコアの遷移を掲載しておきます。 だいたいアプリケーションの修正とindexの追加に時間を使っていて、順調にスコアが上げれてましたね。

12:00くらい
初回ベンチマーク実行
=> スコア 146

12:30
* nginx worker process を5に
* unicorn worker process を 5に
=> 881

12:35
my.cnf修正
innodb_buffer_pool_size = 1G
innodb_flush_log_at_trx_commit = 2
=> 889

13:01
my.cnf修正
innodb_buffer_pool_size = 2G
=> 911


13:12
vimrcをいつも使ってるサーバからコピペ。vimプラグインは面倒なので持ってこなかった。

13:30
ログを解析したところ / に時間がかかってることがわかったので /を高速化する

13:40
footprints, relationsにindex追加
=> 2248

14:19
is_friend? をone -> anotherの向きのみ見るように修正
=> 2762

14:30
tail -f してたのをやめた
=> 2911

14:55
friend_idsを最初に取っておくように
=> 4352

15:06
comment取得にもfriend_idsを使うように
=> 4828

15:43
comment取得をsqlだけで取得するように。commentsにindex追加
=> 12413


16:04
/friends のSQLをor使わないように修正
=> 12432

16:17
/friendsをリファクタリング
=> 12480

16:26
nginx, unicornのworkerプロセスを5 -> 10に
=> 12313
効果ないので8に変更
=> 
効果ないので5に戻した

16:55
footprintのindexをuser_id, owner_id, created_at に変更
=> 12460

17:08
一部erubis => slimに変更
=> 見た目には正しく変換できてるように見えるがbenchmarkerでエラーになるので中止。

17:46
トップページでfriendsがcountしか使われてなかったので、sqlでcountを取得するように修正
=> 13145

18:06
get_userが何度もviewから呼ばれてるので、開始時に取得するように修正(途中)
=> 14468

18:17
viewからentryを何度もSQLで取得してたので、一括して取得するように修正
=> 15476

18:31
/entriesでコメント数を取るために何度もSQL実行してたのを一括して取得するように修正
=> 16704

18:45
/footprints, /friendsでget_userを何度も実行してたのを一括して取得するように修正
nginxのaccess log をoffに
=> 19450

入門Reactの感想

Reactの学習のために、入門Reactを、ひと通りざっと読んだ。

入門 React ―コンポーネントベースのWebフロントエンド開発

入門 React ―コンポーネントベースのWebフロントエンド開発

読むまでのReactの知識は、Hello, Worldを以前試したことがある程度だったが、問題なく読み進めることができた。ただし、インストールの手順などを丁寧に説明してはくれないので、JavaScriptでの開発経験は求められると思う。本の内容を一言で言うと、Reactの構成をひと通り説明している本だった。

Reactがカバーする範囲の線引を明確にしてくれたのが、個人的に一番ありがたかった。 おおまかに言うと、ReactがカバーするのはDOMの操作とイベント処理で、ルーティングやMVCのモデルなどはReactの範囲外になる。そのあたりは他のフレームワークなりと組み合わせてまかなう必要がある。16章で、Backbone.RouterやFluxなど、Reactと組み合わせて使うライブラリの候補を挙げてくれてる。

また、データの流れを一方向にする仕組みや、props, stateの使い分けの基準がよくわからなかったが、そのあたりをわかりやすく説明してくれている。コンポーネントのフック関数一覧を、呼ばれるタイミングを交えて詳しく書いてくれてるのもありがたい。

本書を読み終えた段階で、今後Reactを勉強するのに困らない、下準備ができたと感じた。 ReactはMVCでいうところのVを担当するライブラリなので、サーバとのデータのやりとりや、プログラムのモジュール構成といったものは、別の仕組みで補う必要があり、Reactだけではプログラムは完結しない。Reactを組み込んだプログラムの設計方法を今後調べていくつもり。

Rubyのcaseを使ってFizzBuzzを書く

Shinosaka.rb #4におじゃましてきました。

今日はFizzBuzzを書くという課題でした。

FizzBuzzは何回か書いたことがあったので、あたらしい書き方ができないか考えてみたんですが、case式を使えば今までにないFizzBuzzを書けるんじゃないかと思ったんですが、あまりうまくいきませんでした。

それがこのコードなんですが、イメージしてたのはこういうのではなくて、caseのところになんか判定するやつを書いておいて、when のところには15, 3, 5 だけ書いておけばいいようにしたかった。

そういうのをしたかったんですが、Rubyのドキュメント読みなおしてみると、whenの引数をレシーバとして === を実行し、caseの引数を右辺値として実行した結果を評価するとのことで、caseのところにlambdaを置いておくというのではうまくいかなそうなことがわかりました。caseのパラメータをレシーバにしてくれれば良かったんですが...

それで他に手は無いかなと考えてみました。

Fixnum#===をオーバーライドした解です。 case式は非常にすっきりしていい感じです。Refinementsを使っているので、Integer#===の挙動が変わるのはこのファイルだけということも保証されてます。

でもちょっと大げさな感じなので、できればlambdaを使ってなんとかしたい。

そこでこんなふうに実装してみました。

なかなかいいですね。caseにlambda渡すのは諦めて、whenにlambdaを渡すようにしました。 でも個人的に、caseの引数に何も渡してないのが不満。lambdaにiを閉じ込めてるからいいといえばいいんですが、それならifで良くない?という気がしてしまいます。

そこで、curryメソッドを使って以下のように書き換えてみました。

caseにiを渡して評価するようにしました。Proc#=== は Proc#call と同じメソッドなので、

matcher.curry[15][i]

のように実行されているわけですね。 でも今度はcurryと書いてるのがうっとおしくなってきました。curryを使うんじゃなくて、lambdaを返すlambdaを定義してみましょうか。

whenの後ろがスッキリしました。その分lambdaの定義が読みにくくなってしまいましたが...まあいいでしょう。満足しました。

新しいFizzBuzzの書き方を思いつけて良かったです。ありがとうございました。

(2014/5/1追記)

curryの使い方を勘違いしてました。curryは引数を一つづつとるlambdaを返すんだから、予めcurryしておけばスッキリ書けた。

これでOKですね。curryを使うと、上に書いた、入れ子になってるlambdaと同等のlambdaを出力してくれる。

rubygems-bundlerのしくみ

https://github.com/mpapis/rubygems-bundler

rubygems-bundlerを入れると、bundle exec がいらなくなるということを知りました。

どうやってbundle execを省略できるようにしてるのかわからなかったので、調べてみました。

結論としては、gem がインストールした実行ファイルの shebang を書き換えることで、フックしてます。

たとえば rbenv を使ってて、rakeをインストールすると、 /home/hoge/.rbenv/versions/2.1.1/bin/rake のようなパスに実行ファイルが作られます。

このファイルのshebangは、通常

#!/usr/bin/env ruby

なんですが、gem regenerate_binstubs を実行すると、これらのファイルのshebangだけを

#!/usr/bin/env ruby_executable_hooks

と書き換えてしまいます。 この ruby_executable_hooks というのが各Gemの中に lib/rubygems_executable_plugin.rb というのを探して、あればそれを実行します。

rubygems-bundlerrubygems_executable_plugin.rb は、カレントディレクトリからルートにむかって Gemfile を探し、あればそのファイルを使って Bundler.setup を呼び出します。

こうして bundle exec をつけずに rake を実行しても、 bundle exec をつけて呼び出したのと同様の効果を実現してます。

gem regenerate_binstubsコマンドを提供している executable-hooks は、Gemがインストールされるときにフックを走らせることで、以後は、実行ファイルを提供するGemがインストールされるごとに、自動で shebang を書き換えます。

「統計学が最強の学問である」を読んだ

統計学が最強の学問である」を読んだ。

統計学が最強の学問である

統計学が最強の学問である

最近統計ブームになってきて、データ解析してますとかビッグデータ扱ってますという話をよく聞くけど、ただデータを集めて数を測定して何か分かった気になった、というようなやり方は全然金の無駄で、ちゃんと目的を設定した上でデータ集めや解析をしないといけないというようなことが書いてあった。

たとえばビジネスなら、目標は利益をあげることだから、以下の問いを立てて分析しないといけない。

  • 何かの要因が変化すれば利益が向上するのか?
  • そうした変化を起こすような行動は実際に可能なのか?
  • 変化を起こす行動が可能だとして、そのコストは利益を上回るのか?

確かにそのとおりだと思った。

必要のない正確さについて

他には、「意思決定に必要のない正確さは、無駄なコストだ」ということも書いてあった。普段、何も考えずに正確さを求めてる気がするので反省したい。

A/Bテストについて

A/Bテストのことも書いてあった。A/Bテストは統計の世界ではランダム化比較実験と呼ばれてきたものらしい。実験対象にランダムにそれぞれの方法を試すことで、比較したい両グループの諸条件がほぼ揃う。たとえば畑を40区画に分けて、肥料A、Bをそれぞれランダムな区画に散布すると、肥料Aをまいた土地が日当たりの悪い区画ばかりあたる確率は100万分の1になる。日当たりの良い区画が同数になる確率は13%、+-1まで許容すると36%、+-2まで許容すると57%になる。これは日当たり以外の水はけや土壌などの他の条件についても同じことが言える。ランダム化比較実験は複雑な要素を持ってる実験対象が相手の時に威力を発揮する。なので人間相手に実験したいときには非常に有効。

また、得られた結果から導かれた結論が、どの程度確かなのかを検証する計算も確立されていて、誤差によってその結果が導かれた可能性が5%以下なら、誤差とは考えにくいとみなすのが一般的らしい。

A/Bテストは最近記事などでよく見かけるけど、それがコンバージョン率7%から9%に上がったとして、そこから単純に優劣が決まるのか、それとも誤差のことを考えなくていいのかがよくわかってなかった。ランダム化の強力さと、誤差のことも考えて比較できるということがわかって、非常に腑に落ちた。機会があればA/Bテストを導入していきたい。

その他

統計の分析手法は一般化線形モデルというカテゴリでくくると共通化できるものが多くて、どういうケースでどの手法を使うかという表が載ってて便利。きっとデータ分析が必要になったら見直すと思う。

統計について何もわかってない状態から読んでみたけど、統計学の概観と、統計学が怪しげな学問じゃなくて、数学の裏づけがある学問だということがわかってよかった。もっと統計について勉強したい。

SexyPresenterというRails Presentater Gem作った

以前ブログに書いた、Refinementsを使ったRailsのプレゼンテーション層処理をGemにまとめた。

kmdsbng/sexy_presenter · GitHub

Gemfileに

gem "sexy_presenter"

と書いておくと使える。

このGemが提供する機能は1つだけだ。viewテンプレートの先頭にYAML形式で presenter モジュールを指定する。これだけ。

---
presenter: HeaderPresenter
---
Welcome to our web site.
Now we have <%= @customer_count %> customers. Join us!

presenterと指定したモジュールでは2つのことが書ける。

  • 既存クラスのrefineを書ける。refineで定義したメソッドは、viewテンプレートの中だけで有効化される。
  • viewテンプレート描画直前の処理を書くための before_render フックを書ける。

refineが使えるのは、単に using でモジュールをインクルードしてるから。 before_renderフックは、render :partial でviewを読み込んだ時に、データを準備したいことがよくあるのでつけた機能。結構便利。

便利な render :partial 機能に特化した CellsというGemがあるが、SexyPresenterで置き換えられると思う。

SexyPresenterはRefinementsを使ってるので、Ruby 2.1 以降でのみ使える。 また、モンキーパッチしてる関係で、現状Rails 4のみ対応してる。

SexyPresenterができてしまったので、これまで RailsのPresentation Layerライブラリとして有名だった Draper などのライブラリは、申し訳ないがすべてオワコンになってしまった。

この種のライブラリのやっていることはほぼ同じだ。モデルなどのクラスをextendしたりwrapしたりして、その場でだけ使える拡張を施す。しかしextend方式にしてもwrap方式にしても副作用があり、メンバのクラスを拡張するのが難しかったりして、痛し痒しな使い心地だった。

例えば Draper は対象モデルをPresenterでwrapするライブラリだ。Presenterでメソッドを追加し、モデルの要素にアクセスしたいときは委譲してくれる。ただ、アソシエーションまでwrapするので、生モデルと挙動が変わってしまうことがある。

ActiveDecoratorはDecoratorモジュールでモデルをextendする。extendはwrapに比べると副作用が少ないのが利点だが、モデルのメンバまではextendしてくれない。

もう一点重要な問題が、どちらの方式でも対象オブジェクトを指定するのが面倒ということだ。ActiveDecoratorはcontrollerからviewに渡される変数を勝手にextendしてくれるが、そのメンバまではextendしてくれないので、機能を使おうとするとわざわざ変数にセットしないといけなかったりして、使ってうれしいのかわからなくなってくる。しかも、これらの問題を解決するために涙ぐましい努力をしている。

だが、Rubyに Refinements が実装された現在、あるコンテキスト限定のクラスの拡張は、Refinementsを使うのが最もスマートな解法になってしまった。

Refinementsを使えば、ひとつのモジュールに、コンテキストに関連するクラスの拡張を全て記述できる。しかもusingを定義したファイルでは自動的に拡張されるため、漏れもない。作りが簡単になり、拡張のために今までやってたような苦労も必要ない。

だから、Presentation Layerを実現するのにextendしたりwrapするライブラリはもはや時代遅れになってしまった。

ではviewテンプレートを使わないケースなら使えるかというとそんなこともない。その場合は単にRefinementsを使えばいいだけなので、やはりextendやwrapは選択肢にならない。*1

まとめると、

  • Ruby2.1時代はRefinementsを使ってコンテキスト限定の拡張をするのがオシャレ。
  • SexyPresenterは今一番オシャレなイマドキRails Presentation Layer Library

ということです。

ぜひあなたのRailsアプリケーションにSexyPresenterを使ってみてください!

*1:局所的に機能を追加するというケース以外なら、もちろんextendもwrapも使えるケースはある。

徳川綱吉(と自称するハデな金色の着物のおっさん)と節分豆をぶつけあう夢を見た。

ルールがあって、それぞれ交代で攻守に分かれて、攻撃側はテーブルの向かいから

相手にむかって1個だけ好きなタイミングで豆をぶつける。

どこに当てると高得点ということはなくて、とにかく相手の体のどこかにぶつければいい。テーブルがあるので、自然上半身のどこかを狙うことになる。

攻撃するときは、相手に自分の立ち位置と投げるタイミングを悟らせないため、透明になってから投げる。守り側が透明になると、攻撃側がどこに投げればいいか分からないので、透明にはならない。

僕は何度か守りの順番で間違って透明になってしまい、素人っぷりをさらけ出して失笑を買った。敵のおっさんのほかに2人ほどギャラリーがいた。

 

僕は元野球部の技術を生かして、突如空中から現れる豆を何度も華麗にキャッチして、手練(らしい)の敵のおっさんをうならせた。

夢の中で、「節分豆は球ではないため、あまり強く投げすぎるとコントロールがつかない。なのでコントロール重視で投げるほうがよい」という知見を得ていた。

 

最後の方になると攻守が交代するたびに目の前が激しく明滅するエフェクトが入ってた。

 

目が覚めて、だいぶ疲れてると思った。