人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

Trusterd HTTP/2 WebサーバのTLS接続時の性能を改善

もはやHTTP/2リファレンス実装であるHTTP/2のCライブラリnghttp2の作者であるtatsuhiro_tさんが素晴らしいベンチマーク結果を公開されました。

その中で以下のように、あまりにもH2Oやnghttpdと比べてtrusterdのTLS利用時の性能が遅かったため改善しました。

server 6 bytes 4K bytes
h2o 227865 78333
nghttpd 226716 80673
trusterd 62362 44020

ref: https://gist.github.com/tatsuhiro-t/5f3b170414582ac58091#tls-with-flow-control

主に原因としては以下が考えられます。

  • TLS record sizeが小さすぎる
  • それに伴いTCP_NODELAYオプションとも相まってパケットサイズも小さすぎる
  • パケットに吐き出すまでに貯めるbufferも小さすぎる

そこで、再度実装を見なおし、SSL_writeするまでのwrite bufferの調整とSSL_writeを含むコールバックが呼ばれる頻度をwrite bufferサイズを元に制御し、約3倍から5倍の性能改善を行いました。

性能改善前(3ea19218)

自身の検証環境では以下の様な結果になりました。

6 bytes 4096 bytes
36,813 req/s 24,257 req/s

同環境で、TLS無しだと20万req/s程度でることを考えると、かなり遅い事がわかります。

h2load -v -c 500 -m 100 -n 2000000

パケットの状況

TLSv1.2の右隣のカラムがパケットサイズになります。

479372  10.676007    127.0.0.1 -> 127.0.0.1    TLSv1.2 113 Application Data
479373  10.676058    127.0.0.1 -> 127.0.0.1    TLSv1.2 126 Application Data
479375  10.676120    127.0.0.1 -> 127.0.0.1    TLSv1.2 113 Application Data
479377  10.676171    127.0.0.1 -> 127.0.0.1    TLSv1.2 126 Application Data
479379  10.676191    127.0.0.1 -> 127.0.0.1    TLSv1.2 113 Application Data
479381  10.676242    127.0.0.1 -> 127.0.0.1    TLSv1.2 126 Application Data
479383  10.676262    127.0.0.1 -> 127.0.0.1    TLSv1.2 113 Application Data
479385  10.676312    127.0.0.1 -> 127.0.0.1    TLSv1.2 126 Application Data
479387  10.676421    127.0.0.1 -> 127.0.0.1    TLSv1.2 113 Application Data
479389  10.676474    127.0.0.1 -> 127.0.0.1    TLSv1.2 113 Application Data
479390  10.676484    127.0.0.1 -> 127.0.0.1    TLSv1.2 113 Application Data
479392  10.676516    127.0.0.1 -> 127.0.0.1    TLSv1.2 113 Application Data
479394  10.676547    127.0.0.1 -> 127.0.0.1    TLSv1.2 113 Application Data
479396  10.676578    127.0.0.1 -> 127.0.0.1    TLSv1.2 113 Application Data

100bytes程度と酷い細かさですね...これはダメです。

性能改善後(a266c7a7)

ということで、パケットに吐き出すために貯めるbufferサイズと吐き出す上限サイズに注目して改修を行いました。まずは結果です。

6 bytes 4096 bytes
152,453 req/s 62,004 req/s

かなり早くなりました。

パケットの状況

63591.496181 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63601.496317 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63611.496335 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63621.496350 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63631.496365 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63641.496381 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63651.496395 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63661.496480 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63671.496509 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63681.496538 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63691.496554 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63701.496568 127.0.0.1 -> 127.0.0.1 TLSv1.2 10789
63711.500462 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
63731.502580 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
63751.502676 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
63771.502779 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
63791.502874 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
63811.502956 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
63831.503039 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
63851.503121 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
63871.503201 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
63891.503287 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395

パケットサイズも良い感じです。

