Open15

【Dolt】Git+MySQL⇒Dolt を使ってみる

arikaarika

Links

GitHub
Documents

Doltとは

Dolt is a SQL database that you can fork, clone, branch, merge, push and pull just like a git repository.

(訳) Doltは、gitリポジトリと同じように、フォーク、クローン、ブランチ、マージ、プッシュ、プルできるSQLデータベースです。

公式のUse Caseにあるように

  • データ共有
  • データ取り込み
  • 機械学習(用のデータ管理)
  • 設定管理(Configuration Management)
  • あなたのアプリにVCを導入(Version Control Your Application)
  • (単純に)より良いMySQL

といった用途に使用でき、想像しうる応用例も幅広そう。

arikaarika

Setup

早速導入を行っていく。
今回はdocker-composeを使用して作成を行う。

以下のrepositoryを対象に操作を行っていく。
https://github.com/arika0093/dolt_sample

なお、検証は以下の環境で行った。

Kernel: Linux 5.4.72-microsoft-standard-WSL2 GNU/Linux
Distribution: Ubuntu 20.04.2 LTS
CPU: Intel(R) Core(TM) i7-9700KF CPU @ 3.60GHz
arikaarika

公式のdockerサンプルを元に作成した。
まずは最小限の構成で立てて、各種処理は手元のSQL実行環境から行うことにする。

docker-compose.yaml
version: '3.3'

services:
  db_dolt:
    build:
      context: .
      dockerfile: ./services/
    ports:
      - "3306:3306"
    volumes:
      - db_dolt:/home/db_dolt

volumes:
  db_dolt:
dockerfile
# using ubuntu 20
FROM ubuntu:focal
WORKDIR /db_dolt

# install dolt
RUN apt update \
 && apt install -y curl \
 && curl -L https://github.com/dolthub/dolt/releases/latest/download/install.sh | bash \
 && dolt config --global --add user.email "DOLT_DEVELOP_SERVER@example.com" \
 && dolt config --global --add user.name "DOLT_DEVELOP_SERVER"

# initialize tables
COPY ./services/db_dolt/initialize.sql ./initialize.sql
RUN dolt sql < ./initialize.sql

# execute dolt commands
COPY ./services/db_dolt/config.yaml ./config.yaml
ENTRYPOINT [ "dolt" ]
CMD [ "sql-server", "--config", "./config.yaml" ]

install過程でデフォルトのユーザー名・emailを追加している。
これはgitと同じくコミットにはusername/emailが必要なため。
※それに加えてinitもできない
ここは上書きもできるのでとりあえずサーバー既定とわかるような名前にしておいた。

また、途中でinitialize.sqlconfig.yamlという2種類のファイルを用意した。

initialize.sqlはテーブルの初期化等を行うスクリプト。
今回はテーブルの作成だけで終わらせている。

initialize.sql
CREATE DATABASE test;

config.yamlはサーバーを建てる際の設定ファイル。公式docs
userは単一指定でroot権限を持つユーザーを指定する。
この方法だとどうやってもパスワードを直書きになってしまうので、本番環境では工夫が必要だと思う…(issue立てようかな)

config.yaml
log_level: info

behavior:
  read_only: false
  autocommit: true

user:
  name: root
  password: password

listener:
  host: 0.0.0.0
  port: 3306
  max_connections: 100
arikaarika

ここでサーバーを立ち上げる。

alias dcu="docker-compose up -V --build -d"
alias dc="docker-compose"
dcu

うまくいくと以下のようにサーバーが立ち上がる。

$ dc ps
        Name                       Command               State                    Ports                  
---------------------------------------------------------------------------------------------------------
dolt_sample_db_dolt_1   dolt sql-server --config . ...   Up      0.0.0.0:3306->3306/tcp,:::3306-

$ dc logs db_dolt
Attaching to dolt_sample_db_dolt_1
db_dolt_1  | Starting server with Config HP="0.0.0.0:3306"|U="root"|P="password"|T="28800000"|R="false"|L="info"

