1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Hyperledger FabricのState DBをPostgreSQLを使って初期化する

Last updated at Posted at 2020-06-20

InitLedgerで既存のRDBからState DBの初期化を実施します

Hyperledger Fabric(以下HF)のDB初期化には色んな方法があると思います。ちなみに、サンプルプログラムで有名なFabcarでは、10レコード分のデータをハードコーディングでState DBへ登録しています:-(
ここでは、レガシーなRDBであるPostgreSQLからデータを読み出し、State DBへ登録する方法を紹介します。
#環境について
動作環境については次の通りです。

  • Ubuntu 18.04.4 LTS
  • docker-compose 1.26.0
  • docker 19.03.11
  • HF 2.1.1
  • go 1.14.4
  • PostgreSQL(Dockerイメージ) 12.3(latest)

#データ構造を考える
これから自動運転の社会になってくると、車は資産として自動車メーカーが管理するのではないかと予想しています。車自体のテクノロジーが高度になりすぎて町工場では対応できないでしょう…
ということで、車の状態を最低限管理できる項目を並べたデータ構造とします。サンプルだし。

type Asset struct {
        Year    string `json:"year"`    // 初度登録年
        Month   string `json:"month"`   // 初度登録月
        Mileage int    `json:"mileage"` // 走行距離(km)
        Battery int    `json:"battery"` // バッテリーライフ(%)
}
type QueryResult struct {
        Key     string `json:"Key"`    // 車台番号
        Record  *Asset
}

バッテリーライフは充電状態ではなくバッテリー自体の劣化状態を意味しています。
(初年度登録年・月は製造年・月の方がより良いかもしれません)

下準備

下準備をします。PostgreSQLのテーブル定義は端折るのでGoogle先生に聞いてください;-)

ダミーデータの作成

何はなくともダミーデータ。
1万台分のデータをPostgreSQLに準備します。ダミーデータをでっち上げるgoプログラムを書いてPostgreSQLのDBへ流し込みました。

  1. 1万台分のデータをCSVファイルに出力(goプログラム)
  2. PostgreSQLのコンテナへCSVファイルをコピー(/tmpなど)
  3. PostgreSQLのコンテナへ入り、\copyコマンドでインポート

もちろん、予めPostgreSQLのDBへテーブルを定義しておく必要があります。
CSVファイルの内容は次の通り。

Key 初年度登録年 初年度登録月 走行距離 バッテリーライフ
"JXT4EGSP0AX100001" "2010" "04" 33880 78
"JXT5EGSP0AX100002" "2010" "02" 6360 96
"JXT4EGSP0AX100003" "2010" "09" 41040 73
: : : : :

このKey値は、VIN(Vehicle Identification Number)コードと呼ばれる17桁の文字列です。上記はダミーデータなのでもちろん架空の値です。その他の値もrandを使って生成しています。初年度登録年はVINと関係してくるのでそれなりのロジックで生成しました。走行距離もそれっぽく。

chaincodeでPostgreSQLを使えるようにする

goのchaincodeモジュールにPostgreSQLを追加します。
いつからかgoはgo.modというファイルでモジュールを管理しているようです。周回遅れ過ぎです。
当初、ローカルな$GOHOMEでchaincodeを書いてビルドできていたのに、HF上へデプロイするとできないのでハマりました。ローカルな環境ではgo get github.com/lib/pqをしていたのでビルドできていたのです。
HFのchaincode置き場でも同様にgo get github.com/lib/pqする必要がありました。assetという名前でchaincodeを作ったので、ディレクトリfabric-samples/chaincode/asset/goを作ってfabcarからgo.modgo.sumvendorをコピーしてきます。その際、go.modのmoduleassetへ変更します。

# pwd
/root/fabric/fabric-samples/chaincode/asset/go
# ls
asset.go  go.mod  go.sum vendor
# go get github.com/lib/pq

これでgo.modとgo.sumが自動的に更新されます。

go.mod
module asset

go 1.13

