まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:hash

僕自身はオープンソース製品である Hyperledger FabricHyperledger Composer を使ったブロックチェーンプラットフォームや、その対応アプリケーションを業務で開発する機会が多くあります。

ありがたいことに実際に作る機会も多いのですが、「作る」という決断に到達する前に方針を変更することも少なくありません。よくある理由としては「(現行の)ブロックチェーンに向かない要件」であるというケースです。パフォーマンス要件だったり、検索機能の要件だったり、現在のブロックチェーンでは本格運用に向かないケースが前提になっていたりすると、そのことを伝えた上で「それでも作るかどうか」を判断していただいています。そして「作らずに検討し直す」と判断されることもある、という意味です。

でも、これはブロックチェーンを使いたいと思いながらも「現在のブロックチェーン技術」では向かない、と判断されたからです。需要としては存在していることも意味しています。

そんな場面を多く見ている中で「だったらブロックチェーンといえるかどうかはともかく、同じような仕組みで耐改竄性を高めながら、この要件も満たせるようなデータストアの仕組みを作れないか?」と考えるようになりました。それが今回紹介するものです。


【「ブロックチェーン」の定義】
まず最初に、以下で紹介するアプリケーションは「ブロックチェーン」と呼べるものかどうかと言われると、私個人的には「呼べないのではないか」と考えています。これは「ブロックチェーンの定義」をどのようにするか、という問題にも関わってくるのですが、ここでは1つの目安として、JBA("Japan Blockchain Association" 日本ブロックチェーン協会)が定義した以下の2つの定義を満たすものをブロックチェーンと定義するものとします:

・ビザンチン障害を含む不特定多数のノードを用い、時間の経過とともにその時点の合意が覆る確率が0へ収束するプロトコル、またはその実装をブロックチェーンと呼ぶ。
・電子署名とハッシュポインタを使用し改竄検出が容易なデータ構造を持ち、且つ、当該データをネットワーク上に分散する多数のノードに保持させることで、高可用性及びデータ同一性等を実現する技術を広義のブロックチェーンと呼ぶ。

(参照)
http://jba-web.jp/archives/2011003blockchain_definition

2018062901



要するに「ブロックチェーンとは『ブロック』が『チェーン』のようにつながって格納される仕組み」というゆるい定義ではなく(注 私個人的にはこれがブロックチェーンの定義でもいいと思ってます)、「ハッシュポインタを用いて接続し、データを複数のノード上に分散して保持し、かつその複数ノードが耐ビザンチン障害性を持っている実装」のことを「ブロックチェーン」と定義する、ということです。 そしてこの定義の上で考えた場合、以下で紹介するものはブロックチェーンではない、ということになることを予め伝えておきます。

自分の考えは、上記のものだと耐ビザンチン障害性をもたせるためのコンセンサスアルゴリズムに問題が発生したり(最近だと「51%攻撃」と呼ばれる手法が話題になりました)、そのためにトランザクションのパフォーマンスを高く実現するのが難しかったりする、と思っています。なのでこの要件が必須でないケースでは、この条件を確保しないことでパフォーマンスや検索機能など他の要件機能を向上させることができるのではないかと考えました。


【色々出てきている】
現状のブロックチェーンにはパフォーマンス遅延や電力消費など当初は想定していなかったことが問題になっていることもあって、それらの苦手部分に対処した新しい仕組みも生まれています(それらが上記ブロックチェーンの定義を満たしているかどうかはともかく)。例えば全参加者のコンセンサスを得る上でのパフォーマンス遅延を解決するために、ランダムに選ばれた一部参加者のコンセンサスを得るように改良したものも出ているようです:
ブロックチェーンを超える?ハッシュグラフ(Hashgraph)とは?


【作ってみようと思ったもの】
今回、自分は以下4つの特徴を持つようなデータストアシステムを新たに開発してみようと思いました:
(1)ハッシュポインタを利用した、改竄検出が容易なブロックチェーンの仕組みはそのまま取り入れる
(2)データは中央集権管理型とする。これによりコンセンサスアルゴリズムや耐ビザンチン障害性の問題を発生させなくした上で、トランザクションパフォーマンスの向上を実現する
(3)データそのものは分散データストアに格納して(格納可能にして)高可用性を実現する。つまり単なる1ピア運用とは異なる
(4)バイナリデータやファイル添付を含む大きなレコードのデータストアへの格納や全文検索機能、データを読み書きするための REST API を標準装備する



