2021/07/03

ローカル環境でyugabyteDBを立ててRustでアクセスしてみました。

yugabytedbrustpostgresql

Postgresql互換のあるk8sで展開できるDBとしてyugabyteDBがありますが、商用環境で使ってみたいなと思い、調査も兼ねてローカルでDBを立ててRustで書いたプログラムからアクセスするところまでやってみました。

docker-composeでシングルクラスターをローカルに構築

version: '2'

services:
  yb-master:
    image: yugabytedb/yugabyte:latest
    container_name: yb-master-n1
    volumes:
      - .docker-data/yb-master:/mnt/master
    command: [ "/home/yugabyte/bin/yb-master",
               "--fs_data_dirs=/mnt/master",
               "--master_addresses=yb-master-n1:7100",
               "--rpc_bind_addresses=yb-master-n1:7100",
               "--replication_factor=1"]
    ports:
      - "7000:7000"
    environment:
      SERVICE_7000_NAME: yb-master

  yb-tserver:
    image: yugabytedb/yugabyte:latest
    container_name: yb-tserver-n1
    volumes:
      - .docker-data/yb-tserver:/mnt/tserver
    command: [ "/home/yugabyte/bin/yb-tserver",
               "--fs_data_dirs=/mnt/tserver",
               "--start_pgsql_proxy",
               "--rpc_bind_addresses=yb-tserver-n1:9100",
               "--tserver_master_addrs=yb-master-n1:7100"]
    ports:
      - "9042:9042"
      - "5433:5433"
      - "9000:9000"
    environment:
      SERVICE_5433_NAME: ysql
      SERVICE_9042_NAME: ycql
      SERVICE_6379_NAME: yedis
      SERVICE_9000_NAME: yb-tserver
    depends_on:
      - yb-master

こちらのファイルを作成して、docker-compose upします。直下に作った.docker-dataディレクトリにわらわらとファイルが書き出されてきます。

docker container内にログインしてアクセス

% docker exec -it yb-tserver-n1 bash
# ysqlsh
ysqlsh (11.2-YB-2.7.1.1-b0)
Type "help" for help.

yugabyte=# 

host側からアクセス

  • psql clientは事前にインストールする必要あります。
% psql -h localhost -p 5433 -d yugabyte -U yugabyte

rustからアクセス

rustのバージョンは以下を使いました。

cat <<EOF > rust-toolchain
nightly-2021-07-02
EOF

Diesel Migrationを設定

まずは、diesel_cliツールをPCにインストール

% cargo install diesel_cli

.envに以下を記載

DATABASE_URL=postgres://yugabyte@localhost:5433/yugabyte

migrationを実行

% diesel setup
% diesel migration generate create_posts

# テーブル作成を確認
% cat <<EOF > ./2021-07-03-043211_create_posts/up.sql
CREATE TABLE posts (
  id SERIAL PRIMARY KEY,
  title VARCHAR NOT NULL,
  body TEXT NOT NULL,
  published BOOLEAN NOT NULL DEFAULT 'f'
)
EOF
% diesel migration run
% psql -h localhost -p 5433 -d yugabyte -U yugabyte
# \dt
                   List of relations
 Schema |            Name            | Type  |  Owner   
--------+----------------------------+-------+----------
 public | __diesel_schema_migrations | table | yugabyte
 public | posts                      | table | yugabyte
(2 rows)
# \q

# テーブル削除を確認
% cat <<EOF > ./2021-07-03-043211_create_posts/down.sql
DROP TABLE posts
EOF
% diesel migration revert
# \dt
                   List of relations
 Schema |            Name            | Type  |  Owner   
--------+----------------------------+-------+----------
 public | __diesel_schema_migrations | table | yugabyte
(1 row)

ちなみに、SAVEPOINT はまだサポートされていないらしく、以下のコマンドを実行するとエラーになりました。

%  diesel migration redo
Failed with: SAVEPOINT <transaction> not supported yet

Rustのコードから接続

Cargo.tomlに以下の依存関係を追加

[dependencies]
diesel = { version = "1.4.4", features = ["postgres", "r2d2"] }
dotenv = "0.15.0"

conn.rs

use diesel::prelude::*;
use diesel::pg::PgConnection;
use dotenv::dotenv;
use std::env;

pub fn establish_connection() -> PgConnection {
    dotenv().ok();

    let database_url = env::var("DATABASE_URL")
        .expect("DATABASE_URL must be set");
    PgConnection::establish(&database_url)
        .expect(&format!("Error connecting to {}", database_url))
}

scheme.rs

table! {
    posts (id) {
        id -> Integer,
        title -> Text,
        body -> Text,
        published -> Bool,
    }
}

entities.rs

#[derive(Queryable)]
pub struct Post {
    pub id: i32,
    pub title: String,
    pub body: String,
    pub published: bool,
}

#[derive(Insertable)]
#[table_name="posts"]
pub struct NewPost<'a> {
    pub title: &'a str,
    pub body: &'a str,
}

adapters.rs

use diesel;
use diesel::{PgConnection, RunQueryDsl};
use diesel::prelude::*;
use crate::entities::{Post, NewPost};
use crate::scheme::posts;

pub struct PostAdapter { }

impl PostAdapter {
    pub fn create<'a>(conn: &PgConnection, title: &'a str, body: &'a str) -> Post {
        let new_post = NewPost {
            title,
            body,
        };
        diesel::insert_into(posts::table)
            .values(&new_post)
            .get_result(conn)
            .expect("Error saving new post")
    }

    pub fn read_all(conn: &PgConnection) -> Result<Vec<Post>, String> {
        posts::table
            .order(posts::id)
            .load::<Post>(conn)
            .map_err(|err| err.to_string())
    }
}

最後に、これらのソースコードをテストを実装して確認しました。

lib.rs

#[cfg(test)]
mod test {
    use super::*;
    use crate::conn::establish_connection;
    use dotenv::dotenv;

    #[test]
    fn test_insert_and_read() {
        dotenv().ok();
        let connection = establish_connection();
        let results = PostAdapter::read_all(&connection)
            .expect("must be read from db");
        assert_eq!(results.len(), 0);
        let title = "new post";
        let body = "test body";
        PostAdapter::create(&connection, title, body);
        let results = PostAdapter::read_all(&connection)
            .expect("must be read from db");
        assert_eq!(results.len(), 1);
    }
}

ysqlからも確認できました。

yugabyte=# select * from posts;
 id |  title   |   body    | published 
----+----------+-----------+-----------
  1 | new post | test body | f
(1 row)

感想

yugabytedbはpostgresqlと互換性があり、アプリケーション側もpostgresqlのつもりで実装すればOKのような感じでした。

ただ、完全に互換性はなくサポートされているものは限られているようです。

ただ、これだけ互換性があれば(私の場合は)そこまで悩まされることはないんじゃないかなと楽観視しています。
(私はアプリケーションを作るときに、「なるべく複雑なSQLやDBの機能は使わない」ようにしているので)

RookをベースにしたK8sのyugabyteDB用のOperatorなるものもあり、開発環境構築にはこちらを使いたいなと思っています。