さらに設定ファイルからチューニング

trusterdには、今回新たに2種類のbufferサイズチューニング設定を追加しています。

  • write_packet_buffer_expand_size
    • TLSにおいてwrite bufferを貯めこむサイズを拡張
  • write_packet_buffer_limit_size
    • TLSにおいてwrite bufferからパケットを吐き出すサイズの上限を制限

上記の設定を使って、Optimizing TLS Record Size & Buffering Latency - igvita.comPerformance and the TLS Record Size « Mike's Lookoutを参考に一旦ざっくり4096bytesと以下のようなチューニングを行います。値について、1400から5000ぐらいを目安にチューニングすると良いんではないでしょうか。ついでにRLIMIT_NOFILEも増やしましょう。ちなみにMacOSXではRLIMIT_NOFILEはなんちゃMAX_SIZE=10240だかの数値以下の値じゃないとinvalid argumentを返しますのでご注意下さい。

:rlimit_nofile => 65535,
:write_packet_buffer_expand_size => 4096,
:write_packet_buffer_limit_size => 4096,

この設定で再度ベンチマークを行うと、以下の結果となりました。

6 bytes 4096 bytes
156, 979 req/s 31,408 req/s

残念ながら4kの結果は遅くなってしまいましたが、6bytesのベンチマーク結果はさらに5千req/s程早くなりました。

パケット状況

80640.974751 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80650.974775 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80660.974787 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80670.974799 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80680.974809 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80730.980858 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80750.980953 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80760.980977 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80770.980994 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80780.981003 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80790.981011 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80800.981019 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80810.981026 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80820.981033 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80830.981042 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80910.989255 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80920.989334 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80930.989346 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80940.989355 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
80980.989363 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
81040.989372 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162
81050.989380 127.0.0.1 -> 127.0.0.1 TLSv1.2 4162

う、美しい...

まとめ

ということで、今回はTLSのrecord sizeおよびパケットサイズが小さすぎる事が原因でおこるTLS接続時の性能問題を解決しました。

というのも、今回この問題を見直しすぐに実装できたのは、tatsuhiro_tさんのベンチマーク情報(+ヒント)とたまたま実家に帰っていて子供を両親が見てくれていたため作業に専念できた事が大きいです。

ありがとうございました。

おまけ

ちなみにH2OとnghttpdのTLSパケットもとても美しいので参考資料まで載せておきます。

H2O TLSパケット

108370  11.008217 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
108371  11.008253 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805
108372  11.008446 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805
108373  11.008449 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
108374  11.008605 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
108375  11.008618 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805
108376  11.008838 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
108377  11.008853 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805
108378  11.009029 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
108379  11.009044 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805
108380  11.009243 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
108381  11.009269 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805
108382  11.009465 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805
108383  11.009642 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805
108384  11.009643 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
108385  11.009831 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
108386  11.009864 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805
108387  11.009913 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395
108388  11.010051 127.0.0.1 -> 127.0.0.1 TLSv1.2 4805
108389  11.010058 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395

nghttpd TLSパケット

122421.827698 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 
122441.828146 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 
122451.828359 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 
122471.828946 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 
122481.829155 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 
122501.829585 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 
122511.829824 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 
122531.830248 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 
122541.830455 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 
122561.830918 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 
122571.831129 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 
122591.831537 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 
122601.831783 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 
122621.832206 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 
122631.832412 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 
122651.832952 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 
122661.833157 127.0.0.1 -> 127.0.0.1 TLSv1.2 1395 
122681.833575 127.0.0.1 -> 127.0.0.1 TLSv1.2 5095 

また、truserdのチューニング無しで同環境でのH2OとnghttpdとのTLSベンチマーク比較は以下になりました。

server 6 bytes 4K bytes
h2o 187,598 65,747
nghttpd 140,788 54,740
trusterd 152,453 62,004

とりあえず改善と言って良い数値が出たように思います。