(2)の時点で上記ブロックチェーンの定義を満たさなくなっています(ブロックチェーンは非中央管集権管理型)。(3)ただ単に1ピアにデータを格納するのではなく、論理的な1データストアが実際には複数のロケーションに分散できるようにしており、これにより特定ロケーションが単一障害点とはならないようにします。そして(4)ブロックチェーンが比較的苦手とする巨大オブジェクトの格納や検索機能もこのシステムでは標準装備することを目標としています。加えて標準で REST API を準備することで(導入すればすぐ使えるようになって)導入時の負担軽減も実現するつもりです。

2018062902



【作っているもの】
上記仕組みを実現するためのソフトウェア(というかプラットフォームというか、API 群というか・・)を開発中です。まだ開発中ですが Github にて MIT ライセンスで公開しています:
https://github.com/dotnsf/hashchainsolo


上記の事情もあり、開発コード名には「ブロックチェーン」は含めず「ハッシュチェーンソロ」という表現にしました。レコードのブロックをチェーンのようにつなげていくものではあるのですが、上述の定義を満たすようなものではなく(むしろそこを犠牲にしている)、自分も「ブロックチェーン」という表現にこだわるわけではないので「ハッシュチェーン」という表現にしたかったのですが、これはこれで別の意味に使われており、ややこしいことになりそうだったので(ブロックチェーンだと単一ピアに相当する設計になるという意味で)「ハッシュチェーンソロ」と命名しています。某○icrosoft Office 365 Solo の影響とかではありませんw

インフラとして、分散データストアには IBM Cloudant を採用しています。Apache CouchDB をベースとした NoSQL 型のマネージド DBaaS で、容量・トランザクションパフォーマンスに制約があるものの無料プランを選択することもできるので、「とりあえず動作確認してみる」ためのハードルは低く実現できると思っています。一方でプランを見直して容量やパフォーマンスを変えたり、要件によってはプライベートクラウド版やオンプレミスソフトウェア版を使うことも可能です。またこの Cloudant 自体がバイナリ(添付ファイル)格納や検索インデックスといった機能を内包しているので、ブロックチェーンでいうところの「ステート DB」に相当する仕組みや外部ファイルシステム等がなくても各種データを格納することができ、上述の4つの特徴を兼ね備えた単独の仕組みを比較的作りやすいと思っています。

#現時点では実装していない余談ですが、IBM Cloudant をデータストアに使っていることで、携帯版の PouchDB と組み合わせてモバイルプラットフォームへの移植も想定しやすい設計にしています。

ハッシュポインタを使って耐改ざん性を高める部分は全てスクラッチで実装しています。タイムスタンプと nonce を併用してマイニングを行い、特定条件(変更可)を満たした時だけハッシュを採用できるようにしています。

2018/Jun/30 の現時点での開発状況は「とりあえず一通りの REST API は動くようになっている」レベルです。時間を見つけてぼちぼちと改良中です。


【Call for Code】
この製品で Call for Code に参加予定です。自然災害の支援を目的としたソリューションのコンテストで、ブロックチェーンを使う事例は多く存在すると思いますが、そのブロックチェーン技術の選択肢の1つとして、既存技術では難しいものも場合によっては解決できる、と思っていて、その実装として提供・参加予定です。




 

ファイルのアップロードを伴うアプリケーションを作っていて悩ましいことの1つに「中身の変わらない(全く同じ)ファイルが別のファイル名でアップロードされることがある」ことです。

アプリケーション側の実装としては、アップロードされたファイルをストレージ等に保存する処理を用意しているのですが、その際のファイル名をどうするか? という問題があります。

例えば "a.jpg" という画像ファイルと、"b.jpg" という異なる画像ファイルがあり、これら2つがアップロードされるケースだけを考えるのであれば、ストレージに保存する際にも元のファイル名をそのまま使えることになります:
2017052201
↑異なる2つのファイルを元のファイル名のまま保存する場合(このケースは問題なし)


