豪鬼メモ

一瞬千撃

gRPCのJavaクライアントライブラリ

データベースライブラリTkrzwのgRPCサービスにJavaで接続して操作するためのライブラリを実装した。これにて、当初予定していたC++JavaPythonRuby、Goの全てを網羅したので、gRPCのクライアントライブラリを書きまくる旅は終了だ。この記事では、簡単な使い方と性能評価を紹介する。Echo操作の結果を1、2、4スレッドで変えてスループットを測定した場合の結果は以下のようになる。
f:id:fridaynight:20211026011911p:plain


前回の記事にて、gRPCのJavaアプリケーションを手動でビルドする手順を確立した。あとはそれをMakefileで自動化すればパッケージが作れる。gRPCの依存パッケージはバイナリパッケージからインストールするのが楽だ。Ubuntuの場合、aptで以下のパッケージを入れればよい。

  • libprotobuf-java
  • libgrpc-java
  • protobuf-compiler-grpc-java-plugin
  • libgoogle-common-protos-java
  • libguava-java
  • libnetty-java
  • libperfmark-java

サーバを動かすシステムには、TkrzwTkrzw-RPCをインストールする必要がある。クライアント側のシステムには必ずしもTkrzwとTkrzw-RPCをインストールする必要はない。とはいえ今回はサーバと組み合わせてテストするので、TkrzwとTkrzw-RPCはインストールしてあるものとする。それぞれソースパッケージを落として、make && make install すれば入るはずだ。クライアントで側は、上述の依存パッケージを入れた上で、Tkrzw-RPCのJavaパッケージを make && make install すればよい。

Tkrzw-RPCのサーバは以下のコマンドで起動できる。デフォルトでは、オンメモリのデータベースを起動する。デバッグログも表示するようにしよう。

$ tkrzr_server --log_level debug

Javaの最も簡単なクライアントアプリケーションは、以下のようなコードになる。ローカルのTkrzwではDBMクラスを使っていたが、その代わりにRemoteDBMクラスを用いる。また、ファイルを開いたり閉じたりするopen/closeメソッドの代わりにネットワークコネクションを開いたり閉じたりするconnect/disconnectメソッドを用いる。それ以外のAPIはほとんどローカルと同じである。

// Tkrzw-RPCのシンボルをインポートする
import tkrzw_rpc.*;

public class Example1 {
  // メイン関数
  public static void main(String[] args) {
    // データベースサーバへの接続を準備する
    RemoteDBM dbm = new RemoteDBM();
    dbm.connect("localhost:1978", -1);
    
    // レコードを格納する
    // キーと値は文字列で指定できるが、バイト列として格納される。
    dbm.set("first", "hop");
    dbm.set("second", "step");
    dbm.set("third", "jump");

    // レコードを検索する
    // 検索に失敗した場合、nullが返される
    System.out.println(dbm.get("first"));
    System.out.println(dbm.get("second"));
    System.out.println(dbm.get("third"));
    System.out.println(dbm.get("fourth"));

    // データベース内の各レコードに横断的にアクセスする
    // 作ったイテレータはdestructで破棄すること
    Iterator iter = dbm.makeIterator();
    iter.first();
    while (true) {
      String[] record = iter.getString();
      if (record == null) {
        break;
      }
      System.out.println(record[0] + ": " + record[1]);
      iter.next();
    }
    iter.destruct();

    // データベース接続を破棄する
    dbm.disconnect();
    dbm.destruct();
  }
}

細かいことはJava版のAPI文書をご覧いただきたい。以前の記事を参考にすれば、メッセージキューとしても使える。

以前やったベンチマークテストJava版の結果も加えよう。Echo、Set、Getの各操作を10万回行い、そのスループットを測る。Java版はUNIXドメインソケットをサポートしていないので、全言語でIPv4を使って測定しなおした。

1スレッドのスループットは以下。

Echo Get Set
C++ 20389 21111 20936
Java 17512 16786 16504
Python 9748 9300 9341
Ruby 11556 11476 11266
Go 14262 14148 14200

2スレッドのスループットは以下。

Echo Get Set
C++ 28025 27861 27800
Java 24830 23752 23969
Python 13160 12643 12319
Ruby 16752 16249 16128
Go 22670 22375 22420

4スレッドのスループットは以下。

Echo Get Set
C++ 34542 34399 34859
Java 34806 33169 33259
Python 10689 10307 10115
Ruby 15623 15287 14954
Go 32366 32250 32284

Echoの結果に着目すれば、これはTkrzw-RPCのベンチマークというより、素のgRPCのベンチマークとみなすことができる。ということで、gRPCのクライアントでC++に次いで高速なのはJavaということになった。4スレッド並列にすると性能はC++とほぼ同じになる。とはいえ、何度か述べていることだが、クライアント側はマシンの台数を増やしてスループットを上げられることが多いので、この結果を気にする必要はあまりない。サーバ側はC++で実装してスループットを最大化しつつ、クライアント側では多くの言語をサポートしてインターオペラビリティを向上させることが重要だ。