LoginSignup
10
4

More than 3 years have passed since last update.

[Rust] CockroachDBを使ってみる(diesel)

Last updated at Posted at 2020-03-01

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を書きます。

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

Screen Shot 2020-02-29 at 23.35.10.png

モダンな画面が出てきましたね

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

docker-compose.yml
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を使うサンプルがあったので、これで行ってみます。

migrations/2020-02-29-150223_create_users/up.sql
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

とりあえず、適当なデータを入れるコードを書いてみます

Cargo.toml
[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

schema.rs
table! {
    users (id) {
        id -> Uuid,
        name -> Text,
    }
}
models.rs
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,
}
lib.rs
#[macro_use]
extern crate diesel;

pub mod models;
pub mod schema;
main.rs
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
Cargo.toml
[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を追加
models.rs
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,
}
main.rs
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を追加します

docker-compose.yml
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

up.sql
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を上げましょう

docker-compose.yml
...
  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台に増やしてみます。

docker-compose.yml
...
  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

管理画面から見てみます。

Screen Shot 2020-03-01 at 22.59.37.png

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等でシャーディングっぽくする必要があるんだろうか?
      • 要調査
10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4