【Dolt】Git+MySQL⇒Dolt を使ってみる
Links
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
といった用途に使用でき、想像しうる応用例も幅広そう。
Setup
早速導入を行っていく。
今回はdocker-compose
を使用して作成を行う。
以下のrepositoryを対象に操作を行っていく。
なお、検証は以下の環境で行った。
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
公式のdockerサンプルを元に作成した。
まずは最小限の構成で立てて、各種処理は手元のSQL実行環境から行うことにする。
version: '3.3'
services:
db_dolt:
build:
context: .
dockerfile: ./services/
ports:
- "3306:3306"
volumes:
- db_dolt:/home/db_dolt
volumes:
db_dolt:
# 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.sql
とconfig.yaml
という2種類のファイルを用意した。
initialize.sql
はテーブルの初期化等を行うスクリプト。
今回はテーブルの作成だけで終わらせている。
CREATE DATABASE test;
config.yaml
はサーバーを建てる際の設定ファイル。公式docs
userは単一指定でroot権限を持つユーザーを指定する。
この方法だとどうやってもパスワードを直書きになってしまうので、本番環境では工夫が必要だと思う…(issue立てようかな)
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
ここでサーバーを立ち上げる。
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:
お試しフェーズ
今回はコマンドラインから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>
暗黙のコミット、明示的なコミット
まずはテーブルを作成してみる。
ここで、あえてブランチを切らずに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内でのみ有効な変更となる。- ここでいうCOMMITは一般的なSQLの方。公式のFAQにも一応ある。
- 変更を反映させるには
DOLT_COMMIT
する。- こっちのCOMMITはgitの方。
- リモートを設定してないので(別PCのリポジトリに直接コミットしてるようなもの)、
push
は要らない。
-
USE database/branch
の構文でブランチ指定が可能 ↩︎
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などを参照していた、というわけ。
今回はtest
DBに対して作成をしていたので、これらの変更はdolt log
に反映されなかったというオチである。
当然、とてもわかりにくいので今回はCLIを使わないことにした。
色々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 |
+----------------------------------+---------------------+---------------------------------+-------------------------+----------------------------+
develop
)を作成して、新しいデータを入れた(コミット2回)。
1. Xさんがbranch(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からはこのように見える。
main
)に新しいデータを入れた(コミット無し)。
2. Yさんがbranch(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として存在している。
main
)にbranch(develop
)の更新を反映させようとした。
3. Xさんがbranch(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つが挙げられる。
- 全ての実績データを逐一commitする。
- merge時に溜まったmodofiedをまとめて反映させる。
- 別ブランチで更新を行い、cronjob等で一定間隔ごとにcommit/mergeする。
1のメリットはログが正確になる点。常にmainブランチは最新の状態となる。
しかしその代償としてコミットログが大量に増えてしまう。またオーバーヘッドも著しいことが予測される。
2のメリットは動作が高速になる点。コミット回数は最小限に抑えられる。
しかし、mainブランチの状態が古いままという事態が容易に起こりうる。
上記の例でいけば、ToDoを組み直すにはある程度最新の情報がないとやりにくいだろう。
3のメリットは上記1・2の利点を両方受けられる点。
ある程度の履歴を担保しながらも、動作の高速性とmerge戦略の容易さを得られるのは心強い。
特にmain反映時にconfilictした場合などに、運用は平行して行える点が優秀といえる。
一方で更新頻度をどれぐらいにするかという点、cronjob等の別途運用にかかるコスト、実績反映用のブランチ作成など、考えることが多いのが欠点といえる。
main
)の更新をコミットした上でsquash mergeを行った(未コミット)。
4. Xさんがbranch(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 |
+------------+--------+----------+
今回はsquash
mergeを行ったため、develop
ブランチで行ったコミットは残っておらず、未更新状態で残っている(反映させるにはもう一回commitが必要)。
main
)へのsquash mergeをコミットした。
5. Xさんがbranch(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 |
+----------------------------------+---------------------+---------------------------------+-------------------------+-------------------------------+
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であることに注意。
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!!!! |
+-------+--------------------+