require (
        github.com/hyperledger/fabric-contract-api-go v1.1.0
        github.com/lib/pq v1.7.0
)

go.modにgithub.com/lib/pq v1.7.0が追加されました。

PostgreSQLのdocker-composeファイルを作成する

HFのpeerからPostgreSQLのコンテナへアクセスできるようにするcomposeファイルを定義します。
docker-compose-pgsql.yamlとしてfabric-samples/test-network/dockerディレクトリへ置きます。
定義内容は次の通り。

  1. HFと同じネットワークで稼働させる
  2. コンテナ名を定義する(これがホスト名になります)
docker-compose-pgsql.yaml
version: '2'

networks:
  test:

services:
  pgsql:
    image: postgres:latest
    environment:
      POSTGRES_PASSWORD: secret
    volumes:
      - /tank/pgsql-data:/var/lib/postgresql/data
    ports:
      - 5432:5432
    container_name: pgsql
    networks:
      - test

testというネットワークにpgsqlという名前でデプロイします。
察しの良い方は気づくかもしれませんが、DBはZFS上に永続化しています。

###fabcarの環境をassetの環境へ書き換える
今回はサンプルコードのfabcar環境を流用しています。

書き換えるファイルは次の2つです。

  • fabric-samples/test-network/network.sh
  • fabric-samples/test-network/scripts/deployCC.sh

network.shfabcarassetへ文字列置換して、PostgreSQLのupとdownの記述を追加します。
deployCC.shは文字列置換のみでOKです。chaincodeのFunction名も必要に応じて変更します※。
queryAllCars -> QueryAllAssetsみたいな

# diff network.sh network.sh.org
27c27
<   echo "      - 'deployCC' - deploy the asset chaincode on the channel"
---
>   echo "      - 'deployCC' - deploy the fabcar chaincode on the channel"
244,250d243
<     echo "############################"
<     echo "##### Start PostgreSQL #####"
<     echo "############################"
< 
<     IMAGE_TAG=${PGSQL_IMAGETAG} docker-compose -f $COMPOSE_FILE_PGSQL up -d 2>&1
< 
<     echo
407d399
<   docker-compose -f $COMPOSE_FILE_PGSQL down --volumes --remove-orphans
425c417
<     rm -rf channel-artifacts log.txt asset.tar.gz asset
---
>     rm -rf channel-artifacts log.txt fabcar.tar.gz fabcar
452,453d443
< # docker-compose.yaml file if you are using postgresql
< COMPOSE_FILE_PGSQL=docker/docker-compose-pgsql.yaml

作業ディレクトリは、fabric-samples/fabcarfabric-samples/assetでコピーします。
中のシェルはchaincodeに依存していないので、そのまま使えます。

  • startFabric.sh:HFを開始するシェルです。HFコンテナ群のデプロイからchaincodeのデプロイ、初期化・全件クエリまで自動で実施します。
  • networkDown.sh:全てを掃除してなかったことにしてくれます。

fabcarディレクトリには他にもディレクトリがありますが、不要なので削除しても構いません。

InitLedgerの実装

先ずは必要なライブラリをインポートします。

asset.go
import (
        "bytes"
        "encoding/json"
         :
        "database/sql"
        _ "github.com/lib/pq"
)

"database/sql"_ "github.com/lib/pq"を追加しています。

asset.go
func (s *SmartContract) InitLedger(ctx contractapi.TransactionContextInterface) error {
        fmt.Println(time.Now())

        db, err := sql.Open("postgres", "host=pgsql port=5432 user=postgres password=secret dbname=asset sslmode=disable")
        defer db.Close()

        if err != nil {
                return fmt.Errorf("sql.Open: %s", err.Error())
        }

        rows, err := db.Query("SELECT * FROM asset;")

        if err != nil {
                return fmt.Errorf("sql.Query: %s", err.Error())
        }

        var id string
        for rows.Next() {
                var asset Asset
                rows.Scan(&id, &asset.Year, &asset.Month, &asset.Mileage, &asset.Battery)
                assetAsBytes, _ := json.Marshal(asset)
                ctx.GetStub().PutState(id, assetAsBytes)
        }

        fmt.Println(time.Now())
        return nil
}

