Feature #1333

まともなHTTPを話せるようにする

Added by Kouhei Sutou about 6 years ago. Updated over 4 years ago.

Status:完了Start date:06/01/2012
Priority:NormalDue date:
Assignee:Kouhei Sutou% Done:

0%

Category:-
Target version:groonga-nginx-1

Description

h2. 問題

groongaのHTTPサーバーは必要最小限の機能しかもっておらず(ただしやっていることが少ないため速い)、POSTや認証機能などHTTPで使える便利な機能が使えなくて不便である。

h2. 期待する状態

ちゃんとしたHTTP機能を提供する。ただし、速度は落とさない。

速度性能の面で現状のgroonga httpサーバと同等かそれ以上のものを狙い、かつhttpの仕様を完全に満たすことがゴールになる。

h2. 解決方法

groongaのHTTPサーバーをnginxベースにする。 イメージはPassengerのstandaloneモード。

具体的には、まず、groonga用のnginxモジュールを作る。そして、nginxをgroongaのソースコードにバンドルして、groonga用nginxモジュール付きでビルドする。できあがったバイナリをgroonga-httpdという名前にしてインストールする。

Apacheモジュールとすることもできそうだけど、Apacheよりnginxの方が性能がでるので、nginxの方がよさそう。

nginx公式サイトで使えるかも程度で紹介されているこれを使うかを決める: https://github.com/simpl/ngx_devel_kit

うる覚えだけど、keep aliveが無くて、nginxにはあるからいいという話もあったはず。keep alive性能も調べるべきか?(abだと-kオプション)

性能試験の指標

同時接続数は10kくらいまで試す。

  • 検索処理が重い大量リクエスト (groongaの検索処理がCPU数までスケールするかを確認する)
  • 検索結果のデータ量が大きいリクエスト (groongaのHTTPサーバーとnginxのHTTPのデータ通信のスループットを確認する)
  • 軽いリクエスト (個別のHTTPリクエストの処理のオーバーヘッドを確認する)

h2. 参考資料

