Network Programming

October 27, 2010

XSSに強いウェブサイトを作る – テンプレートエンジンの選定基準とスニペットの生成手法 (第1回神泉セキュリティ勉強会発表資料)

発表資料は以下のとおり。春山様はじめECナビの皆様、ありがとうございました。

October 16, 2010

My Slides at YAPC::Asia 2010 (Writing Prefork Servers / Workers, Unix Programming with Perl)

The slides I used for my presentations at YAPC::Asia 2010 are now available from the links below. Thank you to those who came, and JPA and the volunteer stuff for running the event. I hope you enjoyed (will enjoy) my slides!

August 17, 2010

Releasing Starlet 0.10, now runs faster than ever

Last Sunday my friend told me that Starman is now way faster than Starlet and I started to wonder why.  There should not be such a difference between the two servers, sicne they share a lot in common.  So I looked into the code yesterday, found the bottleneck and fixed them, as well as adding some tweaks.  Here's what I did, and the chart[1] shows how the performance changed.

Starlet

1) optimize the calculation of Content-Length

Plack::Middleware::ContentLength was the larget bottleneck.  Instead of calling the module Starlet now calculates the Content-Length header by itself in a faster way, when and only when necessary.

2) use TCP_DEFER_ACCEPT on linux

TCP_DEFER_ACCEPT is a flag that suppresses the return of accept(2) until any data arrives on a newly-established TCP socket.  By using the flag, same number of HTTP requests can be handled by a smaller set of workers, since they do not need to poll waiting for HTTP requests to arrive on non-keepalive connections.  The fact leads to less memory pressure and higher performance.  Starlet turns on the flag by default.

3) switch from alarm to select-based polling

Apps can now use alarm for their own purposes.

4) reuse the fake psgi.input

An optimization copied from Starman.

5) merge short content-length with response headers

It is sometimes faster to merge data within perl and send them as a single packet than sending the two separately.  BTW, anyone going to write a wrapper for writev(2)?

The conclusion is that Starlet is still a good choice if you are looking for an application server that runs behind a reverse proxy, maybe better than Starman until miyagawa-san copies the optimizations back :-p

NOTE 1: The benchmark was taken on linux-2.6.32 (x86_64), using a single core of Core 2 Duo running at 3GHz.  The options used were "ab -c 50", "plackup -s Starlet --max-reqs-per-child=10000", "--workers=64" for keepalive and "--workers=16" for non-keepalive.

July 02, 2010

Writing a Fast and Secure HTTP Parser (in the case of HTTP::Parser::XS)

In May, I had a chance to give a talk at Tsukuba.xs Beer Talks #1.  I forgot to upload the slide since then, but finally, here it comes :-p

The slide titled "HTTP::Parser::XS - writing a fast & secure XS module" covers the design and implementation of HTTP::Parser::XS: an XS HTTP Parser that is used by a number of Plack-compatible web servers, and picohttpparser: its underlying HTTP parser written in C.

My intention was to introduce to programmers who mostly use Perl the fun of writing tight (fast and beautiful) code using the C programming language.  It would be grateful if you could feel some of my addiction from the slides.  Enjoy!

PS. And if you find any security holes or bugs, please let me know. Thank you in advance.

April 24, 2010

REST におけるトランザクションについて (Re: Web を支える技術)

といいつつ、ひとつだけ理解できないというか、納得できないところが。トランザクションのところがなんだかRESTっぽくないのがすごく気になる

Webを支える技術 -HTTP、URI、HTML、そしてREST (WEB+DB PRESSプラスシリーズ)(山本 陽平) - ただのにっき(2010-04-23)

Web を支える技術」は自分もとてもいい本だと思う (教科書としてすばらしいし復習用としても読みやすいのでイイ) のですが、トランザクションの所だけは分かりづらいなと感じました。その原因は、atomic transaction で解決できる課題を例として使っているという点と、トランザクションと更新クエリのレイヤ分割がされていない、という2つの点によるものではないでしょうか。