恐ろしくシンプルなコードです。ホスト名・ユーザ名・パスワード、諸々がベタ打ちなのはサンプルなのでご愛嬌。goがわからなくても読むとなんとなく理解できると思います。

  1. DBをオープン
  2. 全レコードのクエリ("SELECT * FROM asset;")
  3. 1レコードずつ読み取って(rows.Next())、カラムに分解(rows.Scan)
  4. rows.Scanと同時にAsset構造体へ設定
  5. json.MarshalしてState DBへPutState

InitLedgerを実行

1万台分のデータの登録にどのくらいの時間がかかるのでしょう?
実行環境は4C/4TのPentiumにメモリ16GBを積んだマイクロサーバー(LIVA Z)です。
InitLedgerにtime.Now()をプリントするコードを仕込んであるのでpeerのログを覗いてみます。

# docker logs 3575482a43e3
2020-06-20 10:05:34.977106747 +0000 UTC m=+5.210858066
2020-06-20 10:05:42.026225952 +0000 UTC m=+12.259977077

logsに続く文字列はpeerのコンテナIDです。
State DBへの登録に8秒かかっていますね。1万件なら早いのではないでしょうか。HW環境的にも。
全件クエリにはどのくらいかかるでしょうか。

# time ./QueryAllAssets.sh
途中省略
"Record":{"year":"2019","month":"09","mileage":2565,"battery":99}},{"Key":"JXT5EGSP0KX100979","Record":{"year":"2019","month":"01","mileage":353,"battery":100}},{"Key":"JXT5EGSP0KX100980","Record":{"year":"2019","month":"10","mileage":7060,"battery":96}},{"Key":"JXT5EGSP0KX100982","Record":{"year":"2019","month":"02","mileage":1806,"battery":99}},{"Key":"JXT5EGSP0KX100984","Record":{"year":"2019","month":"10","mileage":9240,"battery":94}},{"Key":"JXT5EGSP0KX100985","Record":{"year":"2019","month":"03","mileage":36,"battery":100}},{"Key":"JXT5EGSP0KX100991","Record":{"year":"2019","month":"04","mileage":1160,"battery":100}},{"Key":"JXT5EGSP0KX100992","Record":{"year":"2019","month":"09","mileage":6219,"battery":96}},{"Key":"JXT5EGSP0KX100995","Record":{"year":"2019","month":"08","mileage":3792,"battery":98}},{"Key":"JXT5EGSP0KX100996","Record":{"year":"2019","month":"11","mileage":10868,"battery":93}},{"Key":"JXT5EGSP0KX100998","Record":{"year":"2019","month":"09","mileage":5967,"battery":97}}]

real    0m8.896s
user    0m0.175s
sys     0m0.051s

画面出力にほとんどの時間を使っています。処理的にはアッと言う間ですね。

比較のために、peerコマンドを使ってシェル内ループで1万件を登録してみました。

# time ./CreateAllAssets.sh
途中省略
2020-06-21 12:33:00.825 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
2020-06-21 12:33:00.951 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
2020-06-21 12:33:01.072 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
2020-06-21 12:33:01.195 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200
2020-06-21 12:33:01.319 JST [chaincodeCmd] chaincodeInvokeOrQuery -> INFO 001 Chaincode invoke successful. result: status:200

real    20m44.886s
user    17m9.930s
sys     3m46.809s
#

差は歴然でした。

最後に

HFとレガシーRDBとの連携情報があまりなかったので書いてみました。RDBからのReadができればWriteもできそうです。個人情報などはRBDへ保存して都度HFと連携すると良いと思います。また、データ構造にLocation情報も追加して、ブロックチェーンの特徴でもある取引履歴を応用したトレーサビリティ機能が実装できそうです。
RBDとの連携は興味深いのでまた投稿しようと思います。

みなさんのお役に立てたら幸いです。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?