2020年現在のNewSQLについて - Qiita
こちらの記事を読んで、Spannerクローンってこんなにあるのか、と思ったので、試してみたくなったので、Postgres互換っぽいCockroachDBを使ってみたくなって、やってみたメモ。
Spannerは高いしローカルで開発時に使いづらいので今まで敬遠していた部分があったけど、CockroachDBならローカルでも使える。
環境
- OS: Ubuntu 18.04
- Docker: 19.03.6
- DockerCompose: 1.25.0
- CockroachDB: v19.2.4
- Rust: 1.41.0
CockroachDBをdocker-composeで起動する
Start a Cluster in Docker (Insecure) | CockroachDB Docs
これを参考に進めます。
公式ではdocker run -d
で起動してるけど、docker-composeで楽したいので、docker-compose.ymlを書きます。
version: "3.7"
services:
cockroachdb_1:
image: cockroachdb/cockroach:v19.2.4
container_name: roach1
ports:
- 26257:26257
- 8080:8080
networks:
- roachnet
restart: always
command:
- start
- "--insecure"
volumes:
- "./tmp/cockroach-data/roach1:/cockroach/cockroach-data"
networks:
roachnet:
driver: bridge
※とりあえずクラスタリングなしで1台起動するだけにしたい為 join
オプションは外しました
$ docker-compose up
Starting roach1 ... done
Attaching to roach1
roach1 | *
roach1 | * WARNING: RUNNING IN INSECURE MODE!
roach1 | *
roach1 | * - Your cluster is open for any client that can access <all your IP addresses>.
roach1 | * - Any user, even root, can log in without providing a password.
roach1 | * - Any user, connecting as root, can read or write any data in your cluster.
roach1 | * - There is no network encryption nor authentication, and thus no confidentiality.
roach1 | *
roach1 | * Check out how to secure your cluster: https://www.cockroachlabs.com/docs/v19.2/secure-a-cluster.html
roach1 | *
roach1 | *
roach1 | * WARNING: running 'cockroach start' without --join is deprecated.
roach1 | * Consider using 'cockroach start-single-node' or 'cockroach init' instead.
roach1 | *
roach1 | *
roach1 | * WARNING: neither --listen-addr nor --advertise-addr was specified.
roach1 | * The server will advertise "547bc63e7314" to other nodes, is this routable?
roach1 | *
roach1 | * Consider using:
roach1 | * - for local-only servers: --listen-addr=localhost
roach1 | * - for multi-node clusters: --advertise-addr=<host/IP addr>
roach1 | *
roach1 | *
roach1 | CockroachDB node starting at 2020-02-29 14:30:12.917285211 +0000 UTC (took 0.8s)
roach1 | build: CCL v19.2.4 @ 2020/02/06 21:55:19 (go1.12.12)
roach1 | webui: http://547bc63e7314:8080
roach1 | sql: postgresql://root@547bc63e7314:26257?sslmode=disable
roach1 | RPC client flags: /cockroach/cockroach <client cmd> --host=547bc63e7314:26257 --insecure
roach1 | logs: /cockroach/cockroach-data/logs
roach1 | temp dir: /cockroach/cockroach-data/cockroach-temp785838781
roach1 | external I/O path: /cockroach/cockroach-data/extern
roach1 | store[0]: path=/cockroach/cockroach-data
roach1 | status: restarted pre-existing node
roach1 | clusterID: 49e704f9-fafa-453c-8232-2c46af5932f4
roach1 | nodeID: 1
起動したっぽいので、ブラウザから管理画面を見てみます。
http://localhost:8080
モダンな画面が出てきましたね
Postgresとしてアクセスしてみる
postgresのcliはpgcliを愛用しているので、こちらからアクセスしてみます。
管理画面から見ると、 system
というdatabaseにいくつかテーブルがありそうなので、見てみます
$ pgcli -h localhost -p 26257 -u root -d system
Server: PostgreSQL CCL
Version: 2.1.1
Chat: https://gitter.im/dbcli/pgcli
Mail: https://groups.google.com/forum/#!forum/pgcli
Home: http://pgcli.com
root@localhost:system> \d comments
at or near "pg_catalog": syntax error: type does not exist
DETAIL: source SQL:
SELECT c.relchecks, c.relkind, c.relhasindex,
c.relhasrules, c.relhastriggers, c.relhasoids,
'',
c.reltablespace,
CASE WHEN c.reloftype = 0 THEN ''
ELSE c.reloftype::pg_catalog.regtype::pg_catalog.text
^
Time: 0.006s
入ることは入れましたが \d
はエラーになりました。
さすがにpg_xx系は使えないのか。
root@localhost:system> select * from comments;
+--------+-------------+----------+-----------+
| type | object_id | sub_id | comment |
|--------+-------------+----------+-----------|
+--------+-------------+----------+-----------+
SELECT 0
Time: 0.013s
select は普通にできますね。
(これだけで13msかかってる??)
dieselからアクセスしてみる
一応、Rustのpostgresドライバを使ったサンプルは公式にもあります。
https://www.cockroachlabs.com/docs/stable/build-a-rust-app-with-cockroachdb.html
Rustのdieselからアクセスしてみたいので、cargo initして、Dockerfileも書いて、dieselを使ったコードも書いてみます。
docker
version: "3.7"
services:
rust: # 追加
container_name: rust
build:
context: .
volumes:
- .:/app
depends_on:
- cockroachdb_1
command: cargo watch -x "run" -w src
networks:
- roachnet
environment:
DATABASE_URL: "postgresql://root@roach1:26257?sslmode=disable"
cockroachdb_1:
image: cockroachdb/cockroach:v19.2.4
container_name: roach1
ports:
- 26257:26257
- 8080:8080
networks:
- roachnet
restart: always
command:
- start
- "--insecure"
volumes:
- "./tmp/cockroach-data/roach1:/cockroach/cockroach-data"
networks:
roachnet:
driver: bridge
FROM rust:1.41.0-slim-buster
# ローカルのtargetディレクトリにビルドするとマウントしている時に遅くなるのでビルドディレクトリを変える
ENV CARGO_TARGET_DIR=/tmp/target \
DEBIAN_FRONTEND=noninteractive \
LC_CTYPE=ja_JP.utf8 \
LANG=ja_JP.utf8
RUN apt-get update \
&& apt-get install -y -q \
ca-certificates \
locales \
libpq-dev \
gnupg \
apt-transport-https\
libssl-dev \
pkg-config \
curl \
build-essential \
git \
wget \
&& echo "ja_JP UTF-8" > /etc/locale.gen \
&& locale-gen \
&& echo "install rust tools" \
&& cargo install cargo-watch cargo-make diesel_cli \
&& cargo install diesel_cli --no-default-features --features postgres
RUN USER=root cargo new --bin app
WORKDIR /app
COPY ./Cargo.* ./
RUN cargo build --color never \
&& rm src/*.rs
COPY . /app
RUN cargo build
CMD ["cargo", "run"]
database & table 作成
diesel-cliを入れたので、docker-compose buildして、cliからdatabaseとテーブルを作っていきます。
$ docker-compose run --rm rust bash
root@d232f7a72958:/app# diesel setup
Creating migrations directory at: /app/migrations
database "bench" does not exist
root@d232f7a72958:/app# diesel migration generate create_users
Creating migrations/2020-02-29-150223_create_users/up.sql
Creating migrations/2020-02-29-150223_create_users/down.sql
ん、何故かdatabaseがcreateされないみたいですね。。。今回は一旦手動で作っておきます。
root@f78956c522e6:/app# diesel database setup
Executing migration script /app/migrations/00000000000000_diesel_initial_setup/up.sql
Failed with: at or near "diesel_manage_updated_at": syntax error: unimplemented: this syntax
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;
これが失敗したみたいですが、今回は一旦このあたりは不要なので、消してしまいます。
とりあえずテーブルはなんでもいいので、usersテーブルを以下のように作ってみます。
https://www.cockroachlabs.com/docs/stable/performance-best-practices-overview.html#unique-id-best-practices
ここに書かれているようにシーケンシャルなIDを使うとデータが分散されずにパフォーマンスが劣化するので、複合主キーを使うとかUUID使うと良いらしい。
https://www.cockroachlabs.com/docs/stable/performance-best-practices-overview.html#use-uuid-to-generate-unique-ids
↑UUIDを使うサンプルがあったので、これで行ってみます。
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name STRING NOT NULL
);
root@f78956c522e6:/app# diesel migration run
Running migration 2020-02-29-150223_create_users
テーブルが作成できました。
diesel
とりあえず、適当なデータを入れるコードを書いてみます
[package]
name = "cockroachdb_bench"
version = "0.1.0"
authors = ["yagince <straitwalk@gmail.com>"]
edition = "2018"
publish = false
[dependencies]
diesel = { version = "1.4.3", features = ["postgres", "uuidv07"] }
uuid = { version = "0.7", features = ["serde", "v4"] }
※dieselがuuidの0.7までしか対応してない(2020/02/29現在)ので、uuidv07
をfeaturesに指定して、uuidの0.7を使うようにしています。
https://github.com/diesel-rs/diesel/blob/119d27e2a1/diesel/src/pg/types/mod.rs#L171-L172
table! {
users (id) {
id -> Uuid,
name -> Text,
}
}
use diesel::{pg::PgConnection, prelude::*, Queryable};
use std::env;
use crate::schema::users;
pub fn establish_connection() -> PgConnection {
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
PgConnection::establish(&database_url).expect(&format!("Error connecting to {}", database_url))
}
# [derive(Debug, Clone, Queryable)]
pub struct User {
pub id: uuid::Uuid,
pub name: String,
}
# [derive(Debug, Clone, PartialEq, Insertable)]
# [table_name="users"]
pub struct NewUser {
pub name: String,
}
# [macro_use]
extern crate diesel;
pub mod models;
pub mod schema;
use cockroachdb_bench::{
models::{self, NewUser, User},
schema,
};
use diesel::prelude::*;
fn main() {
let user = NewUser {
name: "hoge".to_owned(),
};
println!("{:?}", user);
let conn = models::establish_connection();
let user = diesel::insert_into(schema::users::table)
.values(&user)
.get_result::<User>(&conn);
println!("{:?}", user);
}
$ cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `/tmp/target/debug/cockroachdb_bench`
NewUser { name: "hoge" }
Ok(User { id: f8d016fd-ec77-4717-b669-67134282d2d0, name: "hoge" })
うん、データはinsertできました。
idもauto generateされていますね。
意外と行けそう。
PostgresとCockroachDBでベンチマークとってみる
負荷をかけるコードに変える
- 100スレッドで並列に書き込み
- rayonで並列化する
- 毎回establish_connectionしたくないので、ConnectionPoolで一気にコネクションをプーリングする
- r2d2
[package]
name = "cockroachdb_bench"
version = "0.1.0"
authors = ["yagince <straitwalk@gmail.com>"]
edition = "2018"
publish = false
[dependencies]
diesel = { version = "1.4.3", features = ["postgres", "uuidv07", "r2d2"] }
uuid = { version = "0.7", features = ["serde", "v4"] }
r2d2 = "0.8.8"
rayon = "1.3.0"
- r2d2とrayonを追加
use crate::schema::users;
use diesel::{
pg::PgConnection,
prelude::*,
r2d2::{self, ConnectionManager},
Queryable,
};
use std::env;
pub type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>;
pub type DbCon = r2d2::PooledConnection<ConnectionManager<PgConnection>>;
fn database_url() -> String {
env::var("DATABASE_URL").expect("DATABASE_URL must be set")
}
pub fn create_db_pool(size: u32) -> DbPool {
r2d2::Pool::builder()
.max_size(size)
.build(ConnectionManager::<PgConnection>::new(database_url()))
.expect("failed to create db connection pool")
}
# [derive(Debug, Clone, Queryable)]
pub struct User {
pub id: uuid::Uuid,
pub name: String,
}
# [derive(Debug, Clone, PartialEq, Insertable)]
# [table_name = "users"]
pub struct NewUser {
pub name: String,
}
use cockroachdb_bench::{
models::{self, NewUser, User},
schema,
};
use diesel::prelude::*;
use rayon::prelude::*;
fn main() {
let concurrency = 100;
let n = 100000;
rayon::ThreadPoolBuilder::new().num_threads(concurrency as usize).build_global().unwrap();
let pool = models::create_db_pool(concurrency);
(1..=n).into_par_iter().for_each(|_|{
let conn = pool.get().unwrap();
let user = conn.transaction(|| {
diesel::insert_into(schema::users::table)
.values(&NewUser {
name: "hoge".to_owned(),
})
.get_result::<User>(&conn)
});
// println!("{:?}", user);
});
}
100並列で10万回書き込む
Postgresを準備
docker-compose.ymlにpostgresを追加します
version: "3.7"
services:
rust:
container_name: rust
build:
context: .
volumes:
- .:/app
depends_on:
- cockroachdb_1
command: cargo watch -x "run" -w src
networks:
- roachnet
environment:
# DATABASE_URL: "postgresql://root@roach1:26257/bench?sslmode=disable"
DATABASE_URL: "postgresql://root@postgres:5432/bench"
postgres: # 追加!
image: postgres:11.1
container_name: postgres
ports:
- 5432:5432
networks:
- roachnet
volumes:
- "./tmp/pgdata:/var/lib/postgresql/data"
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: ""
POSTGRES_INITDB_ARGS: --encoding=UTF-8
POSTGRES_DB: bench
restart: always
cockroachdb_1:
image: cockroachdb/cockroach:v19.2.4
container_name: roach1
ports:
- 26257:26257
- 8080:8080
networks:
- roachnet
restart: always
command:
- start
- "--insecure"
volumes:
- "./tmp/cockroach-data/roach1:/cockroach/cockroach-data"
networks:
roachnet:
driver: bridge
migrationしておきます
root@8ce080efff79:/app# diesel setup
Running migration 2020-02-29-150223_create_users
Executing migration script /app/migrations/2020-02-29-150223_create_users/up.sql
Failed with: type "string" does not exist
STRING
型が無いと怒られました。そりゃ無いですね。
VARCHAR
に変えておきます。
ちなみに、CockroachDBでVARCHARはSTRINGのaliasっぽいですね。
https://www.cockroachlabs.com/docs/stable/string.html#aliases
CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR NOT NULL
);
root@8ce080efff79:/app# diesel migration run
Running migration 2020-02-29-150223_create_users
Executing migration script /app/migrations/2020-02-29-150223_create_users/up.sql
Failed with: function gen_random_uuid() does not exist
おっと、今度はそっちが無いですか。
PostgresでUUIDを生成する - blog.beaglesoft.net
※参考にさせて頂きました。
root@localhost:bench> SELECT * FROM pg_available_extensions WHERE name = 'pgcrypto';
+----------+-------------------+---------------------+-------------------------+
| name | default_version | installed_version | comment |
|----------+-------------------+---------------------+-------------------------|
| pgcrypto | 1.3 | <null> | cryptographic functions |
+----------+-------------------+---------------------+-------------------------+
SELECT 1
Time: 0.025s
入ってないですね。
root@localhost:bench> CREATE EXTENSION if not exists pgcrypto;
CREATE EXTENSION
Time: 0.009s
root@localhost:bench> SELECT * FROM pg_available_extensions WHERE name = 'pgcrypto';
+----------+-------------------+---------------------+-------------------------+
| name | default_version | installed_version | comment |
|----------+-------------------+---------------------+-------------------------|
| pgcrypto | 1.3 | 1.3 | cryptographic functions |
+----------+-------------------+---------------------+-------------------------+
SELECT 1
Time: 0.011s
root@localhost:bench> select gen_random_uuid();
+--------------------------------------+
| gen_random_uuid |
|--------------------------------------|
| d08dbbba-b5b8-47a2-8fbc-30140ca3dc95 |
+--------------------------------------+
SELECT 1
Time: 0.016s
入りました。
もう一度migrationします。
root@8ce080efff79:/app# diesel migration run
Running migration 2020-02-29-150223_create_users
成功しましたね。
じゃあ、さっきのコードを実行してみます。
root@8ce080efff79:/app# cargo run
Compiling cockroachdb_bench v0.1.0 (/app)
Finished dev [unoptimized + debuginfo] target(s) in 0.73s
Running `/tmp/target/debug/cockroachdb_bench`
thread 'main' panicked at 'failed to create db connection pool: Error(Some("FATAL: sorry, too many clients already\n"))', src/libcore/result.rs:1188:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
Connection数が多すぎてPoolが作れないと。
postgresのmax_connectionsを上げましょう
...
postgres:
image: postgres:11.1
container_name: postgres
ports:
- 5432:5432
networks:
- roachnet
volumes:
- "./tmp/pgdata:/var/lib/postgresql/data"
environment:
POSTGRES_USER: root
POSTGRES_PASSWORD: ""
POSTGRES_INITDB_ARGS: --encoding=UTF-8
POSTGRES_DB: bench
restart: always
command: ["-c", "max_connections=200"]
...
command
でオプションを渡します。
$ docker-compose up -d postgres
起動しなおしました。
再実行
$ cargo run
root@8ce080efff79:/app# cargo run
Compiling cockroachdb_bench v0.1.0 (/app)
Finished dev [unoptimized + debuginfo] target(s) in 0.75s
Running `/tmp/target/debug/cockroachdb_bench`
無事動きましたね。
今回はPostgres依存なクエリを使ってないので、コードは書き換えることなく接続先を切り替えられましたが、どこまで互換性があるのかはいつかやってみたいかも。
CockroachDB x 1 VS Postgres x 1
両方一台の構成でベンチマークをとってみます。
先に、releaseビルドを作っておきます。
$ cargo build --release
Postgres
root@8ce080efff79:/app# time DATABASE_URL=postgresql://root@postgres:5432/bench /tmp/target/release/cockroachdb_bench
real 0m2.058s
user 0m2.835s
sys 0m9.661s
CockroachDB
root@8ce080efff79:/app# time DATABASE_URL=postgresql://root@roach1:26257/bench?sslmode=disable /tmp/target/release/cockroachdb_bench
real 0m10.870s
user 0m4.429s
sys 0m11.361s
結果
- Postgres
- 約2s
- CockroachDB
- 約10s
Postgres圧勝ですね。
CockroachDB x 3 VS Postgres x 1
CockroachDBのNodeを増やす
公式documentも3台でクラスタ組んでるのでCockroachDBを3台に増やしてみます。
...
cockroachdb_1:
image: cockroachdb/cockroach:v19.2.4
container_name: roach1
ports:
- 26257:26257
- 8080:8080
networks:
- roachnet
restart: always
command:
- start
- "--insecure"
- "--join=roach1,roach2,roach3"
volumes:
- "./tmp/cockroach-data/roach1:/cockroach/cockroach-data"
cockroachdb_2:
image: cockroachdb/cockroach:v19.2.4
container_name: roach2
networks:
- roachnet
restart: always
command:
- start
- "--insecure"
- "--join=roach1,roach2,roach3"
volumes:
- "./tmp/cockroach-data/roach2:/cockroach/cockroach-data"
cockroachdb_3:
image: cockroachdb/cockroach:v19.2.4
container_name: roach3
networks:
- roachnet
restart: always
command:
- start
- "--insecure"
- "--join=roach1,roach2,roach3"
volumes:
- "./tmp/cockroach-data/roach3:/cockroach/cockroach-data"
...
- joinオプションを追加
- cockroachdb_2, cockroachdb_3 を追加
コンテナを起動します。
➜ docker-compose up -d cockroachdb_1 cockroachdb_2 cockroachdb_3
Recreating roach1 ... done
Creating roach2 ... done
Creating roach3 ... done
管理画面から見てみます。
3台に増えましたね。
ベンチマーク
root@39989b3eaf62:/app# time /tmp/target/release/cockroachdb_bench
real 0m16.872s
user 0m4.649s
sys 0m11.553s
結果
約17s
遅くなりましたね。
Node増やすと速くなるかと思ったんですが、予想に反してかなり遅くなりました。
まとめ
- 既存のPostgresDriverでアクセスできた
- Postgres用のcliやGUIのViwerなど既存のツールが流用できそうなのは良い
- 単純に1台構成で比較するとPostgresのほうが速そう
-
未解決の問題
- diesel setupでcreate databaseできない
- Node増やしたらパフォーマンスが劣化した
- 同じNodeにアクセスして書き込んでいるからだろうか?
- Patition等でシャーディングっぽくする必要があるんだろうか?
- 要調査