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 5 years have passed since last update.

Hyperledger Fabricの初期データ登録に関し

Last updated at Posted at 2020-02-07

ブロックチェーン・アプリケーションの初期データ登録

**この記事の需要があるのかは分からない…**備忘録的に残しておく。
ブロックチェーンでchaincodeを書き、いざ初期データを登録しようとするとchaincode経由でしか登録できないことに気がついた。登録するデータが少量なら問題無いが、数万~数百万もデータがあると1件ずつ登録するには時間がかかりすぎてしまう。
当初はPostgreSQLと連携して登録するコードをGOで書いたが、ローカルだとビルドできるがchaincodeのインストール時ビルドが通らずコケてしまう。相性が悪いようだ。

そこで1つの文字列引数に可能な限りのデータを詰め込むことを考えた。つまり、

CSVファイル→zlib(compress)で圧縮→BASE64で文字列化

を行うツールで入力引数を作成し、chaincodeで逆を行えば良い。
このロジックでは、zlibで約1/5に圧縮されBASE64で約10%ほどサイズが増えるようだ。

エンコードツールのコード

encode.go
package main

import (
        b64 "encoding/base64"
        "fmt"
        "flag"
        "bytes"
        "os"
        "io/ioutil"
        "compress/zlib"
)

func main() {

        // コマンドライン引数をパース
        _ = flag.Int("flag1", 0, "flag 1")
        flag.Parse()

        // CSVファイルのオープン
        f, err := os.Open(flag.Args()[0])
        if err != nil {
                fmt.Println("os.Open:error")
        }
        defer f.Close()

        // CSVファイルを読み込みzlibで圧縮
        b, err := ioutil.ReadAll(f)
        var sbuff bytes.Buffer
        zlibw := zlib.NewWriter(&sbuff)

        zlibw.Write(b)
        zlibw.Close()

        // zlibで圧縮したデータをBASE64で文字列化
        sEnc := b64.StdEncoding.EncodeToString(sbuff.Bytes())

        // 標準出力へ出力
        fmt.Println(sEnc)
}

使い方は、CSVファイルを引数に呼ぶだけ。結果は標準出力へ。

ls -l data.csv
-rw-r--r-- 1 root root  405488 12月 27 22:02 data.csv #元データ

./encode data.csv > data.b64z

ls -l data.b64z
-rw-r--r-- 1 root root  99525  2月  7 10:59 data.b64z #zlib圧縮+BASE64

chaincodeでデコード

createAssetForB64Z
func (s *SmartContract) createAssetForB64Z(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

        fmt.Println("createAssetForB64Z:START")
        // 入力引数チェック
        if len(args) != 1 {
                return shim.Error("Incorrect number of arguments. Expecting 1")
        }

        // 入力引数の文字列をBASE64デコード
        argAsBytes, err := b64.StdEncoding.DecodeString(args[0])
        if err != nil {
                return shim.Error("b64.StdEncoding.DecodeString:error")
        }

        // BASE64デコードしたバイト列をzlibでデコード
        var sbuff bytes.Buffer
        sbuff.Write(argAsBytes)
        var dbuff bytes.Buffer
        zlibr , err := zlib.NewReader(&sbuff)
        if err != nil {
                return shim.Error("zlib.NewWriter:error")
        }

        io.Copy(&dbuff, zlibr)
        zlibr.Close()

        // CSVデータとして取り出す
        recs := csv.NewReader(strings.NewReader(string(dbuff.Bytes())))
        var asset Asset
        for {
                rec, err := recs.Read()
                if err == io.EOF {
                        break
                }
                if err != nil {
                        fmt.Println("csv.NewReader:error")
                        break
                }

                asset.Year    = rec[1]
                asset.Month   = rec[2]
                asset.Mileage, _ = strconv.Atoi(rec[3])
                asset.Battery, _ = strconv.Atoi(rec[4])
                assetAsBytes, _ := json.Marshal(asset)

                // ステートDBへPUT
                APIstub.PutState(rec[0], assetAsBytes)
        }

        fmt.Println("createAssetForB64Z:END")
        return shim.Success(nil)
}

入力引数として可能なサイズとは

xargs --show-limitsで表示できる。

xargs --show-limits
Your environment variables take up 583 bytes
POSIX upper limit on argument length (this system): 2094521
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2093938
Size of command buffer we are actually using: 131072
Maximum parallelism (--max-procs must be no greater): 2147483647

2,093,938バイトがMAXのように書いてあるが、実際には131,072バイトまでのようだ。
また、構築している環境が非力な場合※はchaincode処理でタイムアウトが発生するので、split -lコマンド等でCSVファイルを分割し調整する必要がある。AWS等のパブリッククラウドを使いXeon2CPU、4GBメモリなら131,072バイトを超えなければ問題なく実行できる。
※自宅オンプレ:Pentium+Atom混在の非力なKubernetes構成みたいな

実際に呼び出してみる(cliを利用)

  • Docker ComposeでHyperledger Fabricを構築している場合
createAssetForB64Z.sh
# !/bin/bash
files="data/*.b64z"
for file in $files; do
        echo $file
        read line < $file
        docker exec cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n asset -c '{"Args":["createAssetForB64Z","'${line}'"]}'
done
  • KubernetesでHyperledger Fabricを構築している場合
createAssetForB64Z.sh
# !/bin/bash
files="data/*.b64z"
for file in $files; do
        echo $file
        read line < $file
        kubectl exec -i cli -- bash -c "peer chaincode invoke -o orderer-example-com:30050 -C mychannel -n asset -c '{\"Args\":[\"createAssetForB64Z\",\"${line}\"]}'"
done

実際の効果はどうだったの?

1万件のデータを1件毎にcliバッチを使ってcreateAssetすると次の日になりそうな勢いだったのがこの方法だと、非力オンプレ・AWSの両方で数十秒のオーダーで完了した(両環境共にKubernetes上にHyperledger Fabricを構築している)。

やりたいことが実現できて満足だ。

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?