この状態のrepository:
https://github.com/arika0093/dolt_sample/tree/af4b7e82ff961370f80296afa5e807c8b7c627c8

arikaarika

お試しフェーズ

今回はコマンドラインからSQLを実行していくことにする。
※information_schema絡みが悪さをしててSQLEditorが全滅してる……

コンテナ内に入り、dolt sql-clientで組み込みのSQL clientが立ち上がる。
ここからSQLを実行していくことにする。

$ dc exec db_dolt bash

$ dolt sql-client -p password
# Welcome to the Dolt MySQL client.
# Statements must be terminated with ';'.
# "exit" or "quit" (or Ctrl-D) to exit.
mysql>  
arikaarika

暗黙のコミット、明示的なコミット

まずはテーブルを作成してみる。
ここで、あえてブランチを切らずにtableを作成してみる。

USE test;
CREATE TABLE test.test_table (
	idx varchar(100) NOT NULL,
	value varchar(100) NULL,
	CONSTRAINT test_table_PK PRIMARY KEY (idx)
);

正常に終了している。

mysql> show tables;
+------------+
| Table      |
+------------+
| test_table |
+------------+

環境変数 @@AUTO_COMMIT = 0とした上で、別のテーブルを作成する。

SET @@autocommit = 0;
CREATE TABLE test.test_table2 (
	idx varchar(100) NOT NULL,
	value varchar(100) NULL,
	CONSTRAINT test_table_PK PRIMARY KEY (idx)
);
mysql> show tables;
+-------------+
| Table       |
+-------------+
| test_table  |
| test_table2 |
+-------------+

作成されている。

ただし、ブランチを明示的に指定した場合[1]

mysql> use `test/main`;
mysql> show tables;
+------------+
| Table      |
+------------+
| test_table |
+------------+

消滅する。

また、sessionを切ってから再接続した場合も

mysql> ^D
$ dolt sql-client -p password
mysql> use `test`;
mysql> select * from dolt_status;
+------------+--------+-----------+
| table_name | staged | status    |
+------------+--------+-----------+
| test_table | 0      | new table |
+------------+--------+-----------+

消滅する。

では、この状態でcommitしてみよう。
CLIの方はうまく動かない(後述)ので、SQLを叩いてコミットを行う。

やり方は単純で、commit -a -m "comment"とするだけ。
ここの引数でユーザー名の指定ができる。( 公式docs )

select DOLT_COMMIT("-a", "-m", "create one table", "--author", "Arika <arika@example.com>");

うまくいくと新しいhashが返ってくる。

+--------------------------------------------------------------------------------------+
| DOLT_COMMIT("-a", "-m", "create one table", "--author", "Arika <arika@example.com>") |
+--------------------------------------------------------------------------------------+
| 6f3t7ieh90edu9srobrbebi6teandvkv                                                     |
+--------------------------------------------------------------------------------------+

logを見ると

select * from dolt_log;
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+
| commit_hash                      | committer           | email                           | date                    | message                    |
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+
| 6f3t7ieh90edu9srobrbebi6teandvkv | Arika               | arika@example.com               | 2022-03-23 12:44:29.053 | create one table           |
| 3m1tgvavomchi7a03vhl0755l6vff8j6 | DOLT_DEVELOP_SERVER | DOLT_DEVELOP_SERVER@example.com | 2022-03-23 12:02:07.53  | Initialize data repository |
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+

うまく行ってそう。

結論

  • 標準では、変更は自動的にstagingされる。
  • AUTO_COMMIT=0とすると、session内でのみ有効な変更となる。
  • 変更を反映させるにはDOLT_COMMITする。
    • こっちのCOMMITはgitの方。
    • リモートを設定してないので(別PCのリポジトリに直接コミットしてるようなもの)、pushは要らない。
脚注
  1. USE database/branch の構文でブランチ指定が可能 ↩︎

arikaarika

dolt initとかdolt statusはどうしたの?

