豪鬼メモ

一瞬千撃

DBサービスを作ろう その4 gRPCの性能測定

Tkrzw-RPCの基本機能を実装したので、バージョン0.5.0をリリースした。簡易的な性能測定をして、gRPCのオーバーヘッドについて考えてみる。


gRPCの性能測定をしよう。典型的なユースケースを再現すべく、データベースは200万バケットのHashDBMを用いる。よって、以下のようにサーバを立てる。サーバとクライアントは同一のマシンで動かして、ネットワーク層には、最速であるUNIXドメインソケットを用いる。

$ tkrzw_server --address "unix:$HOME/tkconn" "casket.tkh#num_buckets=2m"

クライアントは以下のコマンドを実行する。実際にはデータベースにはアクセスせずに、8バイトのデータを送ったらそれをそのまま送り返すEchoというメソッドを実行する。それに加えて、8バイトのキーと8バイトの値を持つレコードを格納するSetメソッドと、個々のレコードを取り出すGetメソッドと、個々のレコードを参照するイテレータ作ってキーと値を取得するIterateメソッドと、レコードを削除するRemoveメソッドの性能を測定する。個々の操作は100万回行う。クライアント側のスレッド数は1、2、4と変えるが、複数スレッドの場合には、スレッド間で同一コネクションを使い回す場合と別々のコネクションを使う場合に分けて測定する。

$ tkrzw_dbm_remote_perf sequence --address "unix:$HOME/tkconn" --threads 1 --iter 1000000
$ tkrzw_dbm_remote_perf sequence --address "unix:$HOME/tkconn" --threads 2 --iter 500000
$ tkrzw_dbm_remote_perf sequence --address "unix:$HOME/tkconn" --threads 2 --iter 500000 -separate
$ tkrzw_dbm_remote_perf sequence --address "unix:$HOME/tkconn" --threads 4 --iter 250000
$ tkrzw_dbm_remote_perf sequence --address "unix:$HOME/tkconn" --threads 4 --iter 250000 -separate

結果は以下のようになる。単位はQPS(クエリ毎秒)である。

Echo Set Get Iterate Remove
1スレッド 22271 21113 20671 13249 20161
2スレッド(同ソケット) 27400 26804 26190 15009 24942
2スレッド(別ソケット) 28233 27060 25449 16110 25232
4スレッド(同ソケット) 31293 29761 28792 13648 24885
4スレッド(別ソケット) 31351 29690 27522 16044 25362

IPv4でも同様の実験を行う。サーバ側、クライアント側とも、アドレス指定を "127.0.0.1:1978" に変えたコマンドを実行する。

Echo Set Get Iterate Remove
1スレッド 18412 17370 17862 11807 17634
2スレッド(同ソケット) 27262 22440 23176 13139 22670
2スレッド(別ソケット) 26240 21816 23236 13332 22666
4スレッド(同ソケット) 28071 25192 19548 12150 20694
4スレッド(別ソケット) 26730 22232 23214 13158 19505

IPv6でも同様の実験を行う。サーバ側、クライアント側とも、アドレス指定を "[::1]:1978" に変えたコマンドを実行する。

Echo Set Get Iterate Remove
1スレッド 18108 16754 17019 11577 16648
2スレッド(同ソケット) 27370 22008 23601 12574 23177
2スレッド(別ソケット) 27372 22667 23515 12148 22451
4スレッド(同ソケット) 25661 22147 20092 11185 19207
4スレッド(別ソケット) 25576 22341 19496 13052 20867

結果を考察しよう。まず、Echoの結果から、ネットワーク層だけの性能を比較すると、UNIXドメインソケットの方がIPv4IPv6より速いことが分かる。1スレッドの結果から言えば、UNIXドメインソケットのラウンドトリップはIPv4IPv6よりも20%ほど高速だと言えそうだ。ラウンドトリップのレイテンシは、UNIXドメインソケットで44マイクロ秒、TCP/IPで54マイクロ秒ということになろうか。スレッド数を増やすと、スループットの差は小さくなるが、それでも視認できる差は存在する。よって、ローカルで完結するサービスの場合にはUNIXドメインソケットを使うのが有用と言えそうだ。IPv4IPv6の差は誤差以上のものは見られないので、どちらを使ってもよいだろう。

EchoとSet/Get/Removeを比べると、当然、Echoの方が速い。この差がデータベース処理によるオーバーヘッドと言える。ローカルの性能測定結果から考えると、データベース操作のオーバーヘッドはもっと小さいことを予想していたが、意外にも視認できる差が出た。とはいえ、1スレッドだとSetのスループットはEchoのそれの95%であり、RPCのオーバーヘッドの方が支配的ではある。同じ理由で、SetとGetとRemoveの差は小さい。Iterateが他に比べて遅いのは、ストリームAPIの中で、JumpとGetという2つのメソッドを呼んでいるからである。

スレッド数を増やすと、どの実験でも微妙にだけスループットが向上する。サーバとクライアントが同じマシンで動いているので、クライアントのスレッド数を増やすとサーバにとっての負荷も上がってしまうのが、微増しかしない理由の一つだろう。実際のサーバの限界スループットを引き出すには、クライアント用のマシンを多数用意しなければならないが、それについては追って考える。

スレッド間で同じコネクション(RemoteDBMオブジェクト)を使い回すのと、スレッド毎に異なるコネクションを使うのとで差が出るかということだが、4スレッド程度だと有意な差は見られなかった。全体のコネクションの数が増えればサーバ側の負荷も増えるため、クライアント側ではできるだけコネクションを使い回す方がよいだろう。

今回使ったAPIは、最も単純である、同期かつユナリーな実装である。それでも2万QPSくらいでデータベース操作ができることは分かった。これが早いと捉えるか遅いと捉えるかはユースケース次第だが、個人的には遅いと捉えている。ローカルでは400万QPSが出せるデータベースなのに、リモートだと2万QPSしか出せていないというのは勿体無い。実験方法がしょぼいというのが遅い理由の一つではあるが、APIの設計も原因の一つである。gRPCのPerformance Best Practicesに述べてあるように、より高いスループットを得るには、非同期やストリームの方式のAPIを検討する価値があるだろう。


まとめ。Tkrzw-RPCの簡易的な性能測定をして、同一マシン上なら2万QPSくらいのスループットが出せることが分かった。これをベースラインにして、今後、より高いスループットを出す方法について検討していくことになる。