ビットコインノードの同期のパフォーマンス
こんばんは。最近、ライトニングノードをいくつか立ち上げる理由があり、自分がコントロールするノードそれぞれにビットコインノードを併設するのは負担が大きく感じたため、ライトニングノードを遠隔のビットコインノードに接続するNeutrinoという動作モード(いわゆる「ライトクライアント」)を活用することにしました。ところが、Neutrinoを通してライトニングノードにブロックチェーンのデータを送ることができるビットコイン実装はbtcdなのです。ノード実装にはBitcoin Coreの他にもbtcd、bcoin、Bitcoreなどがあり、bcoinなら自分でも使用したことがありますが、btcdは同期が非常に遅いことに気が付きました。同期を開始して5日以上経ちましたが、まだ2019年6月の段階なので、あと2~3日かかりそうです。比較対象として、今年同じ環境でBitcoin Coreを最初から同期したときは12時間前後で完了しました。ビットコインノードの同期がどのように進歩してきたか、btcdはなぜ同期が遅いのかなど、感じたことや調べたことをまとめてみました。
ビットコインの分散性を維持する上で、ブロックのサイズを小さくする目的は同期コストや検証コストを下げることなので、ブロックのサイズに関係なく同期や検証の速度を上げる技術は非常に重要なものとなります。
実装ごとのパフォーマンス
ちょうど一年ほど前に、Jameson Lopp氏がビットコインの実装ごとの同期パフォーマンス(所要時間)を計測した記事が出ていました。全ての実装に共通の設定として過去全ての署名を検証する設定を行い、また実装ごとにCPUとRAMの使用量をできる限り大きくしたそうです。
結果だけをまとめると:
・Bitcoin Core 0.19 - 6時間39分 (前年比 +28%)
計測した結果、ネットワークやストレージ、RAMではなく、CPUがボトルネックだったそうです。
・bcoin - 18時間29分 (前年比 -44%)
パフォーマンスの改善になると思って導入したUTXOキャッシュが実は改善にならなかったため排除した結果、そこそこ早い同期が実現しました。それでもBitcoin Coreには遠く及びません。
・btcd - 3日3時間12分 (前年比 -27%)
btcd自体はLndのバックエンドとして使用されるようになるまでは一時的に開発がほぼ中断していたため、改善の蓄積という面で不利ですが、それでも前年よりは確実にパフォーマンスがよくなっていたそうです。Bitcoin Coreと比べると10倍以上時間がかかっています。
・Gocoin - 19時間56分 (前年比 +59%)
自分は聞いたことがありませんでしたが、btcdと同じくGoという言語を用いたビットコイン実装だそうです。CPU使用率は常時50%程度で、ボトルネックは不明だそうです。
・Libbitcoin - 27時間37分 (前年比 +35%)
ディスクからの読み込みが多いのが遅さに繋がっている可能性を指摘されています。また、最初から全部検証する設定を行うのが大変だったようです。
・Parity Bitcoin - 2日2時間10分 (前年比 +31%)
通信帯域幅の最適化は優れているようですが、CPUの活用やRocksDBというデータベースが曲者の可能性があるようです。・
Stratis - 失敗
4日3時間かけてブロック高428,002まで進みましたが、使用済みのUTXOのデータを削除しないバグのせいでストレージがいっぱいになりクラッシュしたそうです。ストレージが十分にあっても、1週間はかかる計算です。全体的に見ると、bcoinとbtcdは1年分のブロックチェーンの成長にもかかわらず大きく所要時間を縮めており、順調に改善していることがわかります。Bitcoin Coreは単純にブロックチェーンのサイズの増加に影響されていそうです。例えばトランザクションを検索しやすくする情報を追加で記録する実装があるなど、厳密には同じ状況での比較とは言えませんが、単純に同期速度を比較する上ではいいベースラインだと思います。
「ブロックチェーンの同期って、最初から全部検証するんじゃないの?」と思った方は、鋭い質問です。後述しますが、検証はCPUの負担が大きく、PoWのおかげで長期間を遡る改ざんは難しいという観点から、一定のブロックより前のブロックやトランザクションについては既定では署名を検証しない実装がほとんどです。ただし、その多くは設定すれば最初から検証することもできます。
初回同期のダウンロードの略歴
初回同期のダウンロードの負担を削減する機能で、有名なものを挙げてみました:初回同期時に全部のブロックをダウンロードする前にブロックヘッダーをダウンロードすることで、最初から最長のチェーンを判断できます。これによって無駄なブロックをダウンロードする時間を削減できるようになったほか、Disk-fill攻撃という、大量の無意味なオーファンブロックをダウンロードさせてディスク容量を逼迫させる攻撃を防げました。(2015年に導入)
AssumeValidは先述した「署名の検証は所定のブロック以降からにする」ことで検証処理の負担を減らし、同期時間を削減する機能で、使用が必須ではありません。所定のブロック以前のコインを動かしているのが本当の秘密鍵の所有者かは検証しませんが、不正発行やブロックハッシュなどは検証します。ブロック自体は全てダウンロードします。(2017年に導入)
AssumeUTXOは、AssumeValidとブロックのように、とある時点でのUTXOセットを検証せずに最初から正しいと受け入れる選択肢を作る提案です。現在使用可能なビットコインを表すUTXOセットは本来はブロックチェーンを一から同期してトランザクションを追うことで作っていきますが、これには上述の通り時間がかかるので、フルノードをすぐに使用したい初回のユーザーには優しくありません。そこで、UTXOセットのスナップショットを受け入れてそこから先を短時間で検証し、リスクを受け入れればとりあえず送金できるようにします。その後ジェネシスブロックから再度検証してスナップショットが不正ではないか確認します。この時点で普通のフルノードと同等の検証を行ったことになります。(未導入)AssumeValidとAssumeUTXOは当然ながら(任意だったり、一時的だったりしますが)トラストが必要になりますが、多くの場合に選択肢に上がる程度のトレードオフなのではないでしょうか。「検証できるけどしない」という、フルトラストとトラストレスの中間領域が興味深いな、と思いつつ、ビットコインのコードを読まずにソフトを動かすのも似たようなものだと感じます。
ダウンロード関連のもの以外にも、最近特許が切れて次回のメジャーアップデートで既定で利用されるようになる署名検証の高速化や、ネットワーク関連の効率化など、同期時間の短縮には様々な側面からのアプローチがあります。逆に、どれか1つを改善できたとしても、ボトルネックとなる側面が短縮の限界を決めるので、やってみるまで実際の時間短縮効果はわかりません。
BTCDはなぜ遅いのか
話は戻りますが、btcdはGoという言語で書かれているビットコイン実装です。ところが、Goに精通しているわけではないプログラマによって作成されたからか、Bitcoin CoreのC++に近いコードで書かれており、これがGoの仕様を全く活用できていなくてパフォーマンスの足を引っ張っているようです。少なくとも、GitHubではそう議論されています。特にこの投稿によると、特に排他制御の書き方がGoではとても遅いやり方だそうです。また、プログラムのメモリ管理の一環として、定期的に古くなって使わなくなったメモリ領域を開放してまた使えるようにするガベージコレクションというものがありますが、LevelDBというデータベースを利用するコードの設計が悪いため、これが効率悪く発生しているのも理由の1つのようです。使う人が少ない実装で、パブリックなノードに至っては世界に13台しかいないらしいので開発リソースが集まらないのは仕方ないと思いますが、同期に1週間はかなりつらいです。低スペックなVPSやラズパイだと1ヶ月かかりそう。同期遅いな…ノードうるさいな…と思いながら、ネットワーク上のノードの大半がBitcoin Coreに偏るのも頷けると納得してしまう自分でした。
次の記事
読者になる
一緒に新しい世界を探求していきましょう。
ディスカッション