RustでgRPCとScyllaDBを使ってみた

こんにちは、今回はRustでgRPCサーバを実装し、ScyllaDBと連携する方法を紹介します。最近、マイクロサービスアーキテクチャや高性能なデータ処理が求められる場面が増えてきています。その中で、gRPCとScyllaDBを組み合わせることで、高速かつスケーラブルなサービスを構築することができます。

## gRPCとは

gRPCはGoogleが開発したRPC（Remote Procedure Call）フレームワークで、高性能かつ双方向の通信を可能にします。Protocol Buffersを使用してインターフェースを定義し、各種プログラミング言語で自動的にコード生成が可能です。

より詳細な説明は、以下のリンクを参照してください。
- [gRPC](https://grpc.io/)
- [gRPC vs REST: Understanding gRPC, OpenAPI and REST and when to use them in API design | Google Cloud Blog](https://cloud.google.com/blog/products/api-management/understanding-grpc-openapi-and-rest-and-when-to-use-them?hl=en)
- [マイクロサービス × gRPCの すゝめ #grpc-gateway - Qiita](https://qiita.com/ohs30359-nobuhara/items/d774cadb3b9efafb7d69)
- [マイクロサービス間通信におけるgRPCとRESTの比較 #REST-API - Qiita](https://qiita.com/hikarunakatani/items/06f4e3e6ef97858094e5)

## ScyllaDBとは

ScyllaDBは高性能なNoSQLデータベースで、Apache Cassandraと互換性があります。低レイテンシと高スループットを実現しており、大規模なデータ処理に適しています。

より詳細な説明は、以下のリンクを参照してください。
- [ScyllaDB | Monstrously Fast + Scalable NoSQL](https://www.scylladb.com/)
- [Apache Cassandra vs. Redis vs. ScyllaDB Comparison](https://db-engines.com/en/system/Apache+Cassandra%3BRedis%3BScyllaDB)
- [NoSQL Database Comparison | ScyllaDB](https://www.scylladb.com/learn/nosql/nosql-database-comparison/)

## 実装概要

今回は、256次元のベクトルを保存・取得するgRPCサーバをRustで実装し、そのデータストアとしてScyllaDBを使用します。

### 全体の構成

1. **Protoファイルの定義**：`vector.proto`でgRPCのサービスとメッセージを定義します。
2. **Rustでサーバ実装**：`tonic`と`scylla`クレートを使用して、サーバを実装します。
3. **ScyllaDBのセットアップ**：Dockerを使用してScyllaDBを起動し、必要なキー空間とテーブルを作成します。

リポジトリはこちらです：[makinzm/hajimeteno-tonic](https://github.com/makinzm/hajimeteno-tonic)

## 実装の詳細

### Protoファイルの定義

[`src/proto/hello.proto`](https://github.com/makinzm/hajimeteno-tonic/blob/main/src/proto/hello.proto)では、以下のサービスとメッセージを定義しています。

```protobuf
syntax = "proto3";

package vector;

service VectorService {
  rpc GetVector (VectorRequest) returns (VectorResponse);
  rpc InsertVector (InsertVectorRequest) returns (InsertVectorResponse);
  rpc GetVectorByKey (GetVectorByKeyRequest) returns (VectorResponse);
  rpc InsertSample (InsertSampleRequest) returns (InsertSampleResponse);
}

message VectorRequest {
  int32 id = 1;
}

message VectorResponse {
  int32 id = 1;
  repeated float vector = 2;
}

message InsertVectorRequest {
  string key = 1;
  repeated float vector = 2;
}

message InsertVectorResponse {
  bool success = 1;
}

message GetVectorByKeyRequest {
  string key = 1;
}

message InsertSampleRequest {
  int32 id = 1;
}

message InsertSampleResponse {
  bool success = 1;
}
```

### サーバの実装

[`src/main.rs`](https://github.com/makinzm/hajimeteno-tonic/blob/main/src/main.rs)でサーバの実装を行います。

package名が`vec`であるprotoファイルを[`build.rs`](https://github.com/makinzm/hajimeteno-tonic/blob/main/build.rs)でビルドしているため、`tonic::include_proto!`マクロを使用して、`vector`パッケージをインクルードすることができます。

```rust
pub mod vector {
    tonic::include_proto!("vector");
}
```

:::note warning
ここで`pub mod`の名称は,protoファイルのpackage名と一致している必要があります。（次の変更を加えると失敗することに注意してください: [failure example by makinzm · Pull Request #1 · makinzm/hajimeteno-tonic](https://github.com/makinzm/hajimeteno-tonic/pull/1/files)）
:::

- [include_proto in tonic - Rust](https://docs.rs/tonic/latest/tonic/macro.include_proto.html)
- [compile_protos in tonic_build - Rust](https://docs.rs/tonic-build/latest/tonic_build/fn.compile_protos.html)

### ScyllaDBのセットアップ

ScyllaDBをDockerで起動します。

```shell
docker pull scylladb/scylla:latest
docker run --name scylladb -d -p 9042:9042 scylladb/scylla:latest
```

キー空間とテーブルを作成します。

```shell
docker exec -it scylladb cqlsh
```

```sql
CREATE KEYSPACE IF NOT EXISTS vector_keyspace WITH replication = {'class': 'SimpleStrategy', 'replication_factor': 1};

CREATE TABLE IF NOT EXISTS vector_keyspace.vectors (
    id int PRIMARY KEY,
    key text,
    vector list<float>
);
```

### サーバの起動

Rustの環境をセットアップし、サーバをビルド・起動します。

```shell
cargo build
cargo run
```

## 動作確認

`grpcurl`を使用して、gRPCサーバにリクエストを送信します。

### サンプルデータの挿入

```shell
grpcurl -plaintext -import-path src/proto -proto vector.proto -d '{
  "id": 100
}' localhost:50051 vector.VectorService/InsertSample
```

期待されるレスポンス：

```json
{
  "success": true
}
```

### ベクトルの取得

```shell
grpcurl -plaintext -import-path src/proto -proto vector.proto -d '{
  "id": 100
}' localhost:50051 vector.VectorService/GetVector
```

取得結果(とても長いので一部抜粋しています)

```shell
{
  "id": 100,
  "vector": [
    0.48001575,
    0.6714103,
    0.16986275,
(中略していますが、実際は256次元のベクトルが返却されます)
    0.82636607
  ]
}
```

## まとめ

今回、RustでgRPCサーバを実装し、ScyllaDBと連携する方法を紹介しました。gRPCとScyllaDBを組み合わせることで、高性能でスケーラブルなサービスを構築できます。コードの各部分で気をつけるべき点や、実際の動作確認方法も含めて解説しました。

### 今後の展望

- **認証機能の追加**：セキュアな通信を実現するために、TLSや認証機能の追加。
- **負荷試験の実施**：大量のリクエストを送信して、サーバの性能を評価。