isucon #2へ参加してきました

前回非常に楽しかったので、今回も参加したいなーと思っていたのですが、「くらげとみかん」チームで、なんとか参加できました。

走り書きだけど、考えたこと、やったことをメモっておく(あとでまとめなおすかもしれない)

まずは最初に全サーバーへsshの鍵を通して、screenで全サーバーへログインした。
前回ベンチマークを走らせるのが遅かった、という反省があったので、まずベンチマークを実行した。

まずappをいじるための環境を整備した。git reposを作ったり。(11:30)

次は、アプリケーションの概要を把握するために、全tableのschemaをshow create tableしたり、全テーブルの件数を把握して、stockテーブル以外は対した容量にならない、ということを把握した。

long_query_timeを0にして、ベンチを走らせて、全クエリをslowログへ出力し、mysqldumpslowで解析した。(この時点でpt-query-digest使っておけばよかった...)

前回は、ボトルネック以外のところへ時間を消費してしまった、という反省があったので、stockテーブルへ注力してチューニングを始めた。

チケットの席を発行する処理がぱっとみ遅そうだ、ということで、mysqldのINSERTまわりの設定をチューニング。

具体的には、

  • innodb_flush_log_at_trx_commitを0に。
  • log-binを無効に

してみると少しスコアがあがった。(通常のwebサービスでは真似しないように)

alter table stock change column seat_id seat_id varchar(6) ascii binary not null;

してseat_idのデータを減らす。

deployスクリプトをかいてなかったので、appをいじるためにdeployスクリプトを書く ( 13:00ごろ)

"order by rand"しているクエリがあったので、とりあえずオフにしてベンチしてみたら通ったので、そのままにした。

alter table stock ROW_FORMAT=Compressed

してみたら少しあがった。

mysqldumpslowが見づらいので、やっぱりpt-query-digestを落としてきて実行する。

stockテーブルのupdated_atは、最後のチケットCSVをダウンロードしているページでしか参照していなくて、order_requestにあっても同じ、ということでupdated_atを移動してやる (stockテーブルよりorder_requestの方が行数がすくない)