ところが更に "a.jpg" というファイル名で、既に保存済みの "a.jpg" とは異なるファイルがアップロードされることもあります。この3つ目のアップロードファイルは "a.jpg" というファイル名で(上書き)保存するわけにはいきません(元のファイルが消えてしまう)。ということは元のファイル名をそのまま使うことは正しい処理ではなくなります:
2017052202
↑同じファイル名で中身の違うファイル保存しようとすると上書きすることになってしまう


また別のケースとして、"c.jpg" というファイル名で、"a.jpg" と中身が全く同じ画像ファイルがアップロードされるケースを考えてみます。この場合、ファイル名そのものは元のもの(c.jpg)を使っても被ることがなくいいのですが、全く同じ画像ファイルを2つ保存することになり、無駄にストレージを消費することになります。画像ファイルであればそのサイズもたかが知れているのかもしれませんが、これが仮想イメージとかだったりすると1ファイルで数10Gバイト消費することもあるため、中身の全く同じファイルであれば複数保存せずにすませたいものです:
2017052203
↑異なる名前で中身の同じファイルを元のファイル名のまま保存すると、無駄な保存領域を使うことになる


上記の問題点を実現する方法として、「同じ中身(バイナリ)のファイルは同じファイル名で、異なる中身のファイルは異なるファイル名を用意して保存する」仕組みを用意する方法があります。で、これを比較的簡単に実現する方法がファイルのハッシュ値を使うことが考えられます:
2017052204
↑ファイルのバイナリデータのハッシュ値をファイル名に使えば、中身が異なるファイルは異なるファイル名になる


試しに Node.js で実装したものを Github に公開しました:
https://github.com/dotnsf/node-upload-sample


画像のバイナリデータ(バイト配列)からハッシュ値を生成しているのは app.js 内の以下の箇所です。Node.js 標準の crypto ライブラリを使って、SHA512 アルゴリズムで path のファイルストリームからハッシュ値を生成している箇所です:
  :
var crypto = require( 'crypto' );

  :
  var path = req.file.path;
  var destination = req.file.destination;

  //. Name after Hash value
  var hash = crypto.createHash( 'sha512' );
  var fstream = fs.createReadStream( path );
  hash.setEncoding( 'hex' );
  fstream.on( 'end', function(){
    hash.end();
    var result = hash.read();

      :

ファイル(画像)をストリーム化してハッシュ値を求め、後ろに元の拡張子を付けたものを保存時のファイル名にしています。


このアプリを実際に動かしてみた様子を以下に紹介します。まず動作確認するにあたって、3種類4つのファイルを用意しました。01.png と 02.png はファイル名は異なりますが、全く同じファイルです。03.png は名前も中身も異なります:
2017052301


これらとは別に、中身の異なる 01.png というファイルを用意しました(ファイル名だけ前述のものと被ります)。これら3種類4つのファイルを全て順にアップロードした時の様子が以下です:
2017052302


まずアプリケーションを起動するとこのような画面になります。登録した画像ファイルが一覧表示されますが、まだ何も登録していない状態では何も表示されません。ここで 01.png (03.png と同じ画像の方)を選択して登録してみます:
2017052301


01.png のハッシュ値が生成され、その値をファイル名として保存されました:
2017052302


このリンクをクリックすると、元の 01.png が登録されていることが確認できます:
2017052303


同様にして、今度は 03.png を登録してみます。すると 01.png と 03.png は異なる画像ファイルであるため、ハッシュ値も異なります。そのため別画像として新たにされます:
2017052304


続けて今度は 02.png (01.png と同じ画像ファイル)を登録します。このファイルはファイル名こそ別ですが実体が 01.png と同じものであるためハッシュ値は 01.png と同じものになります。そのため「既存のデータ」と判断され、新たに画像は登録されません(正確には同じ名前のファイル名で、同じ内容を上書きすることになるので、ファイルは増えず、中身が変わることもありません):
2017052305


更にもう1つの 01.png(元の 01.png とはファイル名は同じだが、中身の異なるもの)を登録してみると、今度は中身の違うファイルなのでハッシュ値も別のものになり、新しいファイルとして登録されます:
2017052306


結果的には3種類4つのファイルを登録しましたが、内容の異なる3つのファイルがアップロードされました。中身の同じファイルは二重登録しない、という当初の目的が達成できました:
2017052307


これでストレージの無駄な空間を使わずに済ませられそうです。

このページのトップヘ