改ざん防止データベース immudb を使ってみた

## はじめに

immudb はオープンソースの改ざん防止データベースです

https://codenotary.com/technologies/immudb/

https://docs.immudb.io/master/

まだ Qiita の記事もなさそうなので、ちょっと使ってみました

## immudb とは

immudb の特徴は以下の3つです

- 不変 (Immutable)
- 監査可能 (Auditable)
- 安全 (Secure)

### 不変

データベースなのに不変とはこれいかに、と思うかもしれませんが、
要するに追加されたレコードは更新、削除されない、という意味です

あるキーで登録した値を更新しようとする場合、前の値は履歴として残り、新しい値が増えていきます

#### 通常のデータベースの場合

キー A 、値 100 を追加

| Key | Value |
|---|---|
| A | 100 |

キー B 、値 200 を追加

| Key | Value |
|---|---|
| A | 100 |
| B | 200 |

キー A を値 300 に更新

| Key | Value |
|---|---|
| A | 300 |
| B | 200 |

値が更新されれば、過去の値は参照できません

#### immudb の場合

キー A 、値 100 を追加

| Key | Value |　TX（トランザクション） |
|---|---|--:|
| A | 100 | 1 |

キー B 、値 200 を追加

| Key | Value |　TX（トランザクション） |
|---|---|--:|
| A | 100 | 1 |
| B | 200 | 2 |

キー A を値 300 に更新

| Key | Value |　TX（トランザクション） |
|---|---|--:|
| A | 100 | 1 |
| A | 300 | 3 |
| B | 200 | 2 |

つまり、いつの時点（どのトランザクションを実行した時点）かをもっており、
その時点での値を保持し続ける仕組みになっています

当然、通常のデータベースでもキーやカラムにトランザクションを持っておけば同じことができますが、
immudb は必ずこのようにデータを保持し、このようにしかできない仕組みになっているため、
データが不変であることが保証されます

### 監査可能

データの追加が常に記録されるため、全ての操作が監査可能になります

### 安全

データは常に暗号化され、検証可能になっています

ブロックチェーンと同じように履歴の一貫性を検証可能で、かつブロックチェーンよりも高速です

ストレージに AWS の S3 を指定することができるため、
ストレージ容量不足で停まる心配もありません 

## immudb のユースケース

上記のような特性を持っているため、以下のようなデータを保持することに向いています

- 重要な取引データ（金融、契約、クレジットカード、法的文書、医療、教育など）
- IoT のセンサーデータ
- テスト結果などのエビデンス
- ログ情報

## immudb のデータ構造

immudb は Key-Value 型の **NoSQL として使用** できる上に、
同時に SQL で操作可能な **RDB としても使用**できます

実例は後で

今までキャッシュデータは Redis 、トランザクションデータは PostgreSQL 、
というように使い分けていたところが、全てを immudb に任せられるわけです

おまけにストレージを S3 にできるので、
容量不足も気にする必要はないし（容量当たりのコストは高いかもしれませんが）、
データが失われるような危険性もほぼなさそうです

## Docker コンテナで immudb を動かす

公式の GitHub に QuickStart があるので、それに従えばすぐに使えます

https://github.com/codenotary/immudb

また、起動後の基本操作も含めて公式ドキュメントに記載されています

https://docs.immudb.io/master/quickstart.html

色々環境に悩まされたくないので、 Docker でサクッと動かします

### immudb の起動

まず immudb のサーバー側を起動します

```bash
$ docker run -d --net host -it --rm --name immudb codenotary/immudb:latest
Unable to find image 'codenotary/immudb:latest' locally
latest: Pulling from codenotary/immudb
4bb8ce72b80c: Pull complete
...
```

これでバックグラウンドで immudb のコンテナが起動した状態になります

### immuclient の起動

次にクライアント （immudb の CLI である immuclient） も Docker 上で動かします

```bash
$ docker run -it --rm --net host --name immuclient codenotary/immuclient:latest
Unable to find image 'codenotary/immuclient:latest' locally
latest: Pulling from codenotary/immuclient
a2abf6c4d29d: Pull complete
...
Status: Downloaded newer image for codenotary/immuclient:latest
immuclient>
```

コマンドを実行すると、コンテナ内で `immuclient` を実行した状態になります

### DB への接続

`login <ユーザー名>` で DB に接続します

デフォルトではユーザー名 immudb 、パスワード immudb になっているので、

`login immudb` を実行し、パスワードに `immudb` を入力します

```bash
immuclient>login immudb
Password:
Successfully logged in
immudb user has the default password: please change it to ensure proper security
immuclient>
```

ログイン成功後、パスワード変更を勧められます（今回はしません）

### Key-Value の操作

`safeset <キー> <値>` でレコードを追加します

balance　に 100 を設定します

```bash
immuclient>safeset balance 100
tx:		2
key:		balance
value:		100
verified:	true
```

tx （トランザクション）が 2 になっています

更新する場合も同じです

balance の値を 9001 にします

```bash
immuclient>safeset balance 9001
tx:		3
key:		balance
value:		9001
verified:	true
```

tx が 3 になっています

`safeget <キー>` で現在の値を取得します

```bash
immuclient>safeget balance
tx:		3
key:		balance
value:		9001
verified:	true
```

tx が 3 の値が取得されます

`history <キー>` で履歴を表示します

```bash
immuclient>history balance
tx:		2
key:		balance
value:		100

tx:		3
key:		balance
value:		9001
```

