ネットワークIOについて

ある日のtwitter


write()のエラーをコールバックする必要があるのかどうか。write()が失敗するのはカーネル内の出力バッファへのコピーが拒否されたときで、送信エラーが発生したときではないわけで。
実際に送信エラーが発生すれば、今度はread()が失敗するはず。特にTCPだったらwrite()が失敗しておきながらread()が失敗しないのは考えにくい…。保証はないけども…。
つまり宛先ノードが落ちたとかは、read()の失敗で検出できる。何ならwrite()が失敗したらファイルディスクリプタをclose()してしまえば、確実にread()が失敗する。
というわけで、送信専用のソケットでread待ちをしていない限りは、writeの失敗をコールバックする必要は無いんじゃないか。
でもwrite()失敗したときにclose()してしまうのは、別の問題が発生するかもしれない。close()するとそのファイルディスクリプタ番号は解放されるわけで、マルチスレッドだと、read()が失敗する前に同じ番号で新しいファイルディスクリプタが作成されるかもしれない。
失敗すると思っていたread()は、実際にread()する前に別の宛先に繋がったファイルディスクリプタに対するread()にすり替わっていて、何事もなく成功してしまう。これはマズイ。
write()が失敗した後、ファイルディスクリプタをclose()せずに、read()を確実に失敗させたい。shutdown()を使えばいいのかな? ファイルディスクリプタが全二重なソケットで無かったことを考えて、とりあえずshutdown()は呼ぶけどエラーは無視する、と。
ソケット通信で信頼性を確保するには、read待ちしていないといけないハズ。あとkeep-aliveパケットを定期的に飛ばす。
read待ち+keepaliveせずに突然write()すると、宛先は既に落ちていても普通にwrite()が成功してしまう。送信の失敗はclose()で検出されるけど、close()時にエラー処理をするような実装は難しそう。
write()で成功しても宛先に届いたどうかは分からない。信頼性を確保したいなら、普通したいけど、相手から返事を受け取るプロトコルにして、read()でそれを受け取る方がずっと実装が楽なんじゃないか。close()でがんばってエラー処理するよりも。
write()するごとにfsync()を呼べば実際に送信エラーが発生したかどうかを検出できるか。でもそれは遅いなぁ。fsync()だけスレッドを分けることもできない気がする。どのwrite()が失敗したかfsync()スレッドから知ることができない。
write()時に送信エラーが発覚したならshutdown()でread()を失敗させる。そうでなければ送信エラーは返事が返ってこない==タイムアウトで検出する。プロトコルは返事を返すものにしておく。write()の失敗は何のアテにもならないのでコールバックしない。
送信側は送信エラーが発生していても何も対処しない…対処するためにはどのwrite()が失敗したか判別しないといけない == write()するたびにfsync()がいるわけで、普通にやらない。
定期的にkeepaliveを飛ばしてやらないと、通信が FIN / FIN ACK なしで突然落ちてしまったときにread待ちエラーが発生しないために、長い間送信できない(しかしwrite()は成功する)ファイルディスクリプタがプロセスの中に残ってしまう。これは良くない。
keepaliveのトラフィックと、送信しようと思ってwrite()したら成功したけど実は送信エラー→返事待ちreadがタイムアウト→再送 のオーバーヘッドのどちらを取るか、というトレードオフ
Partty.org!でクライアントを終了してもサーバー側でセッションが残るバグはコレが原因だろうなぁ…。keepaliveしてないからな…。
Partty!.org(というかtelnetプロトコル)はサーバーからwrite()しようとしないから、write()も失敗しない + read待ちも失敗しない + keepaliveしない で、いつまでたってもエラーが発覚しない。keepalive重要。
ネットワークで送信時の信頼性を保証するのはかなり難しい。タイムアウトとkeepaliveを確実に実装しないといけない。再送するには送信時のバッファを確実に送信できたと分かるまでとっておく必要がある。寿命管理が…
プロトコルを自分で決められるなら、ぜんぶRPCで抽象化してしまいたいなぁ。1回抽象化できたら使い回しまくるであろう。
RPCで抽象化する問題は、プロトコルがメッセージ指向になること。ストリームと相容れない。受け取りつつ送信、というような処理が苦手。やるなら小さいメッセージをたくさん送るようにして、擬似的にストリーム処理するしかない。
しかしメッセージの粒度が小さくなると、RPCメッセージをディスパッチするオーバーヘッドが大きくなってくる。要するには巨大データをやりとりするのに向かない。
RPCで抽象化しつつも、プロトコルをパースする実装に介入できる拡張性がある実装がいい。あるメッセージを受信したら、TCPストリームのハンドラを、RPCメッセージのパース+ディスパッチするハンドラから、ストリーム処理をするハンドラに置き換える。
高信頼性の高い分散ストレージや分散メッセージングの実装が難しいのは、このあたり低レイヤーの実装が難しいところに起因していそう。破綻しない分散アルゴリズムと確実な実装の両方が必要。さらに高速性が求められるとなればかなりキツイ。
分散アルゴリズムか実装かのどちらかが一般化されると、たくさん実装が出てきそうな気がするなぁ。片方は好きだけど、もう片方は苦手だという人が参戦できる。個人的には分散・高信頼メッセージングのアルゴリズムに決定版って無いんですかと…
現状、信頼性の高い分散メッセージングをやるには、ハードウェアに頼るという反則を除けば、P2Pなサーバー群に、1台のマネージャノード(マスタ/スレーブ構成)を加えたハイブリッドモデルしかないともう。さらにパフォーマンスを上げるにはマネージャノードに負荷が集中しない仕組みが必要。