YCSB ~Yahoo! Cloud Serving Benchmark~ について（付. Couchbase Serverを使ったベンチマーク実行手順）

# はじめに

以前に書いた記事（[NoSQLベンチマーク紹介：MongoDB, Cassandra, そしてCouchbase](https://qiita.com/yoshiyuki_kono/items/7dc7188fb572e99d8425)」）で、Altros社によるNoSQLデータベースのベンチマーク結果を紹介しました。

本記事では、Altros社のベンチマークで用いられた[Yahoo! Cloud Serving Benchmark (YCSB)](https://en.wikipedia.org/wiki/YCSB)にフォーカスしながら、実際にCouchbase Serverに対して実行した手順を紹介します。


## 原論文
Brian F. Cooperをはじめとする著者によって、発表された原論文（[Benchmarking Cloud Serving Systems with YCSB](https://www2.cs.duke.edu/courses/fall13/compsci590.4/838-CloudPapers/ycsb.pdf)）では、以下のデータベースが対象とされていました。

- PNUTS
- BigTable
- HBase
- Cassandra
- Sharded MySQL

また、下記のGithubレポジトリで、実行のためのソースコード等が公開されています。
[https://github.com/brianfrankcooper/YCSB](https://github.com/brianfrankcooper/YCSB)

## ベンチマークシナリオ

| Workload   | タイトル                 | シナリオの内容 |  想定するアプリケーション |
|:----------:|:---------------------:|:----------|:-------------------------|
| A | Update heavy workload | 50%の読み込みと50%の書き込み | 最近のアクションを記録するセッションストア |
| B | Read mostly workload  | 95%の読み込みと5%の書き込み | 写真へのタグ付け。タグの追加は更新だが、ほとんどがタグの読み取り |
| C | Read only | 100%の読み込み | （外部で構築された）ユーザプロファイルのキャッシュ |
| D | Read latest workload | 新規追加データへの頻繁なアクセス | ユーザステータス更新 | 
| E | Short ranges | レンジスキャン |  スレッド化されたポスト（投稿）を管理するアプリケーション。スレッドIDでのアクセスに最適化されていることを想定 |
| F | Read-modify-write | データを読み込み、変更し、書き込む | ユーザを管理するデータベース（ユーザーアクティビティ管理等） | 


## Altros社ベンチマークでの利用範囲

Altros社のベンチマークでは、YCSBで定義されたワークロードA~Fのうち、A(Update heavy workload)とE(Short　ranges)のみを用い、以下のような独自のワークロードを定義・実行しています。

### クエリ１：ページネーション（OFFSETとLIMITによるフィルター）
財務アプリケーションを想定した、フィルター処理されたトランザクションを一覧表示するためのサーバー側のページネーション

### クエリ２：ジョイン（テーブル結合）
eコマースアプリケーションを想定した、顧客が利用するさまざまな製品やサービスに関する一連のレポートのためのクエリ

## あらかじめの結論

一般に、ベンチマークの中身（データ等の実行内容、利用API等の実行方法）をブラックボックスのまま、用いることには意味がない（実ビジネスには適用できない）というのは、周知の事実だと考えますが、上記の（YCSB以外のワークロードを実行する必要があると考えた）Altros社の判断を踏まえると、YCSBが当初目的としていたところと、現在のNoSQLのテクノロジーや利用想定範囲との間には、ギャップが存在していると理解するのが適切と思われます。
端的に、（比較対象とされたMySQLは別として）原論文に引かれたデータベースでは、テーブル結合のような処理は、ユースケースとして重要視されていなかったと考えられます。
特に、原論文に含まれていないドキュメント指向データベースである、MongoDBや、Couchbase Serverに対して、ベンチマークを行う場合には、Altros社のような考慮が重要だと考えます。
突き詰めて言えば、実際のアプリケーション要件を想定して、NoSQL（あるいは「Cloud Serving」データベース）のみではなく、RDB(NewRDB含め)と同じ俎上で、ベンチマークを行うことを考える必要があるだろう、というのが、現在の私の個人的な関心です。
その意味で次の章の内容は、副次な意味を持つものでしかありませんが、実行した内容の記録と、そこで気づいた注意点の共有のため記します。


# Couchbase ServerでのYCSB実行

## 利用環境

### ソースコード

今回は、上記のオリジナルのレポジトリではなく、そこからForkしたCouchbase Labsで管理されている下記のレポジトリを利用して、実行確認を行いました。

[https://github.com/couchbaselabs/YCSB](https://github.com/couchbaselabs/YCSB)

READMEの「Getting Started」には、コンパイル済み実行ファイル一式を利用して実行する手順が記載されていますが、このファイルを利用した場合、古いCouchbaseのクライアントライブラリが含まれているため（そのことに起因したエラーにより、今回試行した限りでは、新しいライブラリに入れ替える必要があったため）、後述のように、ソースコードからコンパイルする方法を紹介します。

### 実行環境

AWSの"t2.medium"タイプのEC2インスタンスに"RHEL-8.2.0_HVM-20200423-x86_64-0-Hourly2-GP2"AMIを適用した環境に、Couchbase 6.6をインストールした環境を利用し、１ノードのクラスターとして構築。Couchbaseのサービスとして、Data, Query, Indexのみを有効化。

コンパイル、実行のために、Python2(`ycsb`コマンドの中で、`python`として呼ばれるためのシンボリックリンク作成)、Java8の他、maven、git等適宜インストールしています。

### Couchbase実行準備

defaultバケットを作成し、そのバケットへのアクセス権限を持つユーザーを同名（default）で作成。このバケットに（簡易実行確認のために）下記のようにプライマリインデックスを作成した。(本来は、実行するクエリを意識したセカンダリインデックスを作成するべき)

```
CREATE PRIMALY INDEX default_primary ON default
```

## コンパイル

下記のように、クライアントを特定して、コンパイルを行えます。

```
$ mvn -pl com.yahoo.ycsb:couchbase2-binding -am clean package
```
## データ投入

`load`サブコマンド指定により、データを投入します。

```
bin/ycsb load couchbase2 -p couchbase.password=password -p couchbase.host=ip-10-0-0-39.us-east-2.compute.internal -s -P workloads/workloada
```

## ワークロード実行

`run`サブコマンド指定により、ワークロードを実行します。
処理を成功裏に終えるためにオプションを調整しています。後述の「注意点」の章もご参照ください。

### Workload A
```
$ bin/ycsb run couchbase2 -p couchbase.password=password -p couchbase.host=ip-10-0-0-148.us-east-2.compute.internal  -s -P workloads/workloada
```

### Workload B

```
$ bin/ycsb run couchbase2 -p couchbase.password=password -p couchbase.host=ip-10-0-0-148.us-east-2.compute.internal  -s -P workloads/workloadb
```

### Workload C
```
$ bin/ycsb run couchbase2 -p couchbase.password=password -p couchbase.host=ip-10-0-0-148.us-east-2.compute.internal -p couchbase.kv=false -s -P workloads/workloadc
```

### Workload D
```
$ bin/ycsb run couchbase2 -p couchbase.password=password -p couchbase.host=ip-10-0-0-148.us-east-2.compute.internal -p couchbase.kv=false -s -P workloads/workloadd -p couchbase.upsert=true
```

### Workload E
```
$ bin/ycsb run couchbase2 -p couchbase.password=password -p couchbase.host=ip-10-0-0-148.us-east-2.compute.internal -p couchbase.kv=false -s -P workloads/workloade -p couchbase.upsert=true
```

### Workload F
```
$ bin/ycsb run couchbase2 -p couchbase.password=password -p couchbase.host=ip-10-0-0-148.us-east-2.compute.internal  -s -P workloads/workloadf
```
## 注意点

Couchbase Serverでは、N1QLというSQLをJSONの操作に対して拡張したN1QLというクエリ言語が利用可能です。
N1QLは、標準SQLをサポートする一環として、INSERTステートメントをサポートしています。
ただし、この構文は、開発時に、ユーザが（コマンドライン）ツール（等）で利用するには、有用であるとは言え、実アプリケーションでデータを投入するために、JSONデータをVAUES句に含めたN1QLのインサート文を、アプリケーション内で文字列として構築の上、クエリサービスを介して、クエリを実行することは何重にもナンセンスです（例外は、JSONデータをアプリケーション側に取得せずに、直接データベースで新たなデータを作成するために「INSERT~SELECT」を利用するケースぐらいではないかと思われます）。

YCSBとの関連についていうと、YCSBのCouchbaseクライアントには、`couchbase.kv`というパラメータが用意されています。`kv`は、「Key-Value」の意味で、このパラメータを有効にすることによって、"mutation operations will also be performed through N1QL.(データ更新処理の際に、N1QLも利用される)"とされています。
「ミューテーション（データ更新）」とは、端的に言えば、SQLのUPDATEに相当するといって良いでしょう。Couchbaseの場合、キー（ドキュメントID）により、ドキュメントが特定される場合には、N1QLクエリ（サービスAPI）を実行する必要なく、データサービス（API）を利用して、ドキュメントの一部更新が可能です（SQLクエリで実行できるように、複数のドキュメントを対象として一度に同じ更新処理を実行することのできるAPIも存在します）。

ここで、厄介なのは、データベースの世界で「ミューテーション」と行った場合、更新、削除のような処理だけでなく、挿入処理も含まれます。ただし、YSCBのCouchbaseクライアントでWorkload A,B,Fのようなデータの書き込みが重要なシナリオでは、`-p couchbase.kv=true`オプションを指定した場合、INSERTによる処理実行に伴い下記のようなエラーとなります。

```shell
Caused by: com.couchbase.client.core.CouchbaseException: N1qlQuery Error - {"msg":"syntax error: invalid quoted string - at \"6;h61f93 %!](MISSING)c!?j>4h(L11O/%!N(MISSING)--8>?3 !5r!4n.$` 8:<^\\'&&4-=| Y9$[u:Pg\\'\\e=Ec2W{5X+\\'23\\\"f>E#5M#)S1:L{&#$6 t.\"","code":3000}
```

YCSBでは、データはランダムな文字が利用されており、SQL文として成立させるためのエスケープが行われていない結果として、このような結果となります。厳密に言えば、値をエスケープするか、`-p couchbase.kv=true`オプションをINSERT処理では無効とするか、あるいはプリペアードステートメントを利用して、値をパラメータとして指定するかの対応が行われているべきとも言えますが、利用が想定されていないケースとして、どちらの対応も行われずに実行時に矛盾のある部分が残っています。
例外として、Workload DやEでは、INSERT文が用いられていますが、プリペアードステートメントによる実装となっています（`couchbase.kv`オプションは、これらのワークロードのために導入された経緯と推測されます）。とは言え、KVによる処理（ドキュメント中の値による検索の必要がなく、キーのみによる処理）が実行可能な場面で、N1QLクエリを用いてベンチマークを行うことは、やはりミスリーディングでしかないと考えます。


# 参考情報

[YCSBでNoSQLのベンチマーク　その1](https://imai-factory.hatenablog.com/entry/2013/03/24/174624)

[YCSBでNoSQLのベンチマーク　その2](https://imai-factory.hatenablog.com/entry/2013/04/05/010858)

２０１３年発表。コマンドパラメータや、workloadファイル設定内容、出力結果の読み方など、上記レポジトリを使う際のガイドとして有益な内容が整理されている。

[ベンチマークツール YCSB メモ](https://cloudpack.media/10837)

2015年発表。RedisとCassandraでの実行例を含む。

[YCSBでDynamoDBを殴ってみた](https://dev.classmethod.jp/articles/attack_to_dynamodb_using_ycsb/)

2015年発表。


[YCSBの概要とCassandraへのベンチマーク](https://qiita.com/noralife/items/35a956f682b1aca475f6)


2016年発表。ベンチマークシナリオ表を含む。

[YCSB向けGridDBコネクタを使ってみよう](https://griddb.net/ja/blog/getting-started-griddb-connector-ycsb/)

2017年発表

[YCSB (Yahoo Cloud Serving Benchmark)でインメモリデータベースVoltDBの性能を計測する](https://qiita.com/mkyz08/items/c638356b7dcfc5409b84)

2019年５月発表。brianfrankcooperのレポジトリに含まれていないデータベースを使う方法が説明されている（2019年9月にVolt DBクライアントは、レポジトリに追加済み）。