University of Waterlooの研究室がHyperledger Fabricを7倍速くする方法を提案
Hyperledger Fabric(HLF)をリエンジニアリングしてスループットをほぼ7倍(3000⇒20000)にしてやったぜというペーパーをカナダのUniversity of Waterlooの研究室が公開、というニュースが出ていました。
そのペーパーは以下で読めますので読みましょう。
FastFabric: Scaling Hyperledger Fabric to 20,000 Transactions per Second
とりあえず開いたけど英語だし字がつぶつぶしてるじゃん、ヤダーーッ!ってなりましたか?わたしもウッってなりましたががんばって読んだ、そしてわたしはしんせつなのでペーパーの要点を以下にまとめていきます。
FastFabricペーパーの要点
全体
- HLFのアーキテクチャ全体にわたって、スケーラビリティ強化のための設計の最適化を検討、実験した
- 結果として、最適化の適用前後でエンドトゥエンドトランザクションのスループットが3000TPS⇒20000TPSとほぼ7倍の高速化を実現できた
- 対象としたHLFのバージョンはv1.2だが、最近リリースされたv1.4や、今後リリースが予定されているv2.0系にも適用できるものと考えられる
最適化の内容
大きくOrderer/Ordering Serviceの設計を改善するものと、Peerの設計を改善するものに分けられます。以下にそれぞれ整理していきます。
Orderer/Ordering Service
Transaction HeaderとPayloadの分離
- Ordering Serviceはトランザクションの順序の確定(トランザクション順序のコンセンサス)を行っているが、この順序付けには本来Transaction IDだけがあれば十分で、Payloadは不要であるが、既存の実装ではPayloadも含めてKafkaクラスタに渡されておりサイズに係るオーバーヘッドが生じている
- Ordererがクライアントからトランザクションを受け取った際にTransaction IDを取り出し、対応するPayloadはOrdererがローカルに保持したうえでKafkaクラスタにTransaction IDのみを渡す
- Kafkaでの順序付け後に保持しておいたPayloadを再度構成し、トランザクションをブロック化してPeerに配布する
- このアプローチは他のOrdering Serviceのコンセンサスモデルにも適用可能
- 既存のOrdering Serviceのインターフェースに影響しないためクライアント、Peerには影響がない
メッセージのパイプライン化
- 既存の実装ではあるクライアントのリクエストについて、受付~コンセンサスシステム(Kafka)へのトランザクションの送信はひとつずつしか処理できなかった
- あるクライアントからの複数のリクエストを、それらが単一のgRPCセッション上でのものであっても、同時に処理することができるようにした
- リクエストを並列に受け付けるためのスレッドプールを保持し、スレッドごとにコンセンサスシステム(Kafka)へのTransaction IDの送信およびクライアントへのレスポンスを順次行う
Peer系
State DBのハッシュテーブルへの置き換え
- 多くのシナリオではWorld Stateのサイズはインメモリで扱える程度に収まると考えられる
- World StateをState DB(LevelDB/CouchDB)で扱うとストレージとのI/Oに相応のコストがかかるが、インメモリにすることで不要となる
- また、データベースの持つシステム保護(ACID等)はブロックチェーン自体の持つそれと重複しており、無駄なオーバヘッドとなっている
- World Stateをインメモリのハッシュテーブルで扱うことでこれらの課題を解消し、PeerのWorld Stateにアクセスする処理全般を高速化する
- Peer障害等のための備えとして揮発しない永続ストレージによる補強は次の分散ストレージを利用して行う
分散ストレージクラスタの利用
- 永続ストレージへのブロックの保存およびWorld StateのバックアップはPeerのタスクから処理を切り離す
- 永続ストレージとしてHadoop MapReduceやSparkなどの分散ストレージクラスタを使うことで高速化する
- この場合、分散ストレージクラスタのひとつひとつのノードにはブロック、World Stateの全体ではなく部分のみが保持される(クラスタとして全体を保持する)
PeerのValidation/CommitとEndorsementの役割を分離
- 現状のHLFではEndorsementを行うPeerはEndorsementを行う脇でValidation/Commitも行わなければならない
- EndorsementもValidation/Commitもそれぞれコストがかかる処理である
- Validation/Commitのみを行うPeerとEndorsementのみを行うPeerに役割を分ける
- Validation/Commitのみを行うPeerでは受け取ったトランザクションの検証を行う(現状のCommitting Peerと同様)
- Validation/Commitのみを行うPeerからValidなトランザクションによるWorld Stateの更新をEndorsementのみを行うPeerに伝えるようにする
- Endorsementのみを行うPeerは受け取った更新をValidationせずにそのままWorld Stateに反映する
Validationの一部処理の並列化
- Peerの行うブロックおよびトランザクションのValidationのうち、ブロックヘッダおよびトランザクションヘッダを参照してのSenderの正当性チェック、Endorsement Policyを満たしているかのチェックおよびシンタックスチェックなどは並列化できる
- これらのチェックを複数のgo routineで並列化し、複数トランザクションおよび複数ブロックのチェックを同時に行う
- Read-Write Setのチェックは整合性保護の関係で並列化できないので単一のgo routineで行う(現状のまま)
キャッシング
- HLFのブロック構造は、開発の経緯などによりかなり多くの層に分かれている
- PeerはValidationを行ううえで受け取ったバイト配列をブロック構造の各層ごとにアンマーシャルしており、メモリを大量に食っている
- しかも現状、アンマーシャルしたデータはキャッシュされておらず、Validationの中で使うたびに都度マーシャルしている
- このアンマーシャルしたデータをキャッシュしておくことで、アンマーシャルに伴うオーバーヘッドを回避する
- あるブロック(に含まれるトランザクション)に対するValidation/Commitを終えた段階でそのブロックに関するキャッシュは不要となるため、破棄できる
所感
全体的になるほど~という印象です。いくつかの改善についてはWorld Stateが十分に小さい場合、などの条件がつくものもありますが、総じてイケるんじゃないかと考えています。
ただ、OrdererのPayloadの分離はなんか複数Ordererノードある場合にクライアントから直にトランザクションを受け取ったOrdererノード以外にPayloadを渡してやるステップがないので機能しないような気がするんだけどわたしの理解がまちがっている?
また、20000TPS、7倍ということでめちゃめちゃすごいやんけと感じると思うんですが、この研究でのシナリオは全体に台帳が扱うKey-ValueのValueがわりと単純なケースを想定しており、この成果もそのようなシナリオでのものだという点には注意が必要です。おそらくValueがまあまあ多要素なJSONだったりするとボトルネックが別のところに発生するので、少なくとも7倍速になったり20000TPSになったりはしないかと。
ともあれこのように改善案が出てくるのは喜ばしいことです。また、クライアントから使うインターフェースに変更がないというのも既存HLFユーザーとしては嬉しい点ですね。
ブロックチェーンのパフォーマンスのベンチマークとしてVISAの50000TPSというのがよく挙げられますが、案外早いうちにこのくらい出せるようになるかもしれないですね。