Hateburo: kazeburo hatenablog

SRE / 運用系小姑 / Goを書くPerl Monger

POSIX::tzset and Windows

When changing timezone in perl script. POSIX::tzset is required.

local $ENV{TZ} = 'Asia/Tokyo';
POSIX::tzset();
localtime();


But Windows does not support this.

  • old Windows dies with "not implemented" error.
  • newer Windows does not die. But timezone is not changed. tzset is supported only for subprocess


If you want to use tzset in tests. it's recommended to skip on windows like these.

eval {
    POSIX::tzset;
    die q!tzset is implemented on this Cygwin. But Windows can't change tz inside script! if $^O eq 'cygwin';
    die q!tzset is implemented on this Windows. But Windows can't change tz inside script! if $^O eq 'MSWin32';
};
if ( $@ ) {
    plan skip_all => $@;
}

see http://api.metacpan.org/source/KAZEBURO/Apache-LogFormat-Compiler-0.22/t/04_tz.t

Starlet / How to listen to Unix Domain Socket without Server::Starter

Define $ENV{SERVER_STARTER_PORT} in your script.

if (-S $socket) {
    warn "removing existing socket file:$socket";
    unlink $socket
        or die "failed to remove existing socket file:$socket:$!";
}
unlink $socket;
my $sock = IO::Socket::UNIX->new(
    Listen => Socket::SOMAXCONN(),
    Local  => $socket,
) or die "failed to listen to file $socket:$!";
$ENV{SERVER_STARTER_PORT} = $socket."=".$sock->fileno;

my $loader = Plack::Loader->load(
    'Starlet',
    max_workers => 10,
);
$loader->run($app);

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がリリースされました。

Starlet-0.21 - a simple, high-performance PSGI/Plack HTTP server - metacpan.org - Perl programming language

このバージョンから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で接続するよりもよいベンチマーク結果が得られます。

f:id:kazeburo:20131126002528p:plain

このベンチマークは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