tx = 2 のときと、 tx = 3 のときの両方が取得できました

### RDB の操作

公式ドキュメントでは以下のようにコマンドを紹介しています

```bash
$ immuclient exec "CREATE TABLE people(id INTEGER, name VARCHAR, salary INTEGER, PRIMARY KEY id);"
sql ok, Ctxs: 1 Dtxs: 0
```

しかし、上記のコマンドは immuclient に入っていない状態なので、そのまま実行すると動きません

```bash
immuclient>immuclient exec "CREATE TABLE people(id INTEGER, name VARCHAR, salary INTEGER, PRIMARY KEY id);"
ERROR: Unknown command  | immuclient
```

immuclient 内で実行する場合、先頭の `immuclient` を取る必要があります

ただし、それだけでも動きません

```bash
immuclient>exec "CREATE TABLE people(id INTEGER, name VARCHAR, salary INTEGER, PRIMARY KEY id);"
ERROR: rpc error: code = Unknown desc = syntax error: unexpected $unk at position 1
```

実は `"` （ダブルクォーテーション）は immuclient 内では使用できないので、以下のように実行する必要があります

```bash
immuclient>exec CREATE TABLE people(id INTEGER, name VARCHAR, salary INTEGER, PRIMARY KEY id);
Updated rows: 0
```

この辺りに関しては公式 Issue にも挙げられているので、そのうち何らか対応されるでしょう

まだまだ成長中ですね

https://github.com/codenotary/immudb/issues/971

何はともあれ、　`exec <SQL文>` で SQL が実行できるわけです

先ほど作ったテーブルにデータを追加してみましょう

```bash
immuclient>exec UPSERT INTO people(id, name, salary) VALUES (1, 'Joe', 10000);
Updated rows: 1
immuclient>exec UPSERT INTO people(id, name, salary) VALUES (2, 'Bob', 30000);
Updated rows: 1
```

UPSERT なので、データがあれば更新、なければ追加します

`query <SQL文>` でクエリを発行し、結果を取得します

```bash
immuclient>query SELECT id, name, salary FROM people;
+-----------------------+-------------------------+---------------------------+
| (DEFAULTDB PEOPLE ID) | (DEFAULTDB PEOPLE NAME) | (DEFAULTDB PEOPLE SALARY) |
+-----------------------+-------------------------+---------------------------+
|                     1 | "Joe"                   |                     10000 |
|                     2 | "Bob"                   |                     30000 |
+-----------------------+-------------------------+---------------------------+
```

UPSERT した 2 件が確かに取得できました

UPSERT で Joe の SALARY を 20000 に更新してみましょう

```bash
immuclient>exec UPSERT INTO people(id, name, salary) VALUES (1, 'Joe', 20000);
Updated rows: 1
```

当然、通常の SQL と同じように WHERE 句が使えます

```bash
immuclient>query SELECT id, name, salary FROM people WHERE name='Joe';
+-----------------------+-------------------------+---------------------------+
| (DEFAULTDB PEOPLE ID) | (DEFAULTDB PEOPLE NAME) | (DEFAULTDB PEOPLE SALARY) |
+-----------------------+-------------------------+---------------------------+
|                     1 | "Joe"                   |                     20000 |
+-----------------------+-------------------------+---------------------------+
```

確かに SALARY の値が更新されています

この次に公式ドキュメントではタイムトラベルと題して、次のコマンドを紹介しています

```bash
$ immuclient query "SELECT id, name, salary FROM (people BEFORE TX 3) WHERE name='Joe';"
```

つまり、 TX = 3 の実行前の状態が取得できるというわけです

が、実はこれは動きません

```bash
immuclient>query SELECT id, name, salary FROM (people BEFORE TX 3) WHERE name='Joe';
ERROR: rpc error: code = Unknown desc = syntax error: unexpected IDENTIFIER, expecting SELECT at position 36
```

なんと、ドキュメントには書いてあるものの、未実装なのです

これも Issue に上がっているので、実装されるかドキュメントから消されるかするはずです 

https://github.com/codenotary/immudb/issues/978

伸び代ですね

### immuclient の外から操作する

Docker コンテナから起動すると immuclient に入った状態になるため、
`immuclient exec "CREATE ...` というようには実行できませんでした

以下のように、コンテナ起動時に動かすコマンド（エントリーポイント）を bash に変えると immuclient の外から実行できます

```bash
$ docker run -it --rm --net host --name immuclient --entrypoint /bin/bash codenotary/immuclient:latest
root@minikube:/#
```

immuclient は /app に配置されているため、以下のように実行します

```bash
root@minikube:/# /app/immuclient query "SELECT id, name, salary FROM people WHERE name='Joe';"
+-----------------------+-------------------------+---------------------------+
| (DEFAULTDB PEOPLE ID) | (DEFAULTDB PEOPLE NAME) | (DEFAULTDB PEOPLE SALARY) |
+-----------------------+-------------------------+---------------------------+
|                     1 | "Joe"                   |                     50000 |
+-----------------------+-------------------------+---------------------------+
```

## おわりに

immudb は安全、高速、NoSQLかつRDBという様々な特徴を持つデータベースです

まだまだ実装が粗い部分はあるものの、今後使えるシーンは多そうです

ブロックチェーンを使わずに改ざん防止を保証できるのはいいですね

次は S3 をストレージに設定してみたり、 Python から操作したりしようと思います