Edited at

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

More than 1 year has passed since last update.

クリスマス・イブ:christmas_tree:に不吉な名前のプロダクトを紹介。

現時点(2016/12/24)だとあまり日本語情報がないので、少しでも興味を持ってもらえれば幸いです。

cockroach_db.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については下記が参考になります。


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

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


  • アプリケーションからはPostgreSQLのドライバで接続可能

  • 管理画面を搭載


インストール

builder_craig.png

さて、そんな名前に反して夢のようなDBの実力はどんだけかを試すべく、

公式ページの記載に従い、インストールしてみます。

筆者のMacbook(12inch Early 2016)に入れてみます。

なお、Windowsの場合はDockerが必要です。

Macの場合、バイナリ、Homebrew、Dockerによるインストールが選べます。

今回はHomebrewで入れてみました。

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

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

$ 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)


起動してみる

$ 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


チュートリアル

SCENE_superhero_profile_craig.png

無事起動したところで、早速触っていきましょう。

まずはSQL Shellの起動して、今の状態を確認します。

$ 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というユーザーを作ります。

$ cockroach user set maxroach

INSERT 1


DB作成と権限付与

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

$ cockroach sql

root@:26257> CREATE DATABASE bank;
CREATE DATABASE

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


テーブル作成

maxroachユーザーとbankデータベースを指定して接続し、

accountsテーブルを作成します。

$ 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言語で試してみましょう。


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)
}
}


実行します。

$ go get github.com/lib/pq

$ go run main.go

Initial balances:
1 1000
2 250

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


トランザクションを試す

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

maxroach@:26257> SELECT * FROM accounts;

+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 250 |
+----+---------+
(2 rows)


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

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

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になるのでエラーになる想定ですね。

それでは実行してみましょう。

$ 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と同じように利用できていることが確認できました。

maxroach@:26257> SELECT * FROM accounts;

+----+---------+
| id | balance |
+----+---------+
| 1 | 1000 |
| 2 | 250 |
+----+---------+
(2 rows)


軽く負荷をかけてみる

パフォーマンスはどんなものか?ということで、軽く負荷をかけてみます。

1000goルーチンでそれぞれ100件INSERTして、合計10万レコードを作成してみました。

なお、何か比較対象があったほうがよいかということで、SQLite3でも同様の処理を入れてみました。


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))
}



結果

$ 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

ちなみに、SQLite3だと10万件ごときでは1.6秒で終わってしまうので、あんまり比較にならないです。SQLite3はやはり組込DBとして使うには優秀です(笑)

もちろんCockroachDBはクラスタを組んでリニアに性能が上がるはずですし、単純比較はできませんが。


終わりに

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

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


参考