Amazon QLDBで台帳データベースを構築する

[Amazon QLDB](https://aws.amazon.com/jp/qldb/)を触ってみた。

# 全体構成
![WS075.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/69b0c32c-9a7f-9e82-cc31-0a92125d9308.jpeg)

 - QLDBはテーブルとジャーナルで構成される
    - テーブル
        - データとメタデータを過去の状態を含めてもつ
        - selectの実行先
    - ジャーナル
        - 変更履歴をもつ
        - insert、update、deleteの実行先

## ジャーナル
![WS076.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/580d0e4e-319e-e58e-885e-84453d15523d.jpeg)

 - ドキュメントに行ったすべての変更の完全で不変な履歴全体をもつ
 - 追加していくだけで、変更削除はできない
 - データだけでなく実行されたクエリも記録する

### ブロック
 - ジャーナルにコミットされたオブジェクト
 - トランザクション：ブロック　＝　１：ｎ
 - エントリ、ブロックハッシュを含む

### エントリ
 - ドキュメントリビジョン、それらをコミットした PartiQL ステートメントを含む


## テーブル
 - ドキュメントリビジョンのコレクション
 - ドキュメントの最新のリビジョンだけでなく、過去のイテレーションもすべて含まれる

### ドキュメント
 - データのこと
 - Amazon Ionというフォーマットで表す
    - 構造化データと非構造化データの両方を一緒に保存できる抽象データモデル
    - JSONの拡張フォーマット
 - 1レコードに1つのドキュメントIDがメタデータとして付与されている

#### 例
```Vehicle.json
{
    VIN: "1N4AL11D75C109151",
    Type: "Sedan",
    Year: 2011,
    Make: "Audi",
    Model: "A5",
    Color: "Silver"
}
```

ネストして保持も可能

```VehicleRegistration.json
{
    VIN: "KM8SRDHF6EU074761",
    LicensePlateNumber: "CA762X",
    State: "WA",
    City: "Kent",
    PendingPenaltyTicketAmount: 130.75,
    ValidFrom: 2017-09-14T,
    ValidTo: 2020-06-25T,
    Owners: {
        PrimaryOwner: { PersonId: "IN7MvYtUjkp1GMZu0F6CG9" },
        SecondaryOwners: []
    }
}
```

### ドキュメントリビジョン
 - ドキュメントの更新を表す仕組み
    - ドキュメントIDと0から始めるバージョン番号で表す
 - ドキュメントとドキュメントIDとバージョン番号を含むメタデータで構成される

#### 例
```json
data:{
    VIN: "KM8SRDHF6EU074761",
    LicensePlateNumber: "CA762X",
    State: "WA",
    City: "Kent",
    PendingPenaltyTicketAmount: 130.75,
    ValidFrom: 2017-09-14T,
    ValidTo: 2020-06-25T,
    Owners: {
        PrimaryOwner: { PersonId: "IN7MvYtUjkp1GMZu0F6CG9" },
        SecondaryOwners: []
    }
},
metadata:{
    id:"JOzfB3lWqGU727mpPeWyxg",
    version:0,
    txTime:2019-06-05T20:53:321d-3Z,
    txId:"HgXAkLjAtV0HQ4lNYdzX60"
}
```

### ユーザーテーブル、カレントビュー、ユーザービュー
 - 削除されていない最新の各ドキュメントが格納される
    - 最新かどうかはジャーナルにコミットされたトランザクションに基づく

### システムテーブル、コミット済みビュー
 - 各ドキュメントとそのシステム生成メタデータを格納する
 - ユーザーテーブル作成時にシステムによって作成されるテーブル
 - ユーザーテーブルと1対1の関係
 - 「\_ql_committed_」＋ユーザーテーブル名で命名される
 - 過去のイテレーションは履歴として保存される



#### blockAddress
 - ドキュメントリビジョンがコミットされた台帳のジャーナルのブロックの場所
    - strandId：ブロックを含むジャーナルストランドの一意の ID
    - sequenceNo：ストランド内でブロックの場所を指定するインデックス番号

#### hash
 - ドキュメントリビジョンを一意に表す SHA-256 値
 - ハッシュは data フィールドと metadata フィールドを対象とする
 - 暗号検証に使用

#### data
 - 最新のドキュメント

#### metadata
 - システムによって生成される各ドキュメントごとのメタデータ
    - id：システムによって割り当てられたドキュメント ID
    - version：ドキュメントリビジョンごとに増えていく 0 から始まる整数
    - txTime：ジャーナルにドキュメントリビジョンがコミットされたときのタイムスタンプ
    - txId：ドキュメントリビジョンをコミットしたトランザクションの一意の ID

#### 例
```json
{
    blockAddress:{
        strandId:"JdxjkR9bSYB5jMHWcI464T",
        sequenceNo:14
    },
    hash:{{wPuwH60TtcCvg/23BFp+redRXuCALkbDihkEvCX22Jk=}},
    data:{
        VIN: "KM8SRDHF6EU074761",
        LicensePlateNumber: "CA762X",
        State: "WA",
        City: "Kent",
        PendingPenaltyTicketAmount: 130.75,
        ValidFrom: 2017-09-14T,
        ValidTo: 2020-06-25T,
        Owners: {
            PrimaryOwner: { PersonId: "IN7MvYtUjkp1GMZu0F6CG9" },
            SecondaryOwners: []
        }
    },
    metadata:{
        id:"JOzfB3lWqGU727mpPeWyxg",
        version:0,
        txTime:2019-06-05T20:53:321d-3Z,
        txId:"HgXAkLjAtV0HQ4lNYdzX60"
    }
}
```


### information_schema
 - ユーザーテーブルのメタデータをもつ
    - tableId：テーブル ID
    - name：テーブル名
    - indexes：テーブルのインデックスのリスト
    - status：テーブルの現在のステータス (ACTIVE または INACTIVE)

#### 例
```json
{
    tableId: "5PLf9SXwndd63lPaSIa0O6",
    name: "VehicleRegistration",
    indexes: [{ expr: "[VIN]" }, { expr: "[LicensePlateNumber]" }],
    status: "ACTIVE"
}
```


# チュートリアル
[Amazon QLDB でのデータと履歴の使用](https://docs.aws.amazon.com/ja_jp/qldb/latest/developerguide/working-with-data.html)をなぞってみた

## 台帳作成
 - 「台帳の作成」ボタンを押下
![WS000.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/97dcb37c-a4aa-83c1-9906-cc4470e69bf3.jpeg)

 - 任意の台帳名を入力し、「台帳の作成」ボタンを押下
![WS001.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/33041271-8745-ceaf-9830-29d14d5ccb3b.jpeg)

 - 1分くらいで作成完了
![WS004.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/d21a74cb-13ac-d16f-adc8-f6e3de7817dd.jpeg)



## データ投入
 - 今回はサンプルデータでテーブルとドキュメントを挿入
![WS009.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/c66e58a6-c6fd-2925-4788-3e59fa0647c5.jpeg)

 - 「Vehicle」テーブルのドキュメントをselect
![WS014.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/f035903a-57f9-1430-e910-eddc6a3a6d57.jpeg)

## クエリ
 - 基本的なSQLは投げられる
 - ネストされたデータもSQLで取得可能

```取得元.json
{
  VIN: "1N4AL11D75C109151",
  LicensePlateNumber: "LEWISR261LL",
  State: "WA",
  City: "Tokyo",
  PendingPenaltyTicketAmount: 90.25,
  ValidFromDate: 2017-08-21T,
  ValidToDate: 2020-05-11T,
  Owners: {
    PrimaryOwner: {
      PersonId: "IN7MvYtUjkp1GMZu0F6CG9"
    },
    SecondaryOwners: [{PersonId:"5Ufgdlnj06gF5CWcOIu64s"}]
  }
}
```

```実行SQL.sql
SELECT 
    r.VIN, 
    o.PersonId
FROM
    VehicleRegistration AS r, @r.Owners.PrimaryOwner AS o
WHERE
    r.VIN = '1N4AL11D75C109151'
```

```取得したデータ.json
{
  VIN: "1N4AL11D75C109151",
  PersonId: "IN7MvYtUjkp1GMZu0F6CG9"
}
```


# 更新
 - 更新自体はRDSと同様
 - 更新前ドキュメント
![WS053.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/c678e361-8c80-1282-5ac8-03075b632d41.jpeg)

```実行SQL.sql
UPDATE VehicleRegistration AS r
SET r.Owners.PrimaryOwner.PersonId = 'IN7MvYtUjkp1GMZu0F6CG9',
r.City = 'Tokyo'
WHERE r.VIN = '1N4AL11D75C109151'
```

 - 更新後ドキュメント
![WS056.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/d94992b7-82df-402a-0cbf-5dfd428359f5.jpeg)

 - 更新前のドキュメントを履歴テーブルから取得できる
![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/41de87fa-97d4-932b-7ad8-5ceaccd8689a.png)

 - メタデータも同様に履歴テーブルから取得できる
![004.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/92ab06a1-9a0c-58b8-73f7-5a5243a168c8.png)



# 検証
 - 「ダイジェストを取得」ボタンにて現在の台帳のダイジェストを取得
![WS065.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/f1aebd57-1760-0cc0-0a05-18bdff16a0b5.jpeg)

 - 「保存」ボタンを押下し、ファイルとしてダウンロード
![WS066.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/f28a1c75-1dbb-825d-8e8d-17e9329d11bb.jpeg)

```sampleLedger-2020-02-22.ion.txt
{
	"digest":"h5pUdLTTNpjgMFE3omBszf2tTtlGWHpty8GM1Zxuzo4=",
	"digestTipAddress":"{strandId:\"JOzfHtF3bQL8pjtQlU37Fu\",sequenceNo:107}",
	"ledger":"sampleLedger",
	"date":"2020-02-22T07:53:34.448Z"
}
```

 - 検証したいドキュメントのidとblockAddressを取得
![WS068.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/155db326-561a-6b90-5be3-bb2e44dec569.jpeg)

 - 「検証」ボタンを押下
![005.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/df6241f7-1c4c-0609-6556-30aed8f47ce9.png)

 - ハッシュのチェーンを確認し検証結果を表示
![006.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/059d2c3a-ce84-7236-879c-5313985deb6a.png)

 - リビジョンハッシュと証明ハッシュの1つ目を連結し、ハッシュ化した値を  
   さらに2つ目と連結しハッシュ化し、繰り返す  
   最終的に出たダイジェストがドキュメントのダイジェストと一致するかを検証している
![WS084.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/f5b7d568-ede3-a0fc-1081-5e05f37ace4d.jpeg)

 - ブロック情報も確認できる
![007.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/242897/652c60ec-00d0-cbc7-3e93-b1455f6fc812.png)


# 感想
 - 台帳作成がマネジメントコンソール上で完結していてすごく楽
 - 数クリック作成されるのはこれまでのブロックチェーンサービスと違って良い体験
 - RDSのSQLのように半構造のドキュメントを扱えるのは良い
 - 更新履歴の持ち方や整合性を考える必要がなくなるのはすごく良い
 - 検証するユースケースはよく分からない…………