alter table order_request add column `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;

alter table stock drop column updated_at

updateやJOINしているクエリで読む行数を減らせないかな、というので、

CREATE TABLE `stock_part` (
  `variation_id` int(10) unsigned NOT NULL,
  `seat_id` varchar(6) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `order_id` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY `variation_seat` (`variation_id`,`seat_id`)
) ENGINE=InnoDB AUTO_INCREMENT=40961 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPRESSED
PARTITION BY LINEAR HASH(variation_id)
PARTITIONS 10;
rename table stock to stock_bk, stock_part to stock;

してみた (14:43)

stockテーブルの"order_id IS NULL "や、"order_id IS NOT NULL"のクエリが重いので、
そこをなんとかしてなくせないかなーと考えていたら、updated_atと同じ容量で、order_requestへ移せばいいんじゃね?と思ったので、アプリケーションを安全に保ちつつ、徐々に移行していった。

具体的には、variation_id/seat_idをorder_requestへ追加して、二重にINSERTするようにした。

次はJOINしまくっているクエリで、stockテーブルを使っていたところをカラムを追加したことでorder_requestを代用できるようになったのでとても高速になった。 (16:12)

最後にCSVを出力する処理でもstockを使わずにorder_requestで返せるようになったので修正 (16:20 )

このごろになると、徐々にチューニングが効きはじめて、最初のボトルネックからはかわってきたので、slow queryを取得し、pt-query-digestを実行し、アプリを書きかえては、ベンチを実行して測る、というのを繰り返した

for文の中で、1クエリ実行している箇所があったため、INを使ったクエリへ書きかえた。 (17:00)

さらにfor文の中で、1クエリ実行している箇所があったのでINを使って書きかえた(17:16)

チケットの残り件数を表示している処理がボトルネックになっていたので、これを高速に返すために、チケットを売りあげたときにカウンタテーブルを更新し、チケットの残り件数は、
全チケット数 - 売りあげたチケット数、で算出する、という方式に変更
。(17:45)

ここまででタイムアップしました。

最終的にはチケットの売上枚数が少なかった(1887 ticket)ですが、スコア(260461, BEST: 235274)はけっこうよかったようです。
おそらくカウンタテーブルを用意したことで、購入処理は重くなったが、その一方でチケット残数表示は早くなったんじゃないかと思います。

他の方のまとめはこちら

http://blog.livedoor.jp/techblog/archives/67726806.html

Play Framework 2.0雑感

scala触ってみたいなぁ、とは前々から思っていつつ、なかなか触れていなかったので、えいやと最近よく聞く気がするPlay Framework 2.0のチュートリアルを触ってみたり、軽いアプリケーションを書いてみての感想。

  • APIはよくできていて、非常に綺麗なAPIをしているなと感じた。
  • エラー画面などはカッコいい
  • devモードは、書きかえるたびにコンパイルが走るので、遅い。
  • evolutionsは001.sql/002.sqlなどではダメで、1.sql/2.sqlとしなければいけないのがちょっと管理しにくそう
  • anormは、ORMではなく、SQLを直接書く感じのAPIで、JavaでDBさわるときにめんどうな型定義しまくりをしなくてよい、というのは非常に楽で便利だなと思った。
  • 一方で、今のところanormのSQLParserのAPIの文法がどういうsyntaxになっているのかが今のところさっぱりわからない。
  • テンプレートエンジンは、標準だと、Scalaの文法をそのまま埋めこめるエンジンになっていて、コンパイル時に関数としてコンパイルされ、変数の引き渡しがチェックできるのは新鮮に感じた。一方で、簡単にコンパイルエラーになるので、エンジニア以外が触るのは厳しそうだなーという印象を受けた
    • テンプレートエンジンのタグが、@から始まると、scalaのコードというようになっていて、わりとふつうの文章を書いていてもタグとして判別されるのがイケてないなと感じた。
  • Formは、optional/nonEmptyTextなどの指定が、どのモジュールで定義されているのか、自前で拡張したいときにどうすればいいのか、がイマイチわからなかった。
    • 例えば既に登録されているタイトルを入力できないようにするチェック、などのバリデーションをどう実装すればよいのかわからなかった。
  • wikiのページなどのようにURLに日本語を含むを作ろうとしてみたが、文字化けしてしまった。
    • OSXでよくある、'export JAVA_OPTS="-Dfile.encoding=UTF-8"'の問題なのかな、と思いつつ、よくわからなかった

Sphinx+Guardでファイルを変更したときにビルドしつつ、自動でブラウザをリロードする

色々な言語が混ぜまくりすぎなので、他の人も環境を用意するようなものであればあまりおすすめできない。

guardfile内でふつうにWEBRick起動させるコードを書くのがよいんかな。

source/buildするディレクトリを分ける設定であるのを前提としております。

guard-shellとguard-livereloadが必要。

# A sample Guardfile
# More info at https://github.com/guard/guard#readme

guard 'shell' do
  watch(%r{source/.+\.rst}) {
    puts "invoke sphinx build"
    Process.spawn(%|make html >/dev/null|)
  }
end

guard 'shell' do
  watch(%r{app.psgi}) # dummy
  Process.spawn(%|plackup -MPlack::App::Directory -e 'Plack::App::Directory->new({root=>"build/html/"})->to_app' -p 3000|)
end

guard 'livereload' do
  watch(%r{build/html/.+\.html})
end


追記

WEBrickを使うようにしてみました

# A sample Guardfile
# More info at https://github.com/guard/guard#readme

guard 'shell' do
  watch(%r{source/.+\.rst}) {
    puts "invoke sphinx build"
    Process.spawn(%|make html >/dev/null|)
  }
end

guard 'shell' do
  watch(%r{app.psgi}) # dummy
  Process.fork do
    require 'webrick'
    webrick = WEBrick::HTTPServer.new({
      :DocumentRoot => 'build/html/',
      :BindAddress => '127.0.0.1',
      :Port => 3000
    })
    trap("INT") { webrick.shutdown }
    webrick.start
  end
end

guard 'livereload' do
  watch(%r{build/html/.+\.html})
end

rrails v1.0.0 released.

以前公開した、rails/rakeコマンドを超早くするrrailsですが、
http://d.hatena.ne.jp/walf443/20120427/1335482420

あれから、色々な方に使っていただいているようで、パッチをいくつかもらったりしてupdateしたりしています。

最近quark-zjuさんという方からたくさんpatchをいただいて、今までできなかったことができるようになった & 今までと非互換なところがでてきた、ということで紹介します。

rails console/serverができるように

今まで子プロセスへPTYの引きつぎをしていなかったため、rails console/serverなどが実行しても、こちらの入力をrrailsへ教えることができなかったのですが、できるようになりました。

個人的にはrails consoleあんまり使わないので対応しなくてもいいかなーと思って放置していたところですが、使えるとまぁ便利かなとは思います。

rrails-serverコマンドは非推奨になり、rrails startで起動するように

guard-rrailsつかっているとあまり関係ないですが、rrails-serverコマンドはそのうちなくなると思われるので、rrails startを使うようにしてください。

TCPSocketではなく、UNIXDomainSocketをデフォルトで使うように

rrailsとの通信は今までTCPでの通信でしたが、デフォルトではUnixDomainSocketを使うようになりました。

これにより、複数のプロジェクトでのrrailsの立ちあげが楽になると思われます。
一方で、rrailsを実行する際に、対象のプロジェクトのディレクトリへcdしないと適切に実行できなくなっています。

まとめ

使い方が色々かわっている(なるべくは互換性残すようにはがんばったつもりだけど...)ので、今まで使っていた方はあらためてREADMEを読みなおすことを推奨します。
http://walf443.github.com/rrails/

https://github.com/walf443/rrails/blob/master/Changes.md

DCIについて思うこと

最近DCI(Data Context Interaction)アーキテクチャというのを良く耳にするので、ちょっと調べたり他の人のコード例などをもとにちょろっと書いてみたりしたところ、やっぱりよくわからないなーと思いつつ、つまりはこういうこと?というのをまとめてみる。

たぶん色々と大分間違っているんだろうなぁと思いつつ、間違いを指摘してもらえるとありがたいです。

  • DCIは、MVCを置きかえるものではない。MVCのModelの部分が肥大化し、管理が難しくなる傾向にある(Skinny Controller, Fat Model)ので、そこをうまく扱うにはこう考えて分割すればよろしい、という考え方。つまり、M(D-C-I) - Controller - Viewみたいに書くことになる。
  • 同じモデルでも、ユーザーの役割によって、振舞いが異なることがある
    • 例えば、管理者(Administrator)には見えてよいが、閲覧者(Viewer)には見えてはいけないコンテンツ(Content)
  • 振舞いが異なるものを1つのモデルにして管理しようとすると、混乱が生じる
    • 例えば、Contentに、AdministratorおよびViewerのものをごっちゃに記述すると、Administrator権限のものをViewerで呼んでしまうようなミスが発生しやすくなる
  • そこで、コントローラーからはそのコンテキストに応じたモデルを呼びだすようにしよう
    • 例えば、ViewingContentクラスへViewerがContentを扱う際の情報を記述し、ControllerからはViewerの文脈であれば、ViewingContentを呼びだすようにする。

DCIで検索してみて出てくるコードを参考に書いたりしつつ、疑問に思ったことなど。

  • DCIという名前は個人的にはわかりづらくてMVCほど直感的には感じない気がする
    • コード例をみてみるに、ContextはUseCase /InteractionはRoleと記述されていることが多くて、私もUseCase/Roleの方がわかりやすいなと思う。ただし、Roleのクラス名をうまく考え出すことが難しくてユーザーに関連するようなケース以外だとあまりロールなしでUseCaseだけでよいのではないかと感じた。
  • コード例をみると、UseCaseはModelとRoleを紐づけることだけに専念するように書かれているが、サンプルが簡単な例だからか無駄にレイヤーが増えているだけで個人的にはあまりわかりやすくなったようには感じない。
  • コンテキストというのはユーザーの役割に応じて発生するものと思われるので、誰に対しても同じような振舞いをするものはあえてコンテキストを書く必要はない気もする
    • 実際はそういうものが後々複雑になったりしがちなのがではあるので、一枚噛ませておくのはいいと思う。
  • こういうときに個人的にはDCIで書くのがよさそうだなーという例
    • Userモデル (同じユーザーでも文脈に応じて違うクラスとして振る舞うように書くと便利そう)
    • 公開/非公開などのビジネスロジックを持つモデル
      • 非公開のものを誤って公開することがないように、コンテキストをコードへうまく表現できるとミスが減る

とはいえ、もっとコード書いてみないと、なんともいえないなーという感じではあります。
DCIでバリバリ書かれている本格的なアプリケーションで良い例とかあったりするのかな...

参考: http://d.hatena.ne.jp/digitalsoul/20100131/1264925022

gitのtagを元にChangeLogっぽいものを出力する

ChangeLogはリリースのたびに見直して手で丁寧に書いておく方が利用者にとってはうれしいことが多いですが、一方でまとめなおし作業はまぁまぁめんどいです。

そこで、手直しするにしてもそれまでの情報をコミットログから雛形を自動生成してやれないかなーとおもってつくってみました。

https://github.com/walf443/dotfiles/blob/master/bin/git-changelog

基本的には標準出力に出すので、自動生成して、git add -pしてひとまず欲しいところだけ取りこんで、記述を微調整すればよいのかなー、という感じです。

生成例はこんな感じ

https://github.com/walf443/seqdiag.js/blob/master/Changes.md

ChangeLogのないライブラリの更新がどんな感じかなーと眺めたりするのにもまずまず便利です。(ただしcloneしてくる必要がありますが)

git log --pretty=formatだけで頑張れないかなと思いつつ、プログラム書いた方が早そうだった。(眺めるだけならそれで十分かも

適度に実行時間を抑制するrate_throttle

cronなどで統計処理のSQLをたくさん投げたりするときに、mysqlにあまり負荷をかけたくないので、適度にsleepを挟みながら実行させる、という処理を書くときに、perlだと、Sub::Throttleというid:kazuhookuさんのライブラリを使っていたのですが、rubyで同様のライブラリを探すとなかったので、rubyrate_throttleという名前で移植しました。(本当はProc::Throttleにするつもりだったのですが、既にあったので....)

require 'rate_throttle'

1000.times do
    # limit block to 10% workload.
    RateThrottle.throttle(0.1) do
        # do something.
    end
end

こんな感じで書くと、ブロックを実行した後にその実行時間が10%になるようにsleepを入れてくれます。ブロック内の実行時間が1秒であれば、sleepは9秒で、合計10秒になる、という塩梅です。

crawlerとかを書いているときに適度にsleepを挟むといったときなどにも使えるのではないでしょうか。

参考: http://labs.cybozu.co.jp/blog/kazuho/archives/2008/08/sub-throttle.php