HTTP 上でトランザクションを表現する必要があるケースのほとんどは、atomic transaction ではなく、2相コミットもしくは long-running transaction を行う場合だと思います。

たとえば、HTTP 上で2層コミットを行っているシステムの代表例としては、おさいふケータイのチャージ機能が挙げられます (あれって HTTPS ですよね?)。また、たとえば、数量に限定がある商品を課金代行サービスを使って販売する場合等においては、long-running transaction が必須になります。

なので、トランザクションレイヤを RESTful に表現する手法は理解しておいて損がないものだと思います。具体的には、

  • トランザクションの開始は POST を使ったトランザクションリソースの生成
  • トランザクション開始後は POST ではなく PUT を使い、クライアントがリクエスト ID を指定することで、ネットワーク障害への耐性を確保
  • トランザクションのコミットも再送可能じゃないと困るので PUT
  • コミットの返り値を確認してからトランザクションリソースを DELETE

といったあたりが基本になると思います。

以下、具体例として、「アライブドアチケット」というチケット販売サイトから、ミュージカル「夜のhidek様」のチケット (2010年5月24日の公演) を購入する例を示します。支払いには Yappo! ペイメントを利用します。

まずは、残席があるか確認します。

GET http://ticket.alivedoor.com/musical/20100524/夜のhidek様/seats-left HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/json

{
  "seats-left": 11
}

「夜のhidek様」は、歌舞伎町でも大人気な、あの往年のアイドルが主演する人気ミュージカル。残席が少ないです。今すぐチケットを買わないといけません。トランザクションリソースを生成して、トランザクションを開始します。

POST https://ticket.alivedoor.com/buy HTTP/1.1
Content-Length: 0

HTTP/1.1 201 Created
Location: https://ticket.alivedoor.com/buy/934085
...

次に、座席を確保します。サーバは席が確保できれば「201 Created」を、既に満席になっていれば「409 Conflict」を返します。

PUT https://ticket.alivedoor.com/buy/934085/1 HTTP/1.1
Content-Type: application/json

{
  "url"    : "http://ticket.alivedoor.com/musical/20100524/夜のhidek様",
  "seats"  : 2
}

HTTP/1.1 201 Created
...

座席が確保できたら、支払い金額を確認します。

GET https://ticket.alivedoor.com/buy/934085 HTTP/1.1

HTTP/1.1 200 OK
Content-Type: application/json

{
  "amount": "10000",
  "currency": "JPY"
}

次に、クライアントは Yappo! ペイメントを使って、アライブドアチケットの口座に指定された金額を振り込みます。振込が完了すると、Yappo! ペイメントは振込IDを返すので、その振込IDを指定してトランザクションをコミットします。

PUT https://ticket.alivedoor.com/buy/934085/commit HTTP/1.1
Conetnt-Type: application/json

{
  "payment_id" : "https://payment.yappo.jp/paymentid/02394895923408904930"
}

HTTP/1.1 200 OK
...

「200 OK」が返ってきたので、支払が完了し、取引が成立したことがわかります (失敗した場合は「409 Conflict」が返ってくるでしょう)。最後にトランザクションリソースを削除します。

DELETE https://ticket.alivedoor.com/buy/934085 HTTP/1.1

HTTP/1.1 200 OK
...