余談なので折りたたみ

さて、公式のgetting-startにあるdolt initを今回は行っていない。
その理由は、最初にdolt initをしてから上記手順を行うと分かる。

まず、上記通りにコミットを行った場合。
コミットログが2つ存在している。(init時生成 + commit)

select commit_hash from dolt_log;

6f3t7ieh90edu9srobrbebi6teandvkv
3m1tgvavomchi7a03vhl0755l6vff8j6

そして、dolt initをしてから上記手順を行った後のdolt log

$ dolt log
dolt log | grep commit
commit 0i0ltstte7u9m3ott0elmm7lfcdvepj6 (HEAD -> main)

まず、SQLでコミットした結果が反映されていない。
その上、自動生成されたコミットのhashも異なる。

この原因はshow databasesすると分かる。

db_dolt> show databases;
+--------------------+
| Database           |
+--------------------+
| db_dolt            |
| information_schema |
| test               |
+--------------------+

db_dolt> use db_dolt;
db_dolt> show tables;
+-------+
| Table |
+-------+
+-------+

db_dolt> select commit_hash from dolt_log;
+----------------------------------+
| commit_hash                      |
+----------------------------------+
| 0i0ltstte7u9m3ott0elmm7lfcdvepj6 |
+----------------------------------+

すなわち、dolt initをした時点で自身のフォルダ名と同じdatabaseを作成し、
CLIはそのフォルダ名のdbに対してlogなどを参照していた、というわけ。

今回はtestDBに対して作成をしていたので、これらの変更はdolt logに反映されなかったというオチである。

当然、とてもわかりにくいので今回はCLIを使わないことにした。

arikaarika

色々git風のあれこれを試してみる

とりあえず、適当な値を入れておく。

use test;
insert into test_table values ("hoge", "hogehoge");
select DOLT_COMMIT("-a", "-m", "Insert example value", "--author", "Arika <arika@example.com>");
select * from `test/main`.test_table;
select * from `test/main`.dolt_log;
+------+----------+
| idx  | value    |
+------+----------+
| hoge | hogehoge |
+------+----------+

+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+
| commit_hash                      | committer           | email                           | date                    | message                    |
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+
| 758nrapr51mq7nli1f47qpel4dpphd3q | Arika               | arika@example.com               | 2022-03-23 13:44:15.205 | Insert example value       |
| v3l9j9jngbgm1per3uc1ovjg8pn4tkh0 | Arika               | arika@example.com               | 2022-03-23 13:30:57.424 | create one table           |
| 4i8nfd8esj9o4b31k51cf2h1ckrr53t7 | DOLT_DEVELOP_SERVER | DOLT_DEVELOP_SERVER@example.com | 2022-03-23 13:22:29.2   | Initialize data repository |
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+
arikaarika

1. Xさんがbranch(develop)を作成して、新しいデータを入れた(コミット2回)。

use test;
select DOLT_CHECKOUT("-b", "develop");
insert into test_table values ("fuga", "fuga from develop");
select DOLT_COMMIT("-a", "-m", "Add new development value", "--author", "PersonX <person_x@example.com>");
insert into test_table values ("fuga2", "fuga2 from develop");
select DOLT_COMMIT("-a", "-m", "Add new development value 2", "--author", "PersonX <person_x@example.com>");
select * from `test`.test_table;
select * from `test`.dolt_log;
+-------+--------------------+
| idx   | value              |
+-------+--------------------+
| fuga  | fuga from develop  |
| fuga2 | fuga2 from develop |
| hoge  | hogehoge           |
+-------+--------------------+