zeromqとnginxはうまく噛み合わないかもしれない(参考: http://lists.zeromq.org/pipermail/zeromq-dev/2010-March/002748.html)

似ているnginxモジュール

groonga.txt Magnifier (19 KB) Ryo Onodera, 06/07/2012 04:25 pm

nginx.txt Magnifier (19.3 KB) Ryo Onodera, 06/07/2012 04:25 pm

nginx-with-keep-alive.txt Magnifier (19.7 KB) Ryo Onodera, 06/07/2012 04:59 pm

groonga-normal-query-latency.txt Magnifier (1.15 KB) Ryo Onodera, 06/15/2012 06:03 pm

nginx-normal-query-latency.txt Magnifier (1.16 KB) Ryo Onodera, 06/15/2012 06:03 pm

groonga-fast-query-latency.txt Magnifier (1.21 KB) Ryo Onodera, 06/15/2012 06:27 pm

nginx-fast-query-latency.txt Magnifier (1.23 KB) Ryo Onodera, 06/15/2012 06:27 pm

History

#1 Updated by daijiro MORI almost 6 years ago

  • Target version changed from release-2.0 to groonga-nginx-1

#2 Updated by Kouhei Sutou almost 6 years ago

  • Assignee set to Ryo Onodera

#3 Updated by Ryo Onodera almost 6 years ago

  • Start date changed from 04/05/2012 to 06/01/2012

#4 Updated by Ryo Onodera almost 6 years ago

  • Status changed from 新規 to 担当者作業中

#5 Updated by Ryo Onodera almost 6 years ago

過去のnroonga-httpdの性能評価の比較 (nroonga-httpdのリビジョンはd39285fefc61b8d8c020c5a96c289298d9706ea4あたり)

groonga -s --protocol http (on 14001 port)
vs
nroonga-httpd (on 3000 port)

$ ab -n 10000 http://localhost:10041/d/status
Requests per second:    3942.68 [#/sec] (mean)

$ ab -n 10000 -c 100 http://localhost:10041/d/status
Requests per second:    24626.42 [#/sec] (mean)

$ ab -n 10000 http://localhost:3000/d/status
Requests per second:    757.48 [#/sec] (mean)

$ ab -n 10000 -c 100 http://localhost:3000/d/status
Requests per second:    1142.58 [#/sec] (mean)

#6 Updated by Ryo Onodera almost 6 years ago

  • Description updated (diff)

#7 Updated by Ryo Onodera almost 6 years ago

  • Description updated (diff)

#8 Updated by Ryo Onodera almost 6 years ago

  • Description updated (diff)

#9 Updated by Ryo Onodera almost 6 years ago

  • Description updated (diff)

#10 Updated by Ryo Onodera almost 6 years ago

  • Description updated (diff)

#11 Updated by Ryo Onodera almost 6 years ago

  • Description updated (diff)

#12 Updated by Ryo Onodera almost 6 years ago

  • Description updated (diff)

#13 Updated by Ryo Onodera almost 6 years ago

  • Description updated (diff)

#14 Updated by Ryo Onodera almost 6 years ago

  • Description updated (diff)

#15 Updated by Ryo Onodera almost 6 years ago

  • Description updated (diff)

#16 Updated by Ryo Onodera almost 6 years ago

デフォルトのkernel設定では同時接続数10Kの性能は比較できないので設定をいじる必要がある。

http://gwan.ch/en_apachebench_httperf.html

#17 Updated by Ryo Onodera almost 6 years ago

  • File groonga.txt added
  • File nginx.txt added

#18 Updated by Ryo Onodera almost 6 years ago

  • File deleted (groonga.txt)

#19 Updated by Ryo Onodera almost 6 years ago

  • File deleted (nginx.txt)

#20 Updated by Ryo Onodera almost 6 years ago

#22 Updated by Ryo Onodera almost 6 years ago

h2. 性能評価環境

groonga
  バージョン: 2.0.3
  リビジョン: 848a3391 (開発版)
  groongaサーバーの変更点:
    nginxサーバーと合わせるため、print_return_codeを呼ばなくした。
  nginxサーバーの変更点:
    デバック用のprintfを無くした。評価中に気づいた余計なmemcpyを無くした。
nginx
  バージョン: 1.3.1
  リビジョン: r4677 (開発版)
コンパイラ
  バージョン: 4.3.2 (Debian 4.3.2-1.1)
  最適化オプション: -O2
groongaサーバーの設定(コマンド引数経由)
  -s --protocol http --max-threads 8
nginxサーバーの設定(設定ファイル経由)
  worker_processes 8;
  worker_connections 16384;
  tcp_nopush on;
  access_log off;

h2. 性能評価結果

h3. まとめ

基本的には、QPS的にはgroongaサーバーもnginxサーバーもほぼ同じ性能だった。どちらも同時接続数が増えるに従ってCPUリソースを使い切っていった。これはabの実行結果からは分からないが、topで確認している。

全体的な傾向としては同時接続数が多い時は若干groongaサーバーのほうが速くなっていた。

また、同時接続数が多くなるほど、nginxサーバーの個々のクエリのレスポンス時間のばらつきが開いた。

nginxのマスタープロセス、ワーカープロセス間でのリクエストの割り振り方に問題があると考えられる。この問題の解決方法としては、nginx本体ではなくて、nginxのgroongaモジュールでリクエストの割り振りをするといいかもしれない。

また、ドリルダウンを使ったCPU負荷の高いクエリではgroongaサーバーで性能がスケールしないことも分かった。

補足として行ったKeep-Aliveを有効にした上での性能評価では、全体的にnginxサーバーは速くなっていた。

また、Keep-Alive無しと比較し、同時接続数が多い時のレスポンス時間のばらつきが解消されている場合があった。しかし、Keep-Alive有りでは、レスポンス時間のばらつきが、新しく発生している場合もあった。

このことからTCPのコネクションが維持され続ける場合でも、リクエストの割り振り方に問題があると考えられる。

h3. 普通のクエリの結果 (normal_query)

groongaサーバー、nginxサーバーともにほぼ同じ性能がでた。同時接続数が少ないときは、若干nginxが速く、同時接続数が多いときは、若干groongaサーバーが速かった。

同時接続数が多いとき、nginxサーバーのレスポンス時間にはばらつきがあった。

h3. 静的ファイルの結果 (static_file)

具体的に、ここでの静的ファイルとはgroongaのWebベースの管理ページのHTMLファイルである。

同時接続数がどの場合でも静的ファイルを返す場合は、groongaサーバーよりもnginxサーバーのほうが速かった。しかし、同時接続数が増えるにつれて速さの違いは無くなっていった。具体的には、同時接続数が1の時は1.6倍、同時接続数がCPUコア数(8個)と同じ時は1.3倍、同時接続数が多い時は1.2倍、それぞれ速くなっていた。

同時接続数が多いとき、nginxサーバーのレスポンス時間にはばらつきがあった。

h3. 非常に簡単なクエリの結果 (fast_query)

非常に簡単なクエリとして具体的には/d/statusを用いた。groongaサーバー、nginxサーバーともにほぼ同じ性能がでた。

同時接続数が多いとき、nginxサーバーのレスポンス時間にはばらつきがあった。

一つ懸念点として、一部のnginxサーバーからのレスポンスのContent-Lengthが正しくないことがあった。これはabが性能測定を通してContent-Lengthの値が一定であることを期待しているため。特に問題無いと考えられる。 (http://d.hatena.ne.jp/hightune/20080724/p1 の 4. 動的ページに注意しよう より)

h3. CPU負荷の高いクエリの結果 (cpu_bound_query)

ドリルダウンなどの非常に重い(処理に160msくらいかかる)クエリの場合は以下のような性能の違いが分かった。

同時接続数が1の場合、groongaサーバー、nginxサーバーはともに同じ性能だった。

同時接続数がCPUコア数(8個)と同じ場合、groongaサーバーはあまり速くならなかった(qpsが1.5倍になっただけ)。nginxサーバーでは、コア数分だけ速くなった(qpsが8倍)。

同時接続数が多い場合は、nginxサーバーよりも、groongaサーバーの方が速かった(約1.3倍)。

h3. ネットワーク負荷の高いクエリの結果 (network_bound_query)

limitに大きな値を指定したレスポンスが大きいクエリ(1.7MbyteくらいのJSON)の場合は以下のような性能の違いが分かった。

同時接続数が1の場合、groongaサーバー、nginxサーバーはともに同じ性能だった。

同時接続数が少ない(8接続)場合、nginxサーバーの方が速かった(約1.2倍)。

同時接続数が多い場合、groongaサーバーの方が速かった(約1.1倍)。

#23 Updated by Ryo Onodera almost 6 years ago

h2. CPU負荷の高いクエリでnginxサーバーが速くてgroongaサーバーが遅い理由の調査

h3. 調査結果

調査の結果、grn_ra_ref及びgrn_ra_unrefがボトルネックになっているためと考えられる。

h3. 理由

以下のabの実行結果より、実行にかかった時間は、nginxサーバー(約30秒)と比較してgroongaサーバー(約150秒)は、5倍程度遅くなっている。これはバイナリイメージごとのプロファイル結果のサンプル数が、nginxサーバー(約400万)と比較して、groongaサーバー(約2000万)は5倍多くなっていることからも確認できる。よってこのプロファイル結果の信頼性は妥当であると考えられる。

また、実行時間とCPUのサンプル数が比例関係にあることから、ボトルネックはIOやネットワークではなくCPUであることが分かる。しかし複数あるCPUコアを本当に使い切っているかまではこのプロファイル結果からは分からない。簡略的ではあるが、topコマンドですべてのCPUコアが使いきられていることは確認している。

シンボルごとのプロファイル結果を比較すると、groongaサーバーのプロファイル結果に、nginxサーバーのプロファイル結果には無い、シンボルが突出している。具体的には、grn_ra_ref、grn_ra_unref、grn_ra_unref、grn_ja_refの3つである。

これらのシンボルに対しソースコードレベルのプロファイル結果を調査したところ、grn_ja_refは顕著なボトルネックにはなっていないようだったが、grn_ra_unrefとgrn_ra_unrefは、GRN_IO_SEG_UNREFマクロ付近で非常に多いサンプル数となっている。

以上のことから、ボトルネックは、grn_ra_unrefとgrn_ra_unrefであると考えられる。

h3. nginxサーバー

abの実行結果

Time taken for tests:   30.036 seconds
Time per request:       180.213 [ms] (mean)

シンボルごとのプロファイル結果

$ opreport --merge all --threshold 1 --symbols

samples  %        app name                 symbol name
1017558  26.3293  libgroonga.so.0.0.0      grn_hash_add
496692   12.8519  libgroonga.so.0.0.0      grn_table_group
451409   11.6802  libgroonga.so.0.0.0      grn_obj_get_value
342487    8.8618  libgroonga.so.0.0.0      grn_ja_ref
184020    4.7615  libgroonga.so.0.0.0      grn_ra_ref
168122    4.3502  libgroonga.so.0.0.0      grn_hash_cursor_next
141222    3.6541  libgroonga.so.0.0.0      grn_io_win_map2
133516    3.4547  libc-2.11.3.so           memcpy
125332    3.2430  libgroonga.so.0.0.0      grn_table_add_subrec_inline
113371    2.9335  libgroonga.so.0.0.0      grn_bulk_write
107147    2.7724  libgroonga.so.0.0.0      grn_ctx_at
93328     2.4149  libgroonga.so.0.0.0      grn_obj_get_range

バイナリイメージごとのプロファイル結果

$ opreport --merge all --threshold 1

  samples|      %|
------------------
  3739487 94.9076 libgroonga.so.0.0.0
   138634  3.5185 libc-2.11.3.so
    51811  1.3150 no-vmlinux 

h3. groongaサーバー

abの実行結果

Time taken for tests:   149.523 seconds
Time per request:       897.138 [ms] (mean)

シンボルごとのプロファイル結果

$ opreport --merge all --threshold 1 --symbols

samples  %        app name                 symbol name
7222296  36.1516  libgroonga.so.0.0.0      grn_ra_ref
6098529  30.5265  libgroonga.so.0.0.0      grn_ra_unref
2221804  11.1214  libgroonga.so.0.0.0      grn_ja_ref
959052    4.8006  libgroonga.so.0.0.0      grn_hash_add
602065    3.0137  libgroonga.so.0.0.0      grn_table_group
534368    2.6748  libgroonga.so.0.0.0      grn_obj_get_value
421034    2.1075  libgroonga.so.0.0.0      grn_io_win_map2
382327    1.9138  libgroonga.so.0.0.0      grn_ja_unref
219387    1.0982  libgroonga.so.0.0.0      grn_ja_get_value
202290    1.0126  libgroonga.so.0.0.0      grn_hash_cursor_next

バイナリイメージごとのプロファイル結果

$ opreport --merge all --threshold 1

  samples|      %|
------------------
 20025452 98.4292 libgroonga.so.0.0.0

ソースコードのプロファイル結果

以下から分かるように、grn_ra_refとgrn_ra_unrefの中のGRN_IO_SEG_REFで時間がかかっていることが確認できる。

$ opannotate --merge all --threshold 1 --source

                :void *
                :grn_ra_ref(grn_ctx *ctx, grn_ra *ra, grn_id id)
  18271  0.0900 :{ /* grn_ra_ref total: 7222296 35.5664 */
                :  void *p = NULL;
                :  uint16_t seg;
   6521  0.0321 :  if (id > GRN_ID_MAX) { return NULL; }
   4382  0.0216 :  seg = id >> ra->element_width;
7133337 35.1283 :  GRN_IO_SEG_REF(ra->io, seg, p);
    707  0.0035 :  if (!p) { return NULL; }
  24981  0.1230 :  return (void *)(((byte *)p) + ((id & ra->element_mask) * ra->header->element_size));
  34097  0.1679 :}

                :grn_rc
                :grn_ra_unref(grn_ctx *ctx, grn_ra *ra, grn_id id)
                :{
                :  uint16_t seg;
  30528  0.1501 :  if (id > GRN_ID_MAX) { return GRN_INVALID_ARGUMENT; } /* grn_ra_unref total: 6098529 29.9797 */
  15386  0.0756 :  seg = id >> ra->element_width;
2469150 12.1381 :  GRN_IO_SEG_UNREF(ra->io, seg);
                :  return GRN_SUCCESS;
3583465 17.6159 :}

#24 Updated by Ryo Onodera almost 6 years ago

  • Description updated (diff)

#27 Updated by Ryo Onodera almost 6 years ago

  • Assignee changed from Ryo Onodera to Kouhei Sutou

#28 Updated by Ryo Onodera almost 6 years ago

6月29日にリリースされた最初のgroonga-httpdのリリースで残っている課題は以下の通り

  • locationの複数対応 (一つのnginxサーバーで複数のデータベースへのアクセスを可能にする
  • データベースにアクセスときの権限。nginxはrootで実行されるとワーカープロセスをnobody:nogroupにして権限を落として実行する。なのでデータベースファイルのオーナーと違うとうまくいかない。
  • レスポンスをストリーミングにするには、ただのnginx moduleからupstream nginx moduleに切り替える必要がある。それにはmysqlのnginxモジュールとかpostgresqlのnginxモジュールを参考にする必要がある。
  • groonga APIで外に出してほしい関数がある(grn_ctx_recv_handler_setとか)
  • groongaの--max-threadsが動いていないかもしれない。

#29 Updated by Kouhei Sutou over 5 years ago

これはできた。

  • locationの複数対応 (一つのnginxサーバーで複数のデータベースへのアクセスを可能にする
  • groonga APIで外に出してほしい関数がある(grn_ctx_recv_handler_setとか)

なので、残りはこれ。

  • データベースにアクセスときの権限。nginxはrootで実行されるとワーカープロセスをnobody:nogroupにして権限を落として実行する。なのでデータベースファイルのオーナーと違うとうまくいかない。
  • レスポンスをストリーミングにするには、ただのnginx moduleからupstream nginx moduleに切り替える必要がある。それにはmysqlのnginxモジュールとかpostgresqlのnginxモジュールを参考にする必要がある。
  • groongaの--max-threadsが動いていないかもしれない。

#30 Updated by daijiro MORI over 4 years ago

  • Status changed from 担当者作業中 to 完了

Also available in: Atom PDF