以上のような流れになると思います。「夜のhidek様」言いたかっただけ (ry

処理の対象となるリソース (上の例では http://ticket.alivedoor.com/musical/20100524/夜のhidek様) が、トランザクション中の Request-URI に含まれていないのが気持ち悪い、という意見もあるかもしれません。ですが自分は、トランザクションリソースを使う場合は、HTTP はトランザクション処理に専念、HTTP 上のペイロードで処理対象のリソースを表現する方が、レイヤが分かれるので良いと考えます。

December 01, 2009

TCP通信ではデータの送信をまとめて行うべき、もうひとつの理由(& サーバのベンチマーク手法の話)

 TCP通信をするプログラムを書く際に「データの送信はまとめて1回で」行うべき、というのは鉄則と言っていい、と思います。その理由としては、パケット数を最小限に抑えることでオーバーヘッドを少なくするためだと一般に説明されますが、自分はもうひとつポイントがあると考えています。次のグラフを見てください。

Write_size_and_speed

 グラフは、一定量のデータを転送するのにかかる時間と使用するブロックサイズ(1回のwrite(2)で書き込むサイズ)の関係を表したものです注1

 ホスト間のTCP通信を行っている場合は、TCPのバッファが有効に機能するので、ブロックサイズ(=パケット数の逆数)による速度の変化は、ほぼありません。一方、同一ホスト上で通信を行うと、ブロックサイズと反比例して所要時間が反比例の関係にあることがわかります。

 原因は、同一ホスト上の通信では、送信プロセスがwrite(2)を呼ぶたびにコンテクストスイッチが発生し、受信プロセスが起き上がってread(2)でデータを受信するところにあります。そのオーバーヘッドが大きいため、ブロックサイズを小さくするのに反比例して速度が遅くなるのです。この傾向は、送受信プロセスが交互に起動するユニプロセッサのシステムだけでなく、別個のCPUで実行されるマルチコアの環境でも同様です。

 以上が、TCP通信をするプログラムではデータの送信はまとめて1回で行うべき、もうひとつの理由です。アプリケーションプログラマの視点から見ると、ホスト間のTCP通信はスループット重視だが、同一ホスト間の通信はレイテンシ重視だ、と理解しておけばいいのかもしれません。少なくとも、高速なサーバプログラムを開発する際には、ローカルとリモート両方でのベンチマークをとった上でチューニングを行うべきなのは確かだと思います。

 自分が昨日の Shibuya.pm #12 で「サーバプログラムはリモートホストからベンチマークすべき注2」と主張したのは、以上に基づき、不要なコンテクストスイッチを避けるべき、という考えからです。今、手元にいいデータがないのですが、サーバとベンチマークプログラムの両者を同一ホスト上で動かした場合のみ、コンテクストスイッチが頻発しパフォーマンスが低下するケースがあったと記憶しています注3

 サーバの作りは製品によって異なりますから、最大スループットを比較するようなベンチマークテストを行う場合は、リモートホストから測定するのが公平なのではないでしょうか注4

注1: 確か linux 2.6.18 で測定。TCP_NODELAY はセットしています
注2: 「実際の利用形態がリモートホストからのアクセスになるのであれば」とすべきかも
注3: 例えばマルチスレッドなサーバとマルチスレッドなクライアントだと速度が落ちると思います
注4: 最大スループットを必要とするような大規模の運用では、通常リモートホストからサーバを利用すると考えられるため

October 05, 2009

Apache で X-Reproxy-URL ヘッダを使えるようにするモジュール mod_reproxy を書いた

ウェブアプリケーションにおいて、認証がかかっている画像や大きなファイルを配信する場合には、Perlbal 等でサポートされている X-Reproxy-URL ヘッダが有効なことが知られていて、その理由としては、

  • (メモリを大食いする) アプリケーションサーバのプロセスを転送終了まで占有しない
  • HTTP ベースの分散ファイルシステムとリバースプロキシが直接交信するので、ネットワーク負荷が低い

といった点が挙げられます。「でも、Apache は X-Reproxy-URL ヘッダをサポートしてないんだよねー」という話が、先日の YAPC::Asia 2009 においても話題になっていました[要出典]。回避策としては、ワンタイムURLのような手法もあるのですが、セキュリティな懸念もあります。

なんとかしたいなと思っていたのですが、気が向いたので、Apache に X-Reproxy-URL ヘッダ対応機能を追加するモジュール mod_reproxy を書いてみました。Github に置いてあります。

kazuho's mod_reproxy at master - GitHub

使い方は簡単。ダウンロードして、apxs を使って、コンパイル&インストールを行います。

# apxs -i -a -c -Wc,-Wall -Wc,-g -Wc,-O2 -lcurl mod_reproxy.c

その後、Apache の設定ファイルに

Reproxy On

と追記すれば、その追記した範囲において、X-Reproxy-URL ヘッダによる内部リダイレクトが有効になります。

それだけです。特に説明することもないのですが、状況によっては便利に使えるかと思います。

参考: lighttpd の X-Sendfile を使えるApacheモジュール - spiritlooseのはてなダイアリー

September 22, 2009

「サーバ書くなら epoll 使うべき」は、今でも正しいのか

 多数のTCP接続をハンドリングするサーバを書くなら、1コネクション1スレッドのモデルではなく、epollやkqueueのようなイベント駆動型のI/O多重化を行うべきだ、と言われます。だが、そのような主張は、「C10K問題」が書かれた2002年から7年経過した今でも有効なのでしょうか? echoサーバを書いて、ベンチマークを取ってみることにしました。

 ふたつのグラフは、いずれも接続数とスループットの関係を表しています。最初のグラフは、全接続がアクティブに通信した場合、あとのグラフは、全接続のうち小数のコネクションが順次アクティブになっていく、というモデルです。これらのグラフから、以下ようなことが読み取れます。

  • epoll も per-thread モデルも、良くスケールする
  • epoll は、ワークセットが小さい場合に (最大50%) per-thread モデルよりも高速

 少なくとも、1コネクション1スレッド、という選択肢が現実的であることは確かのようです(特にポータビィリティでイベント駆動モデルよりも優れていることを考えると注1)。また、今日ではマルチプロセッサ対応は必須ですから、マルチスレッド(マルチプロセス)+イベント駆動の二階建てにするよりも、1コネクション1スレッドのモデルの方が、必然的に単純になります。

 一方で、ロックの競合が問題になる場合や、最後の一滴までサーバのリソースを搾り取りたい場合は、まだイベント駆動でがんばるべきなのかもしれません注2

注1: イベント駆動モデルでは、ファイルシステムへのアクセス等も非同期 /Oを使用して書く必要があります
注2: mycached は、このケース

参考: 軽量スレッドブームだと思うので、そこらへんの情報をまとめてみる, Thousands of Threads and Blocking I/O, Completely Fair Scheduler によるマルチプロセッシング

August 26, 2009

Picoev: a tiny event loop for network applications, faster than libevent or libev

I am sure many programmers writing network applications have their own abstracting layers hiding the differences between various I/O multiplex APIs, like select(2), poll(2), epoll(2), ... And of course, I am one among them.  While writing mycached (see Mycached: memcached protocol support for MySQL for more information), I was at first considering of using libev for multiplexing socket I/Os.  Libevent was not an option since it does not (yet) provide multithreading support.

But it was a great pain for me to learn how to use libev.  I do not mean that its is an ugly product.  In fact, I think that it is a very well written, excellent library.  However, for me it was too much a boring task to learn how the things are abstracted, already being familiar with the problems it tries to hide.

So instead I thought it might be a good occasion to write my own library that could be used in any programs I may write in the future.  The result is picoev, and it is faster than libevent or libev!  The benchmark used is a re-modified version taken from libev.schmorp.de/bench.html and can be found here.

Why is it faster?  It is because it uses an array and a ring buffer of bit vectors as its internal structure.  Libevent and libev seem to use some kind of sorted tree to represent file descriptors.  However, if we concentrate on Un*x systems, there is a guarantee that the descriptors will a small positive integer.  Picoev utilizes the fact and stores information related to file descriptors (such as pointers to callback functions or callback arguments) in an array, resulting in a faster manipulation of socket states.

Another optimization technique used by picoev is not to use an ordered tree for keeping timeout information.  Generally speaking, most network applications do not require accurate timeouts.  Thus it is possible to use a ring buffer (a sliding array) of bit vectors for the purpose.  Each bit vector represents a set of file descriptors that time-outs at a given time.  Picoev uses 128 of bit vectors to represent timeouts, for example, the first bit vector represents the sockets that timeout a second after, the second bit vector representing them of two seconds after..., and the bit vectors slide every second.  If the maximum timeout required by the web application is greater than 128, the minimum granurality of timeout becomes two seconds.

I would like to reiterate that both libevent and libev are great libraries.  Picoev is not at comparable to them especially in maturity and the number of features.  It only supports select(2), epoll(2), and kqueue(2) for the time being.  However the design is simple than the other two, and I think it will be a good starting point to write network applications, or to use as a basis for writing one's own network libraries.

If you are interested in using picoev, the source code can be found at coderepos.org/share/browser/lang/c/picoev/.

Mycached: memcached protocol support for MySQL

It is a well-known fact that the bottlenecks of MySQL does not exist in its storage engines, but rather in the core, for example, its parser and execution planner.  Last weekend I started to wonder how fast MySQL could be if those bottlenecks were skipped.  Not being able to stop my curiousity, I started  adding memcached proctol support to MySQL as a UDF.  And that is Mycached.

From what I understand, there are two advantages of using mycached (or the memcached protocol, in general) over using SQL.  One is faster access.  The QPS (queries per second) of mycached is roughly 2x compared to using SQL.  The other is higher concurrency.  As can be seen in the chart below, mycached can handle thousands of connections simultaneously.

And the lack of the described advantages has been the reasons for large-scale web applications to use memcached, even though it requires application developers to take care of keeping consistency between mysql and memcached.  In other words, mycached could become a good alternative here.

Mycached is still in very early stage of development.  I would never recommend using current version in a production environment, but if you are interested, the details of how to use it is as follows.

Source code of mycached is available at coderepos.org/share/browser/platform/mysql/mycached.  There is no Makefile or configure script yet, use GCC directly to create the UDF.  Source code of mysql is needed and should be set as include directories (you need to run make on the source code of mysql prior to compiling mycached).

% g++ -DMYCACHED_USE_EPOLL=1 -shared -fPIC -Wall -g -O2 -I ~/test/mysql-5.1.37/src/include -I ~/test/mysql-5.1.37/src/sql -I ~/test/mysql-5.1.37/src/regex mycached_as_udf.cc -o mycached_as_udf.so

Once the compilation succeeds, copy the shared library into the plugins directory of the lib directory of mysql, and use the CREATE FUNCTION statements to activate the necessary UDFs.  And to start mycached, call mycached_start.  The function takes three arguments: host, port, and number of worker threads to be used for handling requests.  To stop the mycached, call mycached_stop().

mysql> CREATE FUNCTION mycached_start RETURNS INT SONAME 'mycached_as_udf.so';
mysql> CREATE FUNCTION mycached_stop RETURNS INT SONAME 'mycached_as_udf.so';
mysql> SELECT mycached_start(0, 11211, 4);

The only memcached command currently supported by mycached is get (but you can do other things using SQL :-p).  As the following example shows, specify more than one db_name.table_name.primary_key to fetch a row of a table, separated by a whitespace.  The returned value will be a repetition of columns, each of them in the format of column_name:value_length:value.  It is also possible to specify JSON as the response format by adding ":JSON", however it will be slower, since the server needs to convert the values of the columns to UTF8 and to escape them.

$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
get test.t1.1
VALUE test.t1.1 0 68
id:1:1message:51:hello world! hello world! hello world! hello world!
END
get test.t1.1:json
VALUE test.t1.1 0 72
{"id":1,"message":"hello world! hello world! hello world! hello world!"}
END

Thank you for reading.  If you have any ideas or suggestions, please leave a comment.