|
|
||
いろいろ指摘はありがたいのですが、アルゴリズムの話をするのに正直これは枝葉の議論じゃないかと思う。
for(;;) より foreach の方が速いのは知っています。この手の定数項のチューニングはそれこそ大量に入力があって定数項を減らしたいときにベンチマークを取りながら律速になってるところを少しずつ削っていけば良いと思います。
可読性に関しては、私の場合はアルゴリズムの話は C や C++ などで書かれたコードの書籍を良く読んでいるから for(;;) の方が分かりやすい。配列にばらさない方がよいとかも同じ。(Pythonで xrange 使っているのはまだそれしかやり方を知らないんです。) 弾さんはそちらの方が分かりやすい、私はこちらのほうがわかりやすいというただの主観の話ではないかな。Perl は There is more than one way to do it でしょう?
こういうコーディングスタイルの押しつけみたいなの、○○すべき、みたいな話がウェブやメーリングリストで多いから萎縮しちゃう人も多いですが、趣味のプログラムなんて好きなように書いて好きなように晒せば良いんだよ。仕事でのコーディング規約じゃないんだから。
まあ、程度の問題なんで、use strict をまだ知らないとか、そういうのは教えてあげたら良いと思うし、Python でソース書いてたら unicode() を教えてもらったとかそういうのはとてもありがたかった。Perl Best Practice の類は趣味の押しつけが行き過ぎてる。参考にはなるけど、そうしなければならないとは思わない。私もそんな風におもっていたこともありました、でもあれ良いと思ってた頃は、個人的になかったことにしたい。そんな年頃です。
の件ですが、一部誤解があるようです。現在の仕様はどうなっているかと言いますと
となっています。
ですので、例えばみなさんのはてなダイアリーなどでは全ドキュメントに対して遡ることができます。また http://blog.livedoor.jp/dankogai/ のような場合も同様です。
http://finalvent.cocolog-nifty.com/ のような場合は、そのドメインのエントリーのうち、直近 10,000 件しか遡れません。ただし、国内トップサイトのようなサイトでない限り 10,000 エントリー以上ブックマークされたエントリーがあるサイト (合計 10,000 件以上ブックマークを集めたサイト、ではありません) はまずありません。
という仕様ですので、個人が自分のサイトを調べる分に、制限されることはまずありません。またベテランのブログの記事を辿りたいという場合も同様です。
問題になるのはニコニコ動画全体、はてなダイアリー全体、といった特定のサービスを全体にわたって過去に遡る場合です。この場合、数十ページから数百ページ以降は表示されないこともあります。ただし、これが辿れなくて問題になるのはプログラムからアクセスした場合で、ヒューマンアクセスで問題になることはまずないでしょう。
例えば Google の検索結果は 1,630,000 件ヒットしても実際には 500 件程度までしかページングできませんが、それで困る人はまずいません。それと同じです。
残る問題はプログラムで過去のデータを分析したい、というユースケースです。こういった場合には URL とブックマークの件数データを、生データを公開するなどで今後、対応していきたいと思っています。プログラムから統計情報を利用したいという限定された利用のために、通常のアクセスの速度を犠牲にするのは、サービス運営上難しいです。そういったトレードオフがあるということをご理解ください。(そもそも、ロボットで過去のデータをクローリングして統計情報として利用するという利用ケースは推奨していません。過剰アクセスは通常のユーザーアクセスに悪影響を与えるので、制限せざるを得ません。新バージョンのベータ公開時にサイトがダウンしたことがありましたが、原因はこの機能でした。旧バージョンで動作していたのは、スケールしていたからではなく、たまたまです。旧バージョンでも頻繁に、この機能が原因でサイトがダウンすることがありました。)
過去のデータの蓄積から検索したりといったことがブックマークの目的ではないかというのは、その通りです。ですので、できうる限り蓄積したデータは提供していますし、検索もできます。
「集合知」というのはロングテールのデータをロボット等で取り出せるように公開しておく、ということだとは思いません。ロングテールにあるデータも含めて、何かしらの仕組みで情報要求通りに順位付けをした上で提示することだと思います。そのために検索や、関連エントリーなど(人気エントリー、新着エントリーといった限定された範囲のデータを対象にしたものではなく)広範囲のデータに対するアプローチがあります。今後もその辺りを継続的に強化していくつもりです。
delete_if ですが、手元には修正済みのコードがありました。すいません。git に上げてなかったです。 > d:id:yappo
sub delete_if { my ($self, $code) = @_; croak "Argument must be a code" unless ref $code eq 'CODE'; return $self unless $self->size; my $last_index = $self->_last_index; for (0..$last_index) { my $item = $self->shift; local $_ = $item; $self->push($item) unless $code->($_); } return $self; }
もう少し深追い。id:naoya:20070928:1190974874 の通り、Gearman::Worker がリクエストを最後まで受信できないのを直すこと自体は sysread() 呼びだし一回で済ませようとしてるところを(無期限ブロックを回避しつつ)ループにするなり後述する MSG_WAITALL を使うなりすればよさそう。
ここから先に調べたのは二点
というところ。以下、うんちくです。
「ソケットからの read(2) が、その指定したサイズどおりに読めないことがあってそこは不定」というのは FAQ だけども、じゃあ具体的にどういうときに指定したサイズどおりに読めないのか。ちゃんと説明できないなあ、ということで、深追い。
最初は id:naoya:20070928:1190974874 で UNIXネットワークプログラミング〈Vol.1〉ネットワークAPI:ソケットとXTI から引用した箇所にもあるとおり、
という風に思ってたのだけど、それだけじゃないですね。もちろんソケットの受信バッファが溢れたときもそうなる(その場合 TCP の流量制御でクライアント側も送信で待たされる) んだけど、
TCP socket ではデータは単にバイトストリームとして扱われるので、例えば送り側が 2048 Bytes を 1 回の send() で送ったからと言って、受信側が 1 回の recv() で受け取れる保証はない。
転送の都合で send したデータが適宜切り分けられ、複数のパケットで送られる場合がある。そして recv() は「読め」と言われたバイト数が溜るまで待たず、少しでもデータが送られてくれば return する。相手が何バイト送ったかなどをチェックするようにはなっていないということである。
一般的には、これから送るデータのサイズを通知して、受け手の方でこのサイズを受け取るまで繰り返して recv() するという方法がとられるだろう。また、fdopen(sock) しておいて、fread() を使うという手もある。
プログラミング系 Q&A 2
ということでした。
もうひとつ Perlネットワークプログラミング―ソケットの使い方からクライアント/サーバーシステムの開発まで の P.22 から引用すると
sys* () 関数による stdio のバイパスには、実際のデータ量が要求されたデータ量に満たなかった場合に、read() 関数と sysread() 関数で振る舞いが異なるという効果もある。read() 関数の場合には、リクエストされたデータを正確に取得できるまで、関数は無期限にブロックされる。唯一の例外は、ファイルハンドルがリクエストを完全に満たす前にファイルの終端に達した場合である。この場合、read() 関数はファイルの終端までのデータを返す。対照的に、sysread() 関数では部分的な読み取りが可能である。この関数は、リクエストされたデータをすぐに読み取れない場合、その時点で取得できるデータを返す。データがまったくない場合、sysread() 関数は最低でも1バイトを読み取るまでブロックされる。このため、sysread() 関数は、データが不確定なサイズで送信されることが多いネットワーク通信に欠かせない存在である。
とある。(上記は Perl の read() / sysread() の差異の話。sysread は C の read(2) に相当)
この二点を合わせると
という動きになる。
まとめると sysread() (もしくは read(2)) が指定したサイズ通りに帰らない条件は
の 5 つ。
ループで何度か read(2) を発行する意外に、この 5 つの条件を 3 つに減らす方法、つまり例外時以外は指定したバイト数分読むまでブロックすることもできて、recv(2) で MSG_WAITALL 使うなどがあるそう。(via UNP)
例えば Gearman::Util::reas_res_packet() の sysread のところを
my $rv = recv($sock, $buf, $len, MSG_WAITALL);
die $! if not defined $rv;
my $buflen = length $buf;
return $err->("short_body") unless $buflen == $len;
と変更すると、件の問題はひとまず発生しなくなる。(がこの実装だと $len が意図しない値だった場合に無期限ブロックしてしまうのでその対処は必要です。)
ソケットの受信バッファは setsockopt / getsockopt で SOL_SOCKET, SO_RECVBUF で見たりセットしたりできる。
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> int main () { int val; int fd = socket(AF_INET, SOCK_STREAM, 0); int len = sizeof(val); if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &val, &len) == -1 ) { perror("failed to getsockopt"); exit(1); } printf("%d\n", val); return 0; }
こんな具合で。
% ./rcvbuf_size 87380
この 87380 という値は Linux の場合
% cat /proc/sys/net/ipv4/tcp_rmem 4096 87380 174760
と、ここで決まる。真ん中がデフォルトの値。TCP の流量制御の中でこの値は OS が自動でデフォルト値から減らしたり増やしたり調整をするようです。
この SO_RCVBUF の値がいつどういう風に使われるのか。TCP パケットの「Window フィールド」に埋め込まれて、パケットのやりとりの中でサーバー / クライアントで交換されます。これでお互いに、相手にはあとどれぐらいの受信バッファが残っていてどのぐらい送れるか、というのが調整される。これ以上相手には送れない、という状態になったら送り手側はそこでブロック。これが TCP のウィンドウ通知の機能。
この Window フィールドは http://www.fuwafuwa.org/Lecture/learn/network_nepc/course2/chapter04/section05.html などを見てもわかるとおり 16 ビットしかありません。とういことは普通に整数値をやりとりするのでは 65,535 バイトまでしか表現できない。そこでウィンドウスケール。TCP 接続確認時の ACK パケットに Options フィールドに3バイトのデータ
を埋め込んで送りつけてやると、バイナリシフトカウンタで指定した値 (0 ~ 14) に従って Window サイズをビットシフトして計算するようになる。これで 65,535 x 2^14 の SO_RCVBUF の値をやりとりできるようになります。ウィンドウスケールに関しては TCPのしくみと実装―RFCの詳細から実装系の解析まで (TCPIP基礎シリーズ) が詳しかった。
ところで、このウィンドウスケールは TCP の 3ウェイハンドシェイクの時点で何ビットシフトするかを決定します。ということは、setsockopt(2) で明示的に SO_RECVBUF に 16 ビット以上の値を指定したい場合、SYN セグメントを飛ばす前 = connect する前ににセットする必要がある、とのことです。(ただ、Linux の場合はデフォルトで 65,535 以上の値を使ってるので、特に意識しなくてもバイナリシフトカウンタに正の値が指定されて3ウェイハンドシェイクが始まってるんではないかと思う。)
更に、SO_RECVBUF に指定できる値は Linux の場合その上限が
% cat /proc/sys/net/core/rmem_max 131071
で制限されています。また、ウィンドウスケールの有効/無効自体は
% cat /proc/sys/net/ipv4/tcp_window_scaling 1
と、ここで決まる。
最初はソケットの受信バッファ周りを疑っていて、そんで調べていたらこの辺にたどり着きました。せっかく調べたのでこちらもまとめてみました。
Gearman::Util のパッチを、と思ったけど Gearman 全体の処理の中でタイムアウトとかパケットサイズの上限をどう実装すべきか、というのが。
hideokiてすと
stanakaテスト2
http://support.github.com/discussions/feature-requests/167-signing-webhook-as-hmac-using-a-shared-key
GitHub はユーザ名、パスワードで Basic 認証できるのでそれをつかいなよ、って方針みたいですが。
http://github.com/blog/237-basic-auth-post-receives
WebHook-Sign ヘッダ等で提供するなら現状のキーの仕様ともバッティングしませんし、後日よりセキュアな認証方法として仕様追加、でも大丈夫そうですね。検討してみます。
おもちゃな段階が終わった頃に(開発者さま向け)とか書いてあるのは外した方がいいなーと思ったす。
それか Web Hok URLの登録は、hok受ける側のサービスではてな認証したら勝手に登録できるくらい簡単になるとか。
スターやらIDコールも、web hokのインフラ使うようになったら簡単な仕組みじゃないと色々大変そうすな。
そうですね、実際には、開発者が作ったものをそうでない人が利用できるものなのに、開発者さま向けと書いていると自分とは関係ない機能だと思われてしまうということですよね。
はてな認証したら勝手に登録できる、というようなのは、public な web hook を作る人には良い手段ですね。