クリスマス・イブにCockroachDBに負荷をかけてみる

クリスマス・イブ:christmas_tree:に不吉な名前のプロダクトを紹介。
現時点(2016/12/24)だとあまり日本語情報がないので、少しでも興味を持ってもらえれば幸いです。

![cockroach_db.png](https://qiita-image-store.s3.amazonaws.com/0/83154/135a95c0-2cca-cc01-8b2c-28895d2e5cb9.png)

https://github.com/cockroachdb/cockroach
https://www.cockroachlabs.com/

## キミはCockroachDBを知っているか？

そう火星にいる不気味な生命体・・・ではなく、地球に太古から現在に至るまで不屈の生命力で生き長らえ、
あの黒い生物の名前を関した分散SQLデータベースです。
ACIDトランザクションを備えており、いわゆる「NewSQL」という分類になります。

データセンター、クラウドとネットワーク分断されてもしぶとく動き続けるというP2Pアーキテクチャの強靭性が特徴で、これが黒い生物のメタファとなっています。

元Googleのソフトウェアエンジニア3名が創業しており、2016年3月には2000万ドルを投資家から調達しており、現時点(2016/12/24)では、ベータリリースを繰り返しているというステージです。

※RDB、NoSQL、NewSQLについては下記が参考になります。

- 参考：データベース事始め  
http://qiita.com/AkitsuguHirano/items/980de403cc7fe6c941e3

## CockroachDBの特徴

他のRDB/NoSQLとの比較表が公式ドキュメントにあります。

オートスケール・フェイルオーバー・リカバリに対応し、マルチデータセンターでもロストしない設計、
そして、トランザクションサポートしつつもSQLが使えるという夢のようなDB・・・というアピールになってます。

|                                 | CockroachDB | MySQL    | PostgreSQL | Oracle   | SQL Server | Cassandra | HBase    | MongoDB       | DynamoDB  |
|---------------------------------|-------------|----------|------------|----------|------------|-----------|----------|---------------|-----------|
| Automated Scaling               | Yes         | No       | No         | No       | No         | Yes       | Yes      | Yes           | Yes       |
| Automated Failover              | Yes         | Optional | Optional   | Optional | Optional   | Yes       | Yes      | Yes           | Yes       |
| Automated Repair                | Yes         | No       | No         | No       | No         | Yes       | Yes      | Yes           | Yes       |
| Strongly Consistent Replication | Yes         | No       | No         | Optional | Optional   | Optional  | No       | No            | Yes       |
| Consensus-Based Replication     | Yes         | No       | No         | No       | No         | Optional  | No       | No            | Yes       |
| Distributed Transactions        | Yes         | No       | No         | Yes      | Yes        | No        | No       | No            | No*       |
| ACID Semantics                  | Yes         | Yes      | Yes        | Yes      | Yes        | No        | Row-only | Document-only | Row-only* |
| Eventually Consistent Reads     | No          | Yes      | Yes        | Yes      | Yes        | Yes       | Yes      | Yes           | Yes       |
| SQL                             | Yes         | Yes      | Yes        | Yes      | Yes        | No        | No       | No            | No        |
| Open Source                     | Yes         | Yes      | Yes        | No       | No         | Yes       | Yes      | Yes           | No        |
| Commercial Version              | No          | Optional | No         | Yes      | Yes        | Optional  | Optional | Optional      | Yes       |
| Support                         | Limited     | Full     | Full       | Full     | Full       | Full      | Full     | Full          | Full      |

- 出典  
https://www.cockroachlabs.com/docs/cockroachdb-in-comparison.html

他には、下記の特徴があります。

- アプリケーションからはPostgreSQLのドライバで接続可能
- 管理画面を搭載

## インストール

![builder_craig.png](https://qiita-image-store.s3.amazonaws.com/0/83154/2ba487bb-66b5-5f5b-53e9-ef2b06bd634d.png "builder_craig.png")


さて、そんな名前に反して夢のようなDBの実力はどんだけかを試すべく、
公式ページの記載に従い、インストールしてみます。

筆者のMacbook(12inch Early 2016)に入れてみます。
なお、Windowsの場合はDockerが必要です。

Macの場合、バイナリ、Homebrew、Dockerによるインストールが選べます。
今回はHomebrewで入れてみました。

```bash
$ brew install https://raw.githubusercontent.com/cockroachdb/cockroach/master/build/cockroach.rb
```

16分ほどかかってインストール完了です。

```bash
$ cockroach version
Build Tag:   beta-20161208
Build Time:  2016/12/10 13:29:14
Platform:    darwin amd64
Go Version:  go1.7.4
C Compiler:  4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)
```

## 起動してみる

```bash
$ cockroach start --background
CockroachDB node starting at 2016-12-10 22:47:06.030195226 +0900 JST
build:      beta-20161208 @ 2016/12/10 13:29:14 (go1.7.4)
admin:      http://localhost:8080
sql:        postgresql://root@localhost:26257?sslmode=disable
logs:       cockroach-data/logs
store[0]:   path=cockroach-data
status:     initialized new cluster
clusterID:  3c53d4f4-5065-4e46-9d8b-747f07e54b6f
nodeID:     1
```

デフォルトでは、DBポートが26257、管理画面用Webアプリケーションが8080を利用します。
起動は一瞬ですね。

### 管理画面

起動時のコンソールにも出力されている通り、http://localhost:8080/ にアクセスすると、下図のような今風なダッシュボードが表示されます。

![Cockroach_Console.png](https://qiita-image-store.s3.amazonaws.com/0/83154/85e04775-5d8b-94fd-7480-30f84344b509.png "Cockroach_Console.png")

## チュートリアル

![SCENE_superhero_profile_craig.png](https://qiita-image-store.s3.amazonaws.com/0/83154/4c654d80-7302-6ebb-461e-f4d5e83e584d.png "SCENE_superhero_profile_craig.png")


無事起動したところで、早速触っていきましょう。
まずはSQL Shellの起動して、今の状態を確認します。

```bash
$ cockroach sql
# Welcome to the cockroach SQL interface.
# All statements must be terminated by a semicolon.
# To exit: CTRL + D.

root@:26257> show database;
+----------+
| DATABASE |
+----------+
|          |
+----------+
(1 row)

root@:26257> show users;
+----------+
| username |
+----------+
+----------+
(0 rows)
```

当然、まだDBもユーザーも0件です。
とりあえず、CockroachDBの本家サイトに掲載されているチュートリアルに従って、
DB、ユーザー、テーブル作成までやってみます。

### ユーザー作成

maxroachというユーザーを作ります。

```bash
$ cockroach user set maxroach
INSERT 1
```

### DB作成と権限付与

rootユーザーでbankというDBを作り、全権限をmaxroachユーザーに付与します。

```bash
$ cockroach sql
root@:26257> CREATE DATABASE bank;
CREATE DATABASE

root@:26257> GRANT ALL ON DATABASE bank TO maxroach;
GRANT
```

### テーブル作成

maxroachユーザーとbankデータベースを指定して接続し、
accountsテーブルを作成します。

```bash
$ cockroach sql --database=bank --user=maxroach

maxroach@:26257> CREATE TABLE accounts (id INT PRIMARY KEY, balance INT);
CREATE TABLE
```

ここまではRDB同様で違和感ないですね。

### データ投入

本家サイトにサンプルプログラムが各言語ごとに用意されています。
https://www.cockroachlabs.com/docs/build-a-test-app.html

accountテーブルに2件INSERTして、そのデータを標準出力に表示するというものです。

CockroachDBはGo製ということもあり、今回はGo言語で試してみましょう。

```go:main.go
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
)

func main() {
	db, err := sql.Open("postgres", "postgresql://maxroach@localhost:26257/bank?sslmode=disable")
	if err != nil {
		log.Fatalf("error connection to the database: %s", err)
	}

	// Insert two rows into the "accounts" table.
	if _, err := db.Exec(
		"INSERT INTO accounts (id, balance) VALUES (1, 1000), (2, 250)"); err != nil {
		log.Fatal(err)
	}

	// Print out the balances.
	rows, err := db.Query("SELECT id, balance FROM accounts")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	fmt.Println("Initial balances:")
	for rows.Next() {
		var id, balance int
		if err := rows.Scan(&id, &balance); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%d %d\n", id, balance)
	}
}
```

実行します。

```bash
$ go get github.com/lib/pq

$ go run main.go

Initial balances:
1 1000
2 250

```

無事2件のINSERTに成功しましたね！！

## トランザクションを試す

現在上記で試した2件が入ってる状態です。

```bash
maxroach@:26257> SELECT * FROM accounts;
+----+---------+
| id | balance |
+----+---------+
|  1 |    1000 |
|  2 |     250 |
+----+---------+
(2 rows)
```

### エラーでロールバックされることを確認

サンプルプログラムをトランザクションを利用する形に変えてみます。

```go
package main

import (
	"database/sql"
	"fmt"
	"log"

	_ "github.com/lib/pq"
)

func main() {
	db, err := sql.Open("postgres", "postgresql://maxroach@localhost:26257/bank?sslmode=disable")
	if err != nil {
		log.Fatalf("error connection to the database: %s", err)
	}

	// start transaction
	tx, err := db.Begin()

	if _, err := tx.Exec(
		"INSERT INTO accounts (id, balance) VALUES (3, 100), (4, 200)"); err != nil {
		log.Fatalf("error insert: %s", err)
	}
	// expect error
	if _, err := tx.Exec(
		"INSERT INTO accounts (id, balance) VALUES (1, 100), (2, 200)"); err != nil {
		log.Fatalf("error insert: %s", err)
	}
	// commit transaction
	tx.Commit()

	// Print out the balances.
	rows, err := db.Query("SELECT id, balance FROM accounts")
	if err != nil {
		log.Fatal(err)
	}
	defer rows.Close()
	fmt.Println("Initial balances:")
	for rows.Next() {
		var id, balance int
		if err := rows.Scan(&id, &balance); err != nil {
			log.Fatal(err)
		}
		fmt.Printf("%d %d\n", id, balance)
	}
}
```
2回目のINSERTにて、すでに存在する主キーのINSERTになるのでエラーになる想定ですね。
それでは実行してみましょう。

```bash
$ go run main.go

2016/12/23 21:13:44 error insert 2: pq: duplicate key value (id)=(1) violates unique constraint "primary"
exit status 1
```

想定通りエラー終了となりました。
ちなみに、Goのlog.Fatal系メソッドはコールした後にエラー終了するので、あえてロールバックしたり、returnする必要はありません。

念のためSELECTしてみましたが、問題なし。
確かにトランザクションがRDBと同じように利用できていることが確認できました。

```bash
maxroach@:26257> SELECT * FROM accounts;
+----+---------+
| id | balance |
+----+---------+
|  1 |    1000 |
|  2 |     250 |
+----+---------+
(2 rows)
```

## 軽く負荷をかけてみる

パフォーマンスはどんなものか？ということで、軽く負荷をかけてみます。
1000goルーチンでそれぞれ100件INSERTして、合計10万レコードを作成してみました。
なお、何か比較対象があったほうがよいかということで、SQLite3でも同様の処理を入れてみました。

```go:rush.go
package main

import (
	"database/sql"
	"log"
	"sync"
	"time"

	_ "github.com/lib/pq"
	_ "github.com/mattn/go-sqlite3"
)

func main() {
	cockroachdb, err := sql.Open("postgres", "postgresql://maxroach@localhost:26257/bank?sslmode=disable")
	if err != nil {
		log.Fatalf("error connection to the cockroach database: %s", err)
	}
	log.Println("# CockroachDB")
	stressTest(cockroachdb)

	sqlite3db, err := sql.Open("sqlite3", "./bank.db")
	if err != nil {
		log.Fatalf("error connection to the sqlite3 database: %s", err)
	}
	log.Println("# SQLite3")
	stressTest(sqlite3db)
}

func stressTest(db *sql.DB) {
	defer db.Close()
	// start transaction
	tx, _ := db.Begin()
	var wg sync.WaitGroup

	start := time.Now()
	log.Println("Start test.")

	for i := 1; i <= 1000; i++ {
		wg.Add(1)
		go func(i int) {
			for j := 0; j < 100; j++ {
				if _, err := tx.Exec(
					"INSERT INTO accounts (id, balance) VALUES ($1, 1000)", i*100+j); err != nil {
					log.Fatalf("error insert: %s", err)
				}
			}
			wg.Done()
		}(i)
	}
	wg.Wait()
	tx.Commit()
	log.Println("End test. ", time.Since(start))
}
```

### 結果

```bash
$ go run rush.go
2016/12/23 23:13:42 # CockroachDB
2016/12/23 23:13:42 Start test.
2016/12/23 23:15:08 End test.  1m26.310939943s
2016/12/23 23:15:08 # SQLite3
2016/12/23 23:15:08 Start test.
2016/12/23 23:15:10 End test.  1.653004371s
```

10万件のINSERTで約86秒ということで、何度かテストしたり、goルーチンの並列度をチャネルを使って制限かけたり等もしてみたのですが、結局1100〜1200QPSという結果でした。
（上述の私のMacbookでの話です）

![Cockroach_Console 2.png](https://qiita-image-store.s3.amazonaws.com/0/83154/b2e17129-970d-b52e-5505-bd60a4736bdd.png "Cockroach_Console 2.png")

ちなみに、SQLite3だと10万件ごときでは1.6秒で終わってしまうので、あんまり比較にならないです。SQLite3はやはり組込DBとして使うには優秀です（笑）
もちろんCockroachDBはクラスタを組んでリニアに性能が上がるはずですし、単純比較はできませんが。

## 終わりに

まだJOINが使えなかったり、性能面もまだまだ？と、まだプロダクションレベルではないCockroachDBではありますが、NoSQLではテーブル設計が難しく、やはりRDBライクなテーブル設計ができて、ノード追加でリニアにスケールアウトしてくれるDB製品の登場を欲している人は決して少なくないはず。

クリスマス・イブに、不吉な名前？を持つCockroachDBが来年非連続な成長をしてくれることを期待したいと思います。

## 参考

* NewSQLのCockroachDBについて調べてみた｜サイバーエージェント 公式エンジニアブログ  
http://ameblo.jp/principia-ca/entry-11968223741.html
* ユニークな分散型オープンソースデータベースCockroachDBがシリーズA1で$20Mを調達、著名投資家たちが将来性に注目 | TechCrunch Japan  
http://jp.techcrunch.com/2016/03/31/20160330cockroachdb-just-raised-20-million-from-benchmark-index-and-gv/