+----------------------------------+---------------------+---------------------------------+-------------------------+-----------------------------+
| commit_hash                      | committer           | email                           | date                    | message                     |
+----------------------------------+---------------------+---------------------------------+-------------------------+-----------------------------+
| 2eu9pvrsd0f4k8qktt0ehi6u0hovuo0j | PersonX             | person_x@example.com            | 2022-03-23 13:59:09.692 | Add new development value 2 |
| rmtjq0daa0lcj4vjl958nm6cfduv34le | PersonX             | person_x@example.com            | 2022-03-23 13:49:36.924 | Add new development value   |
| 758nrapr51mq7nli1f47qpel4dpphd3q | Arika               | arika@example.com               | 2022-03-23 13:44:15.205 | Insert example value        |
| v3l9j9jngbgm1per3uc1ovjg8pn4tkh0 | Arika               | arika@example.com               | 2022-03-23 13:30:57.424 | create one table            |
| 4i8nfd8esj9o4b31k51cf2h1ckrr53t7 | DOLT_DEVELOP_SERVER | DOLT_DEVELOP_SERVER@example.com | 2022-03-23 13:22:29.2   | Initialize data repository  |
+----------------------------------+---------------------+---------------------------------+-------------------------+-----------------------------+

この時点で外部SQLEditorからはこのように見える。

arikaarika

2. Yさんがbranch(main)に新しいデータを入れた(コミット無し)。

use `test/main`;
insert into test_table values ("piyo", "piyo hot value!!!!");
select * from `test/main`.test_table;
select * from `test/main`.dolt_log;
select * from `test/main`.dolt_status;
+------+--------------------+
| idx  | value              |
+------+--------------------+
| hoge | hogehoge           |
| piyo | piyo hot value!!!! |
+------+--------------------+

+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+
| commit_hash                      | committer           | email                           | date                    | message                    |
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+
| 758nrapr51mq7nli1f47qpel4dpphd3q | Arika               | arika@example.com               | 2022-03-23 13:44:15.205 | Insert example value       |
| v3l9j9jngbgm1per3uc1ovjg8pn4tkh0 | Arika               | arika@example.com               | 2022-03-23 13:30:57.424 | create one table           |
| 4i8nfd8esj9o4b31k51cf2h1ckrr53t7 | DOLT_DEVELOP_SERVER | DOLT_DEVELOP_SERVER@example.com | 2022-03-23 13:22:29.2   | Initialize data repository |
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+

+------------+--------+----------+
| table_name | staged | status   |
+------------+--------+----------+
| test_table | 0      | modified |
+------------+--------+----------+

コミットをしていないのでlogには反映されず、status側にmodifiedとして存在している。

arikaarika

3. Xさんがbranch(main)にbranch(develop)の更新を反映させようとした。

use `test`;
select DOLT_CHECKOUT("main");
select DOLT_MERGE("develop", "--squash")
Error 1105: cannot merge with uncommitted changes

コミットしていない変更があるためmergeは失敗する。
ここらへんは普通のgitと同じ。
つまり、他者が更新しうるブランチに未反映ログを残してはいけないようにしたほうが良い。

一つのやり方は、github等と同じくpull-requestやmergeでしか更新できないようにルールとして決めてしまう(protectionをかける)という方法(要するに直接update/insert等できなくする)。
dolt単体でできるかは怪しいが……

余談:問題となりうるケース例

ToDoアプリを例にして考える。
ToDoを作成する人:1人 / ToDoを参照する人:100人としよう。
作成する1人はある時点でのブランチを起点に今後のToDoを作成していき、完成し次第mainに反映する。
参照する100人は各々がToDoの完了ログをDBに入力していく。
こういった際に、参照する側の実績をmodifiedのままmainにおいておくと上記のようにmergeができない。
この場合の対応策は以下の3つが挙げられる。

  1. 全ての実績データを逐一commitする。
  2. merge時に溜まったmodofiedをまとめて反映させる。
  3. 別ブランチで更新を行い、cronjob等で一定間隔ごとにcommit/mergeする。

1のメリットはログが正確になる点。常にmainブランチは最新の状態となる。
しかしその代償としてコミットログが大量に増えてしまう。またオーバーヘッドも著しいことが予測される。

2のメリットは動作が高速になる点。コミット回数は最小限に抑えられる。
しかし、mainブランチの状態が古いままという事態が容易に起こりうる。
上記の例でいけば、ToDoを組み直すにはある程度最新の情報がないとやりにくいだろう。

3のメリットは上記1・2の利点を両方受けられる点。
ある程度の履歴を担保しながらも、動作の高速性とmerge戦略の容易さを得られるのは心強い。
特にmain反映時にconfilictした場合などに、運用は平行して行える点が優秀といえる。
一方で更新頻度をどれぐらいにするかという点、cronjob等の別途運用にかかるコスト、実績反映用のブランチ作成など、考えることが多いのが欠点といえる。

arikaarika

4. Xさんがbranch(main)の更新をコミットした上でsquash mergeを行った(未コミット)。

use `test`;
select DOLT_CHECKOUT("main");
select DOLT_COMMIT("-a", "-m", "modified auto commit");
select DOLT_MERGE("develop", "--squash");
select * from `test`.test_table;
select * from `test`.dolt_log;
select * from `test`.dolt_status;
+-------+--------------------+
| idx   | value              |
+-------+--------------------+
| fuga  | fuga from develop  |
| fuga2 | fuga2 from develop |
| hoge  | hogehoge           |
| piyo  | piyo hot value!!!! |
+-------+--------------------+

+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+
| commit_hash                      | committer           | email                           | date                    | message                    |
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+
| 9dtuu3rgq3r24lrp65t85ggimt8a6hn3 | DOLT_DEVELOP_SERVER | DOLT_DEVELOP_SERVER@example.com | 2022-03-23 14:27:25.757 | modified auto commit       |
| 758nrapr51mq7nli1f47qpel4dpphd3q | Arika               | arika@example.com               | 2022-03-23 13:44:15.205 | Insert example value       |
| v3l9j9jngbgm1per3uc1ovjg8pn4tkh0 | Arika               | arika@example.com               | 2022-03-23 13:30:57.424 | create one table           |
| 4i8nfd8esj9o4b31k51cf2h1ckrr53t7 | DOLT_DEVELOP_SERVER | DOLT_DEVELOP_SERVER@example.com | 2022-03-23 13:22:29.2   | Initialize data repository |
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+

+------------+--------+----------+
| table_name | staged | status   |
+------------+--------+----------+
| test_table | 1      | modified |
+------------+--------+----------+

今回はsquashmergeを行ったため、developブランチで行ったコミットは残っておらず、未更新状態で残っている(反映させるにはもう一回commitが必要)。

arikaarika

5. Xさんがbranch(main)へのsquash mergeをコミットした。

use `test`;
select DOLT_COMMIT("-a", "-m", "Add new development value 1/2", "--author", "PersonX <person_x@example.com>");
select * from `test`.test_table;
select * from `test`.dolt_log;
+-------+--------------------+
| idx   | value              |
+-------+--------------------+
| fuga  | fuga from develop  |
| fuga2 | fuga2 from develop |
| hoge  | hogehoge           |
| piyo  | piyo hot value!!!! |
+-------+--------------------+

+----------------------------------+---------------------+---------------------------------+-------------------------+-------------------------------+
| commit_hash                      | committer           | email                           | date                    | message                       |
+----------------------------------+---------------------+---------------------------------+-------------------------+-------------------------------+
| kgmbra6quh3bq309gt950gksllk6j0ra | PersonX             | person_x@example.com            | 2022-03-23 14:39:29.938 | Add new development value 1/2 |
| 9dtuu3rgq3r24lrp65t85ggimt8a6hn3 | DOLT_DEVELOP_SERVER | DOLT_DEVELOP_SERVER@example.com | 2022-03-23 14:27:25.757 | modified auto commit          |
| 758nrapr51mq7nli1f47qpel4dpphd3q | Arika               | arika@example.com               | 2022-03-23 13:44:15.205 | Insert example value          |
| v3l9j9jngbgm1per3uc1ovjg8pn4tkh0 | Arika               | arika@example.com               | 2022-03-23 13:30:57.424 | create one table              |
| 4i8nfd8esj9o4b31k51cf2h1ckrr53t7 | DOLT_DEVELOP_SERVER | DOLT_DEVELOP_SERVER@example.com | 2022-03-23 13:22:29.2   | Initialize data repository    |
+----------------------------------+---------------------+---------------------------------+-------------------------+-------------------------------+
arikaarika

6. commit messageを微妙に変えたほうが良かったと思い直し、merge前の状態にrevertする

use `test`;
select DOLT_CHECKOUT("main");
select DOLT_REVERT("kgmbra6quh3bq309gt950gksllk6j0ra ", "--author", "PersonX <person_x@example.com>");
select * from `test`.test_table;
select * from `test`.dolt_log;
+------+--------------------+
| idx  | value              |
+------+--------------------+
| hoge | hogehoge           |
| piyo | piyo hot value!!!! |
+------+--------------------+

+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------------------+
| commit_hash                      | committer           | email                           | date                    | message                                |
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------------------+
| qpb3alpq56bogsci1v9lmeglj19vu713 | PersonX             | person_x@example.com            | 2022-03-23 14:47:03.045 | Revert "Add new development value 1/2" |
| kgmbra6quh3bq309gt950gksllk6j0ra | PersonX             | person_x@example.com            | 2022-03-23 14:39:29.938 | Add new development value 1/2          |
| 9dtuu3rgq3r24lrp65t85ggimt8a6hn3 | DOLT_DEVELOP_SERVER | DOLT_DEVELOP_SERVER@example.com | 2022-03-23 14:27:25.757 | modified auto commit                   |
| 758nrapr51mq7nli1f47qpel4dpphd3q | Arika               | arika@example.com               | 2022-03-23 13:44:15.205 | Insert example value                   |
| v3l9j9jngbgm1per3uc1ovjg8pn4tkh0 | Arika               | arika@example.com               | 2022-03-23 13:30:57.424 | create one table                       |
| 4i8nfd8esj9o4b31k51cf2h1ckrr53t7 | DOLT_DEVELOP_SERVER | DOLT_DEVELOP_SERVER@example.com | 2022-03-23 13:22:29.2   | Initialize data repository             |
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------------------+

commitしたものはrevertでしか戻せない。
ローカル開発でpushした後にresetしてはいけないのと同じ。
※もしかしたらdolt_branchテーブルのhashを差し替えるだけで戻せるかもしれないが……

DOLT_REVERT関数は公式docs一覧にないが、実は使える。
指定するhashは「打ち消したいコミット」のhashであることに注意。

arikaarika

7. Xさんがもう一度merge/commitを行う。

use `test`;
select DOLT_MERGE("develop", "--squash");
select DOLT_COMMIT("-a", "-m", "Add new development values 1 and 2.", "--author", "PersonX <person_x@example.com>");
select * from `test`.test_table;
select * from `test/main`.test_table;
+-------+--------------------+
| idx   | value              |
+-------+--------------------+
| fuga  | fuga from develop  |
| fuga2 | fuga2 from develop |
| hoge  | hogehoge           |
| piyo  | piyo hot value!!!! |
+-------+--------------------+

+------+----------+
| idx  | value    |
+------+----------+
| hoge | hogehoge |
+------+----------+

ブランチtest/mainはsessionが開始された時点でのブランチ状況をそのまま保持している。
これが反映されるためには、一回sessionを閉じる必要がある。

mysql> ^D
$ dolt sql-client -p password
# Welcome to the Dolt MySQL client.
# Statements must be terminated with ';'.
# "exit" or "quit" (or Ctrl-D) to exit.
mysql> select * from `test/main`.test_table;
+-------+--------------------+
| idx   | value              |
+-------+--------------------+
| fuga  | fuga from develop  |
| fuga2 | fuga2 from develop |
| hoge  | hogehoge           |
| piyo  | piyo hot value!!!! |
+-------+--------------------+