「AnyEvent超速分散ダウンローダー」 vs 「wget + fastest server」

http://blog.livedoor.jp/dankogai/archives/51500176.html をみて v0.02 を実行してみた

tr;dr

  • 分割されたファイルの連結に時間がかかる
  • wget + 一番速いミラーを選ぶのがベスト
  • 定期的に実行して普段使うミラーのベンチを取るのに役立つ(?)

結果

AE分散 on ローカル
$ time perl download.pl ubuntu.yml http://releases.ubuntu.com/10.10/ubuntu-10.10-desktop-i386.iso
# Stage 0: checking headers
Status:         200
Elapsed:        0.7761390209198 sec.
Last-Modified:  Thu, 07 Oct 2010 16:25:11 GMT
Content-Length: 726827008
URLs:
  http://ubuntutym2.u-toyama.ac.jp/ubuntu/10.10/ubuntu-10.10-desktop-i386.iso
  http://ftp.jaist.ac.jp/pub/Linux/ubuntu-releases/10.10/ubuntu-10.10-desktop-i386.iso
  http://releases.ubuntu.com/10.10/ubuntu-10.10-desktop-i386.iso
  http://www.ftp.ne.jp/Linux/packages/ubuntu/releases-cd/10.10/ubuntu-10.10-desktop-i386.iso
  http://ftp.riken.jp/Linux/ubuntu-iso/CDs/10.10/ubuntu-10.10-desktop-i386.iso
  http://ubuntu.mithril-linux.org/releases/10.10/ubuntu-10.10-desktop-i386.iso
  http://ftp.yz.yamagata-u.ac.jp/pub/linux/ubuntu/releases/10.10/ubuntu-10.10-desktop-i386.iso
# Stage 1: fetching by pieces
Elapsed: 104.993345022202 sec.
   7 http://ubuntutym2.u-toyama.ac.jp/ubuntu/10.10/ubuntu-10.10-desktop-i386.iso
 151 http://ftp.jaist.ac.jp/pub/Linux/ubuntu-releases/10.10/ubuntu-10.10-desktop-i386.iso
   7 http://releases.ubuntu.com/10.10/ubuntu-10.10-desktop-i386.iso
 217 http://www.ftp.ne.jp/Linux/packages/ubuntu/releases-cd/10.10/ubuntu-10.10-desktop-i386.iso
 101 http://ftp.riken.jp/Linux/ubuntu-iso/CDs/10.10/ubuntu-10.10-desktop-i386.iso
 116 http://ubuntu.mithril-linux.org/releases/10.10/ubuntu-10.10-desktop-i386.iso
  95 http://ftp.yz.yamagata-u.ac.jp/pub/linux/ubuntu/releases/10.10/ubuntu-10.10-desktop-i386.iso
# Final Stage: gathering pieces
Elapsed: 113.27496099472 sec.

real	3m39.208s
user	0m19.649s
sys	0m25.834s
AE分散 on さくらVPS
$ time perl download.pl ubuntu.yml http://releases.ubuntu.com/10.10/ubuntu-10.10-desktop-i386.iso
# Stage 0: checking headers
Status:         200
Elapsed:        1.69118595123291 sec.
Last-Modified:  Thu, 07 Oct 2010 16:25:11 GMT
Content-Length: 726827008
URLs:
  http://ubuntutym2.u-toyama.ac.jp/ubuntu/10.10/ubuntu-10.10-desktop-i386.iso
  http://ftp.jaist.ac.jp/pub/Linux/ubuntu-releases/10.10/ubuntu-10.10-desktop-i386.iso
  http://releases.ubuntu.com/10.10/ubuntu-10.10-desktop-i386.iso
  http://www.ftp.ne.jp/Linux/packages/ubuntu/releases-cd/10.10/ubuntu-10.10-desktop-i386.iso
  http://ftp.riken.jp/Linux/ubuntu-iso/CDs/10.10/ubuntu-10.10-desktop-i386.iso
  http://ubuntu.mithril-linux.org/releases/10.10/ubuntu-10.10-desktop-i386.iso
  http://ftp.yz.yamagata-u.ac.jp/pub/linux/ubuntu/releases/10.10/ubuntu-10.10-desktop-i386.iso
# Stage 1: fetching by pieces
Elapsed: 89.7977340221405 sec.
  14 http://ubuntutym2.u-toyama.ac.jp/ubuntu/10.10/ubuntu-10.10-desktop-i386.iso
 112 http://ftp.jaist.ac.jp/pub/Linux/ubuntu-releases/10.10/ubuntu-10.10-desktop-i386.iso
   4 http://releases.ubuntu.com/10.10/ubuntu-10.10-desktop-i386.iso
 294 http://www.ftp.ne.jp/Linux/packages/ubuntu/releases-cd/10.10/ubuntu-10.10-desktop-i386.iso
 103 http://ftp.riken.jp/Linux/ubuntu-iso/CDs/10.10/ubuntu-10.10-desktop-i386.iso
 136 http://ubuntu.mithril-linux.org/releases/10.10/ubuntu-10.10-desktop-i386.iso
  31 http://ftp.yz.yamagata-u.ac.jp/pub/linux/ubuntu/releases/10.10/ubuntu-10.10-desktop-i386.iso
# Final Stage: gathering pieces
Elapsed: 30.602970123291 sec.

real	2m2.262s
user	0m8.089s
sys	0m30.162s
wget + fastest mirror on さくらVPS

ダウンロード終了後は各URLから何チャンクダウンロードしたかが表示されます。ネットワーク的に遠いサーバーは遅い分それだけ少ないことがきちんと見て取れます。

とあるので、

 294 http://www.ftp.ne.jp/Linux/packages/ubuntu/releases-cd/10.10/ubuntu-10.10-desktop-i386.iso

を選ぶ。

$ time wget http://www.ftp.ne.jp/Linux/packages/ubuntu/releases-cd/10.10/ubuntu-10.10-desktop-i386.iso
--2010-11-20 08:33:00--  http://www.ftp.ne.jp/Linux/packages/ubuntu/releases-cd/10.10/ubuntu-10.10-desktop-i386.iso
Resolving www.ftp.ne.jp... 192.26.91.193, 2001:200:601:10:206:5bff:fef0:466c
Connecting to www.ftp.ne.jp|192.26.91.193|:80... connected.
HTTP request sent, awaiting response... 302 Found
Location: http://ftp-srv2.kddilabs.jp/Linux/packages/ubuntu/releases-cd/10.10/ubuntu-10.10-desktop-i386.iso [following]
--2010-11-20 08:33:00--  http://ftp-srv2.kddilabs.jp/Linux/packages/ubuntu/releases-cd/10.10/ubuntu-10.10-desktop-i386.iso
Resolving ftp-srv2.kddilabs.jp... 202.255.47.226
Connecting to ftp-srv2.kddilabs.jp|202.255.47.226|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 726827008 (693M) [application/octet-stream]
Saving to: `ubuntu-10.10-desktop-i386.iso.3'

100%[=============================================================>] 726,827,008 18.9M/s   in 36s     

2010-11-20 08:33:36 (19.4 MB/s) - `ubuntu-10.10-desktop-i386.iso.3' saved [726827008/726827008]


real	0m35.877s
user	0m0.264s
sys	0m8.441s

うーむ。。。


HTML::Entities::Recursive というモジュールを書いた

https://metacpan.org/module/HTML::Entities::Recursive

HTML::Entities::Recursive

Encode / decode strings of complex data structure with HTML entities recursively

SYNOPSIS

    use HTML::Entities::Recursive;
    my $recursive = HTML::Entities::Recursive->new;

    my $foo = {
        text => '<div></div>',
    };

    my $bar = $recursive->encode_numeric($foo);
    print $bar->{text}; # prints '&lt;div&gt;&lt;/div&gt;'

DESCRIPTION

HTML::Entities::Recursive provides API to encode / decode strings of complex data structure with HTML entities recursively.

To avoid conflicting with HTML::Entities' functions, HTML::Entities::Recursive is written in OO-style.

There is no function to be exported.

To proxy content provider's API, we sometimes want to bulk decode and encode complex data structure that contains escaped strings with untrustworthy way. (yes, response from Twitter API :)

HTML::Entities::Recursive helps you to make output safe with ease.

METHODS

  • new

new() takes no argument.

  • encode( $structure )
  • encode( $structure, $unsafe_chars )
  • encode_numeric( $structure )
  • encode_numeric( $structure, $unsafe_chars )
  • decode( $structure )

http://search.cpan.org/perldoc?HTML::Entities refers to $unsafe_chars.

Methods correspond to HTML::Entities' functions but the first argument can be hashref, arrayref or scalarref.

Internally, all corresponding functions are called in array context. So you cannot use those methods in void context.

That is,

  • Use methods in scalar or array context.
  • Methods do not modify original structure.

使いどころ

perldoc にもあるように、 Twitter からのレスポンスをまとめて (en|de)code かけるときに役に立ちます

再帰

再帰に関しては http://search.cpan.org/perldoc?Sub::Recursive というモジュールがあるのでそれを使ってます

注意点

HTML::Entities は渡した引数を直接書き換えたりできるのですが、このモジュールではそういった使い方はできません。元のデータは変更されないので戻り値を使う。


Google Reader で 選択中の記事をバックグラウンドで開く on Firefox

このぐりもんを使うとできる

http://sunnywu.net/2007/08/02/google-reader-tweak-open-links-in-background


Net::Amazon の謎

Amazon のキーワード検索を非同期で使いたいと思い立ってあれこれ考えた

worker で AE::T::S を使っているので、非同期で書けないとツイートが詰まる。

AE::Net::Amazon 的なモジュールが無いので、 Net::Amazon を使って Amazon にリクエストするサーバを別に立てて AE::HTTP からそのサーバに対してリクエストを投げれば非同期でデータ取得できる、という結論にいたったが、実際に書いてみたら 900MB の swap までも使い果たしてまともにレスポンスができる状況でないほどにメモリを大量に食った。(サーバは Starman, Starlet ともに試した)

コード

こんな感じでリクエストを受け、レスポンスを受け取り、 JSON にして返す。

途中で深追いするのはやめたのであまり詳しくは追ってないけれど何かがおかしいみたい。

sub amazon {
    my $env = shift;
    my $request = Plack::Request->new($env);

    my $keyword = $request->param('keyword');

    unless ($keyword) {
        return [500, [], ['']];
    }

    my $ua = Net::Amazon->new(
        locale => 'jp',
        %$amazon_key,
    );

    my $req = Net::Amazon::Request::Blended->new(
        page => 1,
        blended => $keyword,
    );

    my $res = $ua->request($req);

    unless ($res->is_success) {
        return [500, [], ['']];
    }

    my $hash = {};
    for my $prop ($res->properties) {
        if (my $rank = $prop->SalesRank) {
            $hash->{$rank} = {
                name => $prop->ProductName,
                price => $prop->OurPrice,
                asin => $prop->ASIN,
                image => $prop->ImageUrlSmall,
            };
        } else {
            #noop;
        }
    }

    my %result;
    for my $rank (sort {$a <=> $b} keys %$hash) {
        $result{$rank} = {
            name => $hash->{$rank}{name},
            price => $hash->{$rank}{price},
            asin  => $hash->{$rank}{asin},
            image => $hash->{$rank}{image}
        };
    }

    return [200, ['Content-Type' => 'application/json'], [JSON::encode_json(\%result)]];

};

worker 側で処理するようにした。

結局は自前でリクエスト生成部分を書いた

モジュール化したいけれど Amazon のドキュメントがイマイチすぎて API 仕様を把握しきれないので悩ましい上に、 Net::Amazon を非同期にするのは一人でやるには量が多すぎる

最終的にはこんな風に書けばレスポンス帰ってくる。

sub search_amazon {
    my %args = @_;

    my $keywords = $args{keywords};
    my $type     = 'Blended'; #$args{type};
    my $page     = 1;         #$args{page};

    my %req = (
        Operation     => 'ItemSearch',
        SearchIndex   => $type,
        ResponseGroup => 'Images,ItemAttributes',
        Keywords      => $keywords,
        ItemPage      => $page,

        Service        => 'AWSECommerceService',
        AWSAccessKeyId => $amazon_config->{token},
        Version        => '2009-03-31',
        Timestamp      => POSIX::strftime("%Y-%m-%dT%TZ", gmtime),
    );

    my $uri  = URI->new('http://ecs.amazonaws.jp/onca/xml');

    my $qstr = join '&', map { "$_=" . URI::Escape::uri_escape_utf8($req{$_}, "^A-Za-z0-9\-_.~")} sort keys %req;
    my $signme = join "\n", "GET", $uri->host, $uri->path, $qstr;

    my $sig = Digest::SHA::hmac_sha256_base64($signme, $amazon_config->{secret_key});
    $sig .= '=' while length($sig) % 4;
    $sig = URI::Escape::uri_escape_utf8($sig, "^A-Za-z0-9\-_.~");

    $qstr .= "&Signature=$sig";
    $uri->query($qstr);

    AnyEvent::HTTP::http_get $uri->as_string, sub {
        say Dumper \@_;
        return if ($_[1]->{Status} ne '200');

        my $res = XML::Simple::XMLin($_[0], SuppressEmpty => undef, ForceArray => ['Item']);
        say Dumper $res;

        my $data = {};
        my $count = 0;
        for my $item (@{$res->{Items}{Item}}) {
            ...
        }
    };

}

Plack::Builder についてちょっと調べてみた(2)

前回のエントリにいただいたコメントとどうも話がかみ合わないなぁと思っていたら勘違いしてました。すみませんすみません。

#perl-casual で聞いてみたところ、 「mount は複数の PSGI アプリケーションを mount できるが、dispatcher の代替として使うことは意図したものではない」つまり、前の記事の例は mount するには小さすぎた。

http://blog.livedoor.jp/tokuhirom/archives/50184262.html のように、MyApp->to_app() を mount してやるのが目的、とのこと。

今回はとりあえずここまで。


Plack::Builder についてちょっと調べてみた

Plack::Builder は Middleware をラップしてくれたり、 Plack::App::URLMap を使ったマッピングを担ってくれる。

通常は次のように使う

use Plack::Builder;
use Plack::Session::Store::File;

my $app  = sub { ... };
my $app2 = sub { ... };

builder {
    enable "Session",
        store => Plack::Session::Store::File->new(...);

    mount "/foo" => $app2;
    mount "/"    => $app;
};

Twitter の OAuth をサービスのログインに使いたいとする。この場合、コールバックURLを指定する必要があるが、ログイン処理は /login 以下にまとめるのが良いだろう。

例えば、

http://localhost:5000/login
http://localhost:5000/login/callback

のようなパスにマップしたいときは、次のように builder をネストさせる

use Plack::Builder;
use Plack::Session::Store::File;

builder {
    enable "Session",
        store => Plack::Session::Store::File->new(...);

    mount '/login' => builder {
        mount '/callback' => sub {
            my $env = shift;
            return [200, [], ["in callback"]];
        };

        mount '/' => sub {
            my $env = shift;
            return [200, [], ['<a href="...">Login</a>']];
        };
    };

    mount '/' => sub { ... };

};

この例では "/login" にアクセスすると、 mount された "/login" の中で mount された "/" が呼ばれる。

"/login/callback" にアクセスすると、 mount された "/login" の中で mount された "/callback" が呼ばれる。

builder の注意点

  • mount を一度でも使った場合は、 fallback として "/" を mount する必要がある($app を最後に書くだけではダメ)
  • mount の第一引数は "/" から始まり、前方一致で判定される。ただし、最初の "/" から次の "/" までは完全一致している必要がある。
    • この例の場合、 "/loginx" にアクセスされた場合は一番外側の builder で mount された "/" に fallback する。
    • "/login/callbackx" にアクセスされた場合は mount された "/login" の中で mount された "/" に fallback する。

$env の注意点

$env->{PATH_INFO} と $env->{SCRIPT_NAME} は、 Plack::App::URLMap によって、その coderef の位置する部分に適する形に書き換える。

つまり、

(snip)
    mount '/login' => builder {
        mount '/callback' => sub {
            my $env = shift;

            # $env->{PATH_INFO} eq ''
            # $env->{SCRIPT_NAME} eq '/login/callback'
        };

        mount '/' => sub {
            my $env = shift;
            # $env->{PATH_INFO} eq ''
            # $env->{SCRIPT_NAME} eq '/login'
        };
    };
(snip)

のようになる。


YAPC::Asia 2010 スライドまとめ

見つかったスライドまとめ(敬称略)観測範囲狭すぎ & twitter は流れて思ったほどみつからなかった。RSS フィードだけでもなんでもいいのでまとめて reblog する仕組みがあってもよさげ。

Youtube

http://www.youtube.com/user/yapcasia

タイムテーブル

http://atnd.org/events/8375

http://yapcasia.org/2010/timetable.html

YAPC::Asia 2010 前夜祭

イントロダクション
  • yusukebe 10min
WAF作者+利用者の集い
Lightning Talks 1人 5min
スペシャルゲスト対談 50min
  • スペシャルゲスト ITMedia News記者「岡田有花」さん
  • 対談相手 otsune ( 代打 yusukebe )
gihyo

http://gihyo.jp/news/report/01/yapcasia2010/0000

YAPC::Asia 2010 1日目

  • Welcome Speech / Daisuke Maki
  • That Goes Without Saying (or Does It?) / Larry Wall
  • Modern Perl Web Development on Amazon EC2 / Kazutake Hiramatsu
  • Stylish: Perl ♥ Emacs / Jonathan Rockway
  • When Scrum is not enough / Abigail
  • Inside Mixi / Hiroki Daichi
  • How to test scraping / Kuzuha
  • The perlbrew story / Kang-min Liu
  • Perl and C binding: new and upcoming ways / Shmuel Fomberg
  • Write Good Parser in Perl / Jiro Nishiguchi
  • mixi チェックインの裏側 / Seiji Harada
  • App::* modules / Cornelius (c9s)
  • Perl プログラマが PHP大規模開発の会社に入って / 伊藤直也
  • Webサービスのページング処理について / 吉見 圭司(walf443)
  • Lightning Talks Day One
gihyo

http://gihyo.jp/news/report/01/yapcasia2010/0001

YAPC::Asia 2010 2日目

  • Ajax Application Testing / Yappo
  • O2 - A well proven WebFramework now Open Sourced / Tarjei Vassbotn and Håkon Skaarud Karlsen
  • バウンスメール解析システム``BounceHammer''の紹介 / azumakuniyuki
  • YATT -- Yet Another Template Toolkit, Designed for WebDesigners / 小林 弘明 | KOBAYASI Hiroaki
  • Perl5 is Alive! / Jesse Vincent
  • Perlで任意精度計算 / Daisuke Maki
  • ある連載の舞台裏 - History tells us ... / Kenichi Ishigaki
  • Introducing DBIx::ObjectMapper / Eisuke Oishi
  • perl-casual特別企画 PMグループディスカッション
  • Welcome Perl 6! Is Rakudo Star useful? / risou
  • perl-casual特別企画 私とPerlの関わり / 横山 彰子
  • Lightning Talks Day Two
  • Keynote / Tatsuhiko Miyagawa
gihyo

http://gihyo.jp/news/report/01/yapcasia2010/0002

togetter


Mojolicious の wiki を書いた

というのは嘘で、訳しただけです。

http://github.com/kraih/mojo/wiki/Writing-websocket-chat-using-Mojolicious-Lite

他の方の手も入って(詳しくはwikiの編集履歴を参照してください)よいものができたと思います。

id:naoya さんに快諾していただいた http://d.hatena.ne.jp/naoya/20101011/1286778922 の英語版です。


reverse proxy として動かした nginx で HTTP_X_FORWARDED_FOR を $env に突っ込む設定

server {
  listen      80;
  server_name example.com;
  access_log /var/log/nginx/example.com.access.log main;

  location / {
    access_log      off;
    proxy_pass http://localhost:8080;
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

でおk。 via http://wiki.joyent.com/smartmachine:nginx_apache_proxy

X-Forwarded-For の他にも X-Real-IP や Host の設定もできる。


stfuawsc

via http://blonde.ddo.jp/object/glob.pl/2010/10/06/00/25/22 / see also: http://b.hatena.ne.jp/entry/blonde.ddo.jp/object/glob.pl/2010/10/06/00/25/22

コード一行もなかったんで無視するのが一番なんだろうけども、「実用的な複雑さでどの程度の速度が出るか?」というのは定期的に測るとよろしいと思うのでリポジトリ作った。

ヒマを見つけてボチボチ更新していきます。他にも似たようなことやってる人いるのかなぁ。

pull req 大歓迎です。 http://github.com/punytan/xslate_large_file_bench

とりあえず手前の環境ではこんな感じ。

$ perl bench.pl 
perl version: 5.012001(xslate: 0.2008)
size of tiny.tx: 493
size of large.tx: 23665
render  tiny.tx: 0.000046
render large.tx: 0.000042
          Rate large  tiny
large  41965/s    --  -78%
tiny  190975/s  355%    --

$ perl bench.pl 
perl version: 5.012001(xslate: 0.2008)
size of tiny.tx: 493
size of large.tx: 23665
render  tiny.tx: 0.000023
render large.tx: 0.000027
          Rate large  tiny
large  62704/s    --  -66%
tiny  185140/s  195%    --

$ perl bench.pl 
perl version: 5.012001(xslate: 0.2008)
size of tiny.tx: 493
size of large.tx: 23665
render  tiny.tx: 0.000056
render large.tx: 0.000041
          Rate large  tiny
large  43971/s    --  -76%
tiny  186980/s  325%    --

« 7 »