Amon2でKossy::Request/Responseを使う
使う事があるかどうかは置いといて、使えた
package MyApp::Web; .. # request/response use Kossy::Request; use Kossy::Response; sub create_request { Kossy::Request->new($_[1]) } sub create_response { shift; Kossy::Response->new(@_) }
メリット?
- URL::Encode::XS / HTTP::Headers::Fast による高速化
- application/jsonの自動decodeが使える
- uri_forがある
デメリット?
データベースのmasterとslaveの使い分けの話。2014年版
社内で少し話題になったので。
運用上の話はfujiwaraさんの
MySQLをmaster:slave=1:1構成にして参照をslaveに向けるのがなぜ良くないか - 酒日記 はてな支店
MySQLで参照の負荷分散を行うslaveは3台から構成するのがよいのでは - 酒日記 はてな支店
をみてください。
最近、新しくサービスができたり、新規機能でデータベースを追加する際には必ず全ての参照をmasterに向けてもらっています。理由は上記のエントリを読んでください。このような構成が取れるのはもちろん性能的にそれで問題ないからです。
新しいハードウェアに、設定されたMySQL、問題のないように書かれたSQLであれば、数千QPSは余裕に、また少し頑張れば数万QPSを一台で賄えます。なので大体のサービスはmaster一台で十分です。
さらにこの考え方を進めて、Webアプリケーションの中で
sub dbh { DBI->connect("MASTER"); } sub dbh_slave { my $dbh; for my $dsn ([MASTER]) { #masterを使ってる $dbh = DBI->connect($dsn); last if $dbh; } } sub controller_post { $c->dbh->do("insert ..") } sub controller_get { $c->dbh_slave->selectrow..("select ..") }
のようにしているところのdbs_slaveが要らないんじゃないかと考えています。dbmとdbs_slaveの使い分けを考えてコード書くの、ちゃんとやると面倒ですよね。
sub dbh { DBI->connect("MASTER"); } sub controller_post { $c->dbh->do("insert ..") } sub controller_get { $c->dbh->selectrow..("select ..") }
シンプルになるのでサービスの構築も速くなるでしょう。
んで、サービスがかなりヒットして、master一台ではつらいという嬉しい悲鳴があがった時に、初めてdbs_slaveを足して、「スイートスポットだけ」をslave参照にします。
sub dbh { DBI->connect("MASTER"); } sub dbh_slave { my $dbh; for my $dsn ([SLAVES]) { $dbh = DBI->connect($dsn); last if $dbh; } } sub controller_post { $c->dbh->do("insert ..") } sub controller_get { # ここアクセスが多いからslaveを参照する! $c->dbh_slave->selectrow..("select ..") }
実際どこをslave参照にするかは、ちゃんと計測して行うといいですね。
また、こんな感じでシステムを作る事で、参照をslaveに向けた時に起きるであろう、レプリケーション遅延による様々な問題も回避しやすいんじゃないかなと思います。
Starlet + Server::Stater で UNIX domain socketに対応しました
Starlet-0.21がリリースされました。
このバージョンからServer::Staterとの組み合わせでUNIX domain socketをListenすることができるようになりました。
$ start_server --path /tmp/app.sock -- plackup -s Starlet app.psgi
Server::Stater は以前からUNIX domain socketをサポートしていたのですが、Starlet側でうまくハンドリングできていなかったのを修正したのが今回のリリースです。
ベンチマーク
#plackconでの発表にもある通り、UNIX domain socketを使うとTCPで接続するよりもよいベンチマーク結果が得られます。
このベンチマークはEC2のm3.2xlargeを使い、nginxとStarletを以下のように起動してwrkを使って計測しました。
wrkの起動はこんな感じ。
$ ./wrk -c 300 -t 4 -d 10 http://localhost:8080/
TCPの場合
Starletの起動
$ start_server --port 5000 -- carton exec -- plackup -E production \ -s Starlet --max-reqs-per-child 5000 --min-reqs-per-child 4000 \ --max-workers 10 -a app.psgi
nginx.conf
upstream apps { server 127.0.0.1:5000; } server { listen 8080; server_name _; etag off; location / { proxy_set_header Host $host; proxy_pass http://apps; } }
UNIX domain socketの場合
Starletの起動
$ start_server --path /tmp/app.sock -- carton exec -- plackup -E production \ -s Starlet --max-reqs-per-child 5000 --min-reqs-per-child 4000 \ --max-workers 10 -a app.psgi
nginx.conf
upstream apps { server unix:/tmp/app.sock; } server { listen 8080; server_name _; etag off; location / { proxy_set_header Host $host; proxy_pass http://apps; } }
どうぞご利用くださいませ
ISUCON3 予選のAMIでスコア 65000点以上を出す方法 #isucon
ISUCON3 の予選AMIが公開されてから、ごにょごにょとHackした結果、スコアで65000まで出す事ができました。(一回だけ66000でけどたぶんインスタンスガチャ)
「ISUCON 本戦出場者決定のお知らせ」をみると予選の時のトップのスコアが3.3万(自分たちは1.5万ぐらい)、その後の解説記事「ざっくりと #isucon 2013年予選問題の解き方教えます」だと、5.6万という話でしたので、その数字よりも良いスコアを出すことができました。
んで、本選終わったのでそのソースコード公開しました。
https://github.com/kazeburo/isucon3qualifier-myhack
構成はnginx + perlです。主なポイントは
- セッションに直接ユーザIDを埋める
- / と /recent は静的にHTMLを書き出して、nginxのssiを使ってセッション情報を埋める。
- 初期化時とmemoのPOST時に、memcachedにJSONでキャッシュする
- /memo はluaでmemcachedにアクセスして組み立てる
- 改造Starlet使って、nginxからunix domain socketでproxy
といったあたり。ベンチマークはworkload 2よりも大きくすると遅くなります
$ sudo isucon3 benchmark --init /home/isucon/webapp/perl/conf/init.sh --workload 2 2013/11/11 14:49:42 <<<DEBUG build>>> 2013/11/11 14:49:42 benchmark mode 2013/11/11 14:49:42 initialize data... 2013/11/11 14:49:57 run /home/isucon/webapp/perl/conf/init.sh timeout 60 sec... 2013/11/11 14:50:32 done 2013/11/11 14:50:32 sleeping 5 sec... 2013/11/11 14:50:37 run benchmark workload: 2 2013/11/11 14:51:38 done benchmark Result: SUCCESS RawScore: 65508.9 Fails: 0 Score: 65508.9
最後まで @methane さんの70kには届かなかったのが残念
How to automate CPAN.pm configuration on Travis
I want to automate CPAN.pm configuration on Travis.
I wrote .travis.yaml like this
language: perl before_install: - cpanm -n CPAN - (echo y;echo y;echo o conf commit)|cpan - cpanm -n Module::Install Module::Install::Repository Module::Install::AuthorTests Module::Install::CPANfile perl: - 5.18 - 5.16 - 5.14
Kossy-0.24 Trial has been released. It contains new BodyParser
I released Kossy-0.24-Trial. It contains new request-body parser Kossy::BodyParser.
https://metacpan.org/release/KAZEBURO/Kossy-0.24-TRIAL
Kossy::BodyParser is based on tokuhirom's Plack::BodyParser
https://github.com/plack/Plack/pull/434
I think that this trial version of Kossy is positioned as verification of the next Plack.
Benchmark
package MyApp; use strict; use warnings; use Kossy; get "/" => sub { my ( $self, $c ) = @_; $c->response->body("ok"); }; router [qw/GET POST/] => "/bench" => sub { my ( $self, $c ) = @_; my $param = $c->req->param('q'); $c->response->body("ok => " . $param); }; package main; use HTTP::Request::Common; use HTTP::Message::PSGI; use Plack::Test; use Plack::Builder; use Benchmark qw/cmpthese timethese/; my $env = req_to_psgi(GET '/'); my $env_get = req_to_psgi(GET '/bench?q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome'); my $env_post = req_to_psgi(POST '/bench', [ q=>'HTTP::Request::Common', ie => 'UTF-8', sourceid => 'chrome' ] ); my $app = MyApp->psgi; warn $Kossy::VERSION; cmpthese(timethese(0,{ 'kossy_noparam' => sub { $app->($env); }, 'kossy_get' => sub { $app->($env_get); }, 'kossy_post' => sub { $app->($env_post); }, }));
Results
0.23
Benchmark: running kossy_get, kossy_noparam, kossy_post for at least 3 CPU seconds... kossy_get: 4 wallclock secs ( 3.26 usr + 0.00 sys = 3.26 CPU) @ 11825.46/s (n=38551) kossy_noparam: 3 wallclock secs ( 3.23 usr + 0.00 sys = 3.23 CPU) @ 12707.43/s (n=41045) kossy_post: 3 wallclock secs ( 3.02 usr + 0.00 sys = 3.02 CPU) @ 11577.81/s (n=34965) Rate kossy_post kossy_get kossy_noparam kossy_post 11578/s -- -2% -9% kossy_get 11825/s 2% -- -7% kossy_noparam 12707/s 10% 7% --
0.24
Benchmark: running kossy_get, kossy_noparam, kossy_post for at least 3 CPU seconds... kossy_get: 3 wallclock secs ( 3.21 usr + 0.00 sys = 3.21 CPU) @ 12763.24/s (n=40970) kossy_noparam: 3 wallclock secs ( 3.11 usr + 0.00 sys = 3.11 CPU) @ 13610.29/s (n=42328) kossy_post: 3 wallclock secs ( 3.29 usr + 0.00 sys = 3.29 CPU) @ 12449.85/s (n=40960) Rate kossy_post kossy_get kossy_noparam kossy_post 12450/s -- -2% -9% kossy_get 12763/s 3% -- -6% kossy_noparam 13610/s 9% 7% --
アプリケーションに手を入れずに #isucon 2013 予選のperlアプリケーションのスコアをあげてみようの巻
セッション管理モジュールやルーティングライブラリが速くなる事で何もしなくても isucon3 予選のperlのアプリケーションが高速化していく
— masahiro nagano (@kazeburo) October 10, 2013
ということで、やってみた。
初期
「オンライン予選で使用した問題が手元で再現できるAMIを公開しました」に書かれているAMIを使って起動し、まず、初期状態でのベンチマークを取ってみる。
2013/10/28 14:21:18 done benchmark Result: SUCCESS RawScore: 865.9 Fails: 0 Score: 865.9
disble?
たぶんミスだと思うのですが、Starmanの起動オプションがdisbleになっていたので修正
- command=/home/isucon/env.sh carton exec -- plackup -s Starman --workers 10 --port 5000 -E production --disble-keepalive app.psgi + command=/home/isucon/env.sh carton exec -- plackup -s Starman --workers 10 --port 5000 -E production --disable-keepalive app.psgi
supervisorctl reloadしてベンチマークを動かすとかなり結果がよくなった
2013/10/28 14:24:15 done benchmark Result: SUCCESS RawScore: 1857.9 Fails: 0 Score: 1857.9
Monoceros & OpenFileCache
次にこのエントリで紹介した、Monoceros と Plack::Middleware::Static::OpenFileCacheをいれる。
diff --git a/app.psgi b/app.psgi index 2fb2ba5..220fd06 100644 --- a/app.psgi +++ b/app.psgi @@ -13,7 +13,7 @@ my $root_dir = File::Basename::dirname(__FILE__); my $app = Isucon3::Web->psgi($root_dir); builder { enable 'ReverseProxy'; - enable 'Static', + enable 'Static::OpenFileCache', path => qr!^/(?:(?:css|js|img)/|favicon\.ico$)!, root => $root_dir . '/public'; enable 'Session', diff --git a/cpanfile b/cpanfile index 25ca66c..21fe478 100644 --- a/cpanfile +++ b/cpanfile @@ -9,3 +9,11 @@ requires "DBD::mysql"; requires "Starman"; requires "Plack::Session"; requires "Cache::Memcached::Fast"; + +requires "Monoceros", 0.26; +requires "EV"; +requires "Guard"; +requires "Sys::Sendfile"; +requires "Linux::Socket::Accept4"; +requires "Plack::Middleware::Static::OpenFileCache";
HelloWorldなベンチマークではStarmanよりMonocerosの方が速いっていうのと、sendfileによって静的画像のレスポンスがよくなるのを狙った。
supervisord.condを書き換えて
- command=/home/isucon/env.sh carton exec -- plackup -s Starman --workers 10 --port 5000 -E production --disable-keepalive app.psgi + command=/home/isucon/env.sh carton exec -- plackup -s Monoceros --workers 10 --port 5000 -E production --disable-keepalive app.psgi
2013/10/28 14:35:00 done benchmark Result: SUCCESS RawScore: 1925.2 Fails: 0 Score: 1925.2
ちょっとスコアあがった。
Starletでも同程度になったので、sendfileは影響ない感じでした。
Kossy 0.23
Kossy 0.23でuri_forとResponse->finalizeの最適化が入っているのでアップデート
diff --git a/cpanfile b/cpanfile index 21fe478..78d7bf9 100644 --- a/cpanfile +++ b/cpanfile @@ -1,4 +1,4 @@ -requires "Kossy", 0.19; +requires "Kossy", 0.23; requires "DBIx::Sunny"; requires "JSON::XS"; requires "Digest::SHA";
また少しスコアあがる
2013/10/28 14:38:03 done benchmark Result: SUCCESS RawScore: 2012.7 Fails: 0 Score: 2012.7
Plack::Middleware::Session::Simple
ここで紹介した、セッション周りの改善。keep_emptyを無効にして空のセッションを保存しない、Set-Cookieを行わないようにした
diff --git a/app.psgi b/app.psgi index 220fd06..2c98b6a 100644 --- a/app.psgi +++ b/app.psgi @@ -16,16 +16,12 @@ builder { enable 'Static::OpenFileCache', path => qr!^/(?:(?:css|js|img)/|favicon\.ico$)!, root => $root_dir . '/public'; - enable 'Session', - store => Plack::Session::Store::Cache->new( - cache => Cache::Memcached::Fast->new({ - servers => [ "localhost:11211" ], - }), - ), - state => Plack::Session::State::Cookie->new( - httponly => 1, - session_key => "isucon_session", - ), + enable 'Session::Simple', + store => Cache::Memcached::Fast->new({ + servers => [ "localhost:11211" ], + }), + httponly => 1, + cookie_name => "isucon_session", + keep_empty => 0, ; $app; }; diff --git a/cpanfile b/cpanfile index 78d7bf9..78d3475 100644 --- a/cpanfile +++ b/cpanfile @@ -15,5 +15,5 @@ requires "EV"; requires "Guard"; requires "Sys::Sendfile"; requires "Plack::Middleware::Static::OpenFileCache"; +requires "Plack::Middleware::Session::Simple";
もうちょいスコア増えた。
2013/10/28 14:46:10 done benchmark Result: SUCCESS RawScore: 2131.1 Fails: 0 Score: 2131.1