ビットコイン・インスティテュート

RFC: リリースtarballにブロックチェーン1-74000を同梱する?

ビットコインがブロックチェーン情報を格納するblk0001.datは、Windows、Linux、32ビットおよび64ビット間で互換性があるようだ。

であれば、各リリースにブロック1-74000を同梱することで、新規ユーザーの時間を節約できるのではないか?

おそらく、ローカルファイルのインデックス作成と検証のほうが、P2P経由ですべてのブロックをダウンロードするより速く、ネットワークリソースの消費も少ないだろう。

😕 P2Pって、1つのソースではなく多くのユーザーから同時にダウンロードできるから、速いはずじゃないの? (一部のゲーム会社がアップデートの配布にBitTorrentを使っている理由でもある)

RHorning 2010年11月25日14:25 UTC

これについては複雑な思いがある。問題の一部は、SourceForgeという形でネットワークホスティングサービスの無料財が存在すると認識されていることだ。これがあるために、ソフトウェアをリリースする者は、Bitcoinの現状の規模を超えてかなり大量のデータを同梱できる余地を持つことになる。もしコミュニティとしてMiBあたり何ドル、という形で実費を払っているなら、これを配布物に含めないのは考えるまでもないことだと思う。残念ながらこの観点から見ると、SourceForgeは大半の利用者にとって無料財として映る。

もう一つの問題は、ノード間のネットワーク帯域幅もまた無料財として扱われているということだ。本スレッドの中で私は、ネットワーク帯域幅も無料財扱いされない方がよいかもしれないと述べた。実際、無料財扱いされるべきではないと思っているが、それはこの議論からは独立した別の問題だ。

ブロックのダウンロードのためのネットワーク帯域幅は私にとってどちらにしても大差ない。ただし、「オンライン」になろうとしている新しいクライアントがフルのブロックチェーンを取得しようとすると、Bitcoinネットワークから大量のブロックを吸い上げ、そのノードに接続している全員に影響を及ぼすのは事実だ。これは帯域幅に対して「課金」を始めることが極めて有用だと考える理由の一つでもある。こうした挙動を抑制するためにも、ついでに副収入のBitcoinを稼げるようにしてもよい。別のソースから無料でブロックを取得できるようになれば、例えば本体配布物と一緒に無料ファイルホスティング上の二つ目のパッケージを置いたり、新規クライアントの初期化方法をもっとネットワークに優しい形に工夫したりと、もう少し創意のある方法が出てくるかもしれない。

要するに、これは複雑な問題に対するシンプルな解だが、すべての問題を解決するわけではない。例えば、ブロックデータを別形式で保持するクライアントの存在も無視できない。他のソフトウェアクライアントの配布元にもこの種のデータを同梱させる、あるいは最低限を超える数のブロックを入れさせる積極的な理由も特にない。それでも問題提起としては有用で、議論のきっかけになることを願っている。

ウラジミール・ファン・デル・ラーンの投稿(2010年11月25日13:37 UTC)

😕 P2Pって、1つのソースではなく多くのユーザーから同時にダウンロードできるから、速いはずじゃないの? (一部のゲーム会社がアップデートの配布にBitTorrentを使っている理由でもある)

本質的にP2Pチャネルを通じて分散されているものを、従来のクライアント・サーバー配布モデルに入れるのは非常に奇妙に思える。私がもっと検討の余地があると言っているのは、例えばクライアントを長期間オフにしていた利用者向けに、大量のブロックを取得する手段として何らかのBitTorrent由来の配布経路を促す、といった工夫を視野に入れたいからだ。問題は、新しいクライアントがブロックチェーン全体を要求し、そのチェーンを持つまで「マイニング」や新しいトランザクションの確認に参加できない点にある。その問題を解こう。そちらがより大きな問題だ。

もう一つの問題は、ソフトウェアを更新するだけなのにクライアントにこれらのブロックを含めるのは帯域幅の無駄に思えることだ。さらに同じくらい気になるのは、この「古い」チェーンを含むインストーラーによってブロックチェーンが上書きされ、既存のクライアントが現行のブロックまで再度更新を強いられかねない点だ。もちろんこれは明らかにインストーラの不具合だが、起こり得る話だ。無料財だからといって、この方法をとることに他の影響がないわけではない。

確かに、クライアントにデータを同梱するのは単なる応急処置だ。

Bitcoin P2Pプロトコルが大量のブロック転送でこれほど非効率な主な理由があるなら、それは直すべきだ。それはHDDの同期が行われているせいだと思う。初回ダウンロードではこれを保留にするか、ブロック [0..last-10000]についてはプロトコルをよりBitTorrentに近づけるのがいいかもしれない。それらは基本的に確定しているのだから。

時間がかかるのはダウンロードではなく、検証とインデックス作成だ。

帯域幅の点では、アーカイブをダウンロードするよりも効率的だ。Bitcoinはblk0001.datのデータのみをダウンロードし、現在55MBで、blkindex.dat(47MB)は自分で構築する。blkindex.datの構築がすべてのディスクアクティビティの原因だ。

ブロックのダウンロード中は、500ブロックごとにのみデータベースをディスクにフラッシュする。ブロック数が??499や??999で一時停止するのが見えるかもしれない。それがフラッシュしている時だ。

自分で検証とインデックス作成を行うことが、インデックスデータの安全性を確保する唯一の方法だ。信頼できないソースからblk0001.datとblkindex.datをコピーした場合、その中身すべてを信頼できるかどうか知る方法はない。

Berkeley DBの設定を調整して、キャッシュメモリーを有効化または増加できるかもしれない。

MrFlibble 2010年11月25日23:19 UTC

最初の反応は「高速セットアップに+1」だったが、24時間の遅延のほとんどはローカルディスクによるものだった。キャッチアップモード中にデータベースのfsync(?)を無効にすることが最も効果的だろう。

ウラジミール・ファン・デル・ラーンの投稿(2010年11月25日13:37 UTC)

😕 P2Pって、1つのソースではなく多くのユーザーから同時にダウンロードできるから、速いはずじゃないの? (一部のゲーム会社がアップデートの配布にBitTorrentを使っている理由でもある)

良い指摘だ。しかし、ブロックのSHA-256がコードに組み込まれているので、データも同梱するのは完全に合理的だ。ブロックチェーンが500MBを超えたら、転送効率が重要になると思う。

選択肢がある、

  • SFの利用規約の範囲内で妥当な限り、SFからブロックチェーンを配布する。ファイルサイズの上限は見つからなかった。プロジェクトのウェブサイトサービスについても同様だ(ドキュメントをざっと見ただけだが)。
  • SFから「小さな」バイナリを配布し、BitTorrent経由でデータ付きの「大きな」リリースを配布する
  • 「小さな」リリースを配布し、ブロックチェーン用の.torrentとフェッチャースクリプトを含める。このスクリプトは、対象プラットフォーム向けに人気のコマンドラインBitTorrentクライアント3つのいずれかを探し、それを使ってチェーンを取得する。見つからなければ文句を言う。

http://sourceforge.net/apps/trac/sourceforge/wiki/Developer%20webにはこう書かれている。

うーん、各バイナリアーキテクチャごとにブロックチェーンを同梱するのは馬鹿げている。

では、誰がデータのトラッカーとシードを提供するのか? インセンティブやコミュニティ精神のある人? まあ、このフォーラム+Wikiはhttp://www.slicehost.com/ で動いているようだ => 最低月$20。おそらくWebサイトに影響を与えずに共有でき、シードを厳しくスロットルして他のBTシードにより多くの負担をかけることもできる。

サトシ・ナカモトの投稿(2010年11月25日17:51 UTC)

時間がかかるのはダウンロードではなく、検証とインデックス作成だ。

これは多くの初心者ユーザーには当てはまらない。「まあ、90000ブロックすべてを取得するのに数時間かかりましたが、最終的に到着しました」と言うような人たちだ(今日IRCで新規ユーザーからの引用)。

同意する。アーカイブに圧縮すると、blk0001.datは約36MBになる。

検証ではなくダウンロードが最も高い体験のばらつきを持っている。初回ユーザーはソフトウェアが実際に使えるようになるまで30分から数時間の遅延を経験する。一部のP2Pノードは非常に遅い場合がある(古いブロックや512バイトを超えるブロックでは、レイテンシとスループットに大きなばらつきが見られる)。エンドユーザーの帯域幅は低く、不安定で、高価かもしれない。ファイアウォールはしばしば問題になる。

上記の苦情はおそらくMicrosoftのファイアウォールが原因だったと思うが、それでも論点は変わらない。ネットワーク構成と性能の大きなばらつきを踏まえると、P2Pダウンロードの影響は、90,000ブロックのディスク上の検証の影響よりもはるかに大きい可能性がある。

誰が「信頼できない」と言った? 提案しているのは、bitcoin.orgの公式クライアントのダウンロードにblk0001.dat(それだけ)を同梱するということだ。もちろんクライアントは、初回使用時にblk0001.datの検証にいくらか時間を費やすことになる。これは避けられないことであり、検証を変更または排除することを提案した者は誰もいない。

公式Bitcoinにblk0001.datを同梱するだけで、新しいBitcoinユーザーが経験し続ける複数の問題を解消できる。

サトシ・ナカモトの投稿(2010年11月25日17:51 UTC)

Berkeley DBの設定を調整して、キャッシュメモリーを有効化または増加できるかもしれない。

ダウンロード中にACIDのどのプロパティが必要だろうか?

BDBレコードの追加はチェックポイントを発行するまで単にログファイルへの追記だ。チェックポイントがメインデータベースファイルを更新する。

通常のBDBトランザクションでは、トランザクションのコミットが成功する前に各ログレコードがディスクに同期されることが保証される。これは非常に厳密だが、完全なACIDには必要だ。DB_TXN_NOSYNCを有効にしても多くが得られる。

 「データベースの整合性は維持されるが、アプリケーションやシステムに障害が発生した場合、
  最近コミットされた一部のトランザクションがリカバリー時に取り消される可能性がある」

Bitcoinは最近の取引が取り消されても明らかにリカバリできるので、初期ブロックダウンロードの100%でこのフラグを設定するのが有用だと思われる。

チェックポイントについては、チェックポイント時に実行される作業量——ログからデータベースファイルにコピーする必要があるレコード数——と実時間のバランスだ。いくつかの値を試して何が「適切」に感じるか見るしかない——おそらく10,000ブロックごとにチェックポイント?

7年前の遅いドライブでテストしたが、帯域幅とCPUは明らかにボトルネックではなかった。初回ダウンロードは1時間20分かかった。

それよりはるかに長く、特に24時間もかかるなら、非常に遅いノードからダウンロードしているか、接続速度が約15KB/秒(120kbps)よりかなり遅いか、何か他に問題があるはずだ。そのような場合にボトルネックが何であるように見えるかわかると良いのだが。

最新のブロックが送信される10分程度ごとに、より速いノードに切り替える機会があるはずだ。最新のブロックがブロードキャストされると、他のノードに次の500ブロックを要求し、最も速く送信するノードからダウンロードを継続する。少なくとも、そのように動作するはずだ。

ジェフ・ガージックの投稿(2010年11月26日02:07 UTC)
サトシ・ナカモトの投稿(2010年11月25日17:51 UTC)

Berkeley DBの設定を調整して、キャッシュメモリーを有効化または増加できるかもしれない。

ダウンロード中に、ACID (http://en.wikipedia.org/wiki/ACID)プロパティのどれが必要だろうか?

より多くの読み取りキャッシュが役立つかもしれない。インデックスを作成するためにblk0001.datとblkindex.dat全体をランダムに読み取る必要がある。ファイルがメモリーより小さいと仮定することはできないが、現在はまだそうだ。ほとんどの依存関係が最近のものなので、キャッシュは効果的だろう。

誰かが異なるBerkeley DB設定で実験して、ダウンロードを大幅に速くするものがないか確認すべきだ。大幅な改善が見つかれば、詳細を詰めることができる。

ジェフ・ガージック

BDBレコードの追加はチェックポイントを発行するまで単にログファイルへの追記だ。チェックポイントがメインデータベースファイルを更新する。

500ブロックごとにチェックポイントしている。

RHorning 2010年11月26日17:42 UTC
ジェフ・ガージックの投稿(2010年11月26日01:47 UTC)

誰が「信頼できない」と言った? 提案しているのは、bitcoin.orgの公式クライアントのダウンロードにblk0001.dat(それだけ)を同梱するということだ。もちろんクライアントは、初回使用時にblk0001.datの検証にいくらか時間を費やすことになる。これは避けられないことであり、検証を変更または排除することを提案した者は誰もいない。

公式Bitcoinにblk0001.datを同梱するだけで、新しいBitcoinユーザーが経験し続ける複数の問題を解消できる。

個人的な提案としては、ブロックデータを別ダウンロードにするが、強く推奨する形がよいと思う。Windowsユーザーやこの種のブロックファイルを適切なディレクトリに配置できないコンピューターに詳しくないユーザーのインストールを簡素化したいなら、正式なインストールファイルを作成して適切な場所に配置するのがより「ユーザーフレンドリー」だろうが、実際に必要なのはブロックデータだけだ。

この目的は主に、新バージョンにアップデートする際に同じブロックデータを繰り返しダウンロードしなくて済むようにすることだ。ブロックデータは定義上、時間とともに増大していく。

zipslack 2010年11月26日18:08 UTC
RHorningの投稿(2010年11月26日17:42 UTC)

この目的は主に、新バージョンにアップデートする際に同じブロックデータを繰り返しダウンロードしなくて済むようにすることだ。ブロックデータは定義上、時間とともに増大していく。

あなたの環境がどうかは分からないが、自分の場合、Bitcoinをアップグレードする際にブロックを再ダウンロードする必要はない。アップグレード前の状態からそのまま続行される。

RHorning 2010年11月26日19:17 UTC
zipslackの投稿(2010年11月26日18:08 UTC)
RHorningの投稿(2010年11月26日17:42 UTC)

この目的は主に、新バージョンにアップデートする際に同じブロックデータを繰り返しダウンロードしなくて済むようにすることだ。ブロックデータは定義上、時間とともに増大していく。

あなたの環境がどうかは分からないが、自分の場合、Bitcoinをアップグレードする際にブロックを再ダウンロードする必要はない。アップグレード前の状態からそのまま続行される。

それがまさにポイントだ。ブロックがアップデートに含まれていれば、すでにネットワーク経由で取得済みのブロックも含まれることになる。だからこそ、通常のディストリビューションに含めるのではなく、新規ユーザー向けの別のダウンロード(ただし強く推奨)にすべきだと提案しているのだ。

IRCの別の新規ユーザー(今回はLinux)が、1ブロックあたり4秒の速度でダウンロードしていた——推定合計ダウンロード時間は約4日間。

このスレッドの他のコメント者は、アップグレードするユーザーにはブロックデータベースは不要だと正しく指摘している……しかし、新規ユーザーの初期ブロックダウンロード体験を改善するために何かをする必要がある。データベースをいくら改善しても……さまざまな理由でブロックをゆっくり提供するピアは残る。

Genesisブロックからブロック74000までのハッシュがBitcoinにハードコード(コンパイル)されているので、ブロックデータベースの圧縮zipファイルをどこからでも自動的にダウンロードし、展開し、検証し、実行を開始できない理由はない。

tyler 2010年11月28日07:23 UTC
ジェフ・ガージックの投稿(2010年11月28日02:33 UTC)

このスレッドの他のコメント者は、アップグレードするユーザーにはブロックデータベースは不要だと正しく指摘している……しかし、新規ユーザーの初期ブロックダウンロード体験を改善するために何かをする必要がある。データベースをいくら改善しても……さまざまな理由でブロックをゆっくり提供するピアは残る。

何かをする必要がある。ブロックチェーンは来年かそこらで巨大になるのだろう?

tylerの投稿(2010年11月28日07:23 UTC)
ジェフ・ガージックの投稿(2010年11月28日02:33 UTC)

このスレッドの他のコメント者は、アップグレードするユーザーにはブロックデータベースは不要だと正しく指摘している……しかし、新規ユーザーの初期ブロックダウンロード体験を改善するために何かをする必要がある。データベースをいくら改善しても……さまざまな理由でブロックをゆっくり提供するピアは残る。

何かをする必要がある。ブロックチェーンは来年かそこらで巨大になるのだろう?

はい、その通り。

おそらくある時点でブロックヘッダーのみをダウンロードする軽量クライアントが登場するだろうが、それでも数十万のヘッダーがある……

zipslack 2010年11月28日08:53 UTC
RHorningの投稿(2010年11月26日19:17 UTC)

新規ユーザー向けの通常のディストロに組み込むのではなく、別途だが強く推奨されるダウンロードとすべきだと提案しているのはそのためだ。

すまない、誤解していた。

ジェフ・ガージックの投稿(2010年11月28日02:33 UTC)

Genesisブロックからブロック74000までのハッシュがBitcoinにハードコード(コンパイル)されているので、ブロックデータベースの圧縮zipファイルをどこからでも自動的にダウンロードし、展開し、検証し、実行を開始できない理由はない。

チェックポイントのことだと思うが、もしそうなら、私の理解では、チェックポイントはダウンロードされたブロックの検証時にのみ適用される。blk0001.datとblkindex.datの内容がクライアントによってチェックされることは決してない。クライアントはそれらのファイルに書き込む前にデータをチェックするよう設計されているからだ。サトシがこのスレッドで示したように、

サトシ・ナカモトの投稿(2010年11月25日17:51 UTC)

自分で検証とインデックス作成を行うことが、インデックスデータの安全性を確保する唯一の方法だ。信頼できないソースからblk0001.datとblkindex.datをコピーした場合、その中身すべてを信頼できるかどうか知る方法はない。

zipslackの投稿(2010年11月28日08:53 UTC)

チェックポイントのことを言っているのだろうか? そうであれば、理解している限り、それらはダウンロードされたブロックの検証中にのみ適用される。blk0001.datとblkindex.datの内容はクライアントによってチェックされない。なぜならクライアントはデータがそれらのファイルに書き込まれる前にチェックするよう設計されているからだ。

それは正確ではない。-checkblocks(CheckBlock())はblk0001.dat / blkindex.datの内容に対してかなりの数のチェックを実行する。AcceptBlock()はコンテキストを追加してもう少し多くのことをするが、大した差ではない。ただ、今はこの点は置いておこう。

もっと重要な点として見落としているのは、検証の省略を提案している人は誰もいないということだ。Bitcoinのコードは信頼できないblk0001.datデータの検証とインデックス作成を十分にこなせる。blkindex.datが存在しない場合に合理的に動作するよう、いくつかの修正が必要なだけだ。

提案は単純に、バルクデータ転送用に設計されていないプロトコル(Bitcoin P2P)を使って大量の非圧縮データをダウンロードするのをやめよう、ということだ。

クライアントはblk0001.datの暗号学的な完全性を信頼できないソースから検証する能力を明らかに持っている。なぜなら、ネットワーク経由で受信したブロックに対してそれを行っており、blk0001.datには……信頼できないソースからネットワーク経由で受信したシリアライズされたブロックが含まれているからだ。

blk0001.datのファイル位置データをProcessBlock()に渡し、下流の呼び出し先AcceptBlock()でWriteToDisk()のストレージ呼び出しをスキップするだけで、それほど難しくなさそうだ。

RHorning 2010年11月28日16:09 UTC
ジェフ・ガージックの投稿(2010年11月28日10:02 UTC)

クライアントはblk0001.datの暗号学的な完全性を信頼できないソースから検証する能力を明らかに持っている。なぜなら、ネットワーク経由で受信したブロックに対してそれを行っており、blk0001.datには……信頼できないソースからネットワーク経由で受信したシリアライズされたブロックが含まれているからだ。

blk0001.datのファイル位置データをProcessBlock()に渡し、下流の呼び出し先AcceptBlock()でWriteToDisk()のストレージ呼び出しをスキップするだけで、それほど難しくないはずだ。

私が間違いでなければ、これは現時点で「公式」クライアントがblk0001.datに検証済みデータが含まれていると推定していることを意味し、別のソースからダウンロードしたデータが侵害されていた場合、現在この情報を検証する方法がない。これはソフトウェアがこの問題に対処しようとしている間の一時的な危険だ。

一方で、UIやbitcoindのコマンドラインスイッチとして、ブロックデータの何らかの「再検証」をローカルで実行するオプションを追加することもできるだろう。コンピューターのウイルスがブロックチェーンのデータを操作する場合の予防策など、他の用途もあると思う。しかし、それはソフトウェアに追加されるべきオプションであるように思う。検証コードはすでにソフトウェアにあるので、アルゴリズムとトリガーメカニズムを設定するだけだ。検証プロセス中に懸念のある特定のブロックがあれば、ピアノードへのブロック要求に基づいてチェーンを「修復」する努力や、チェーン全体を破棄することもできるだろう。

そのような機能がいつか追加されることを望む。

MoonShadow 2010年11月28日16:25 UTC

自分の理解では、インデックスが見つからない場合、クライアントはすでに起動時にブロックチェーンの再チェックを行っているはずだった。最初に始めた時にこれをやったが、確かにチェーンを辿っているように見えた。どのみちインデックスがないと機能しないのではないか? 起動時にブロックチェーンが有効だと仮定する理由は? 誰でも編集できるのだから。Genesisブロックはクライアントにエンコードされているよね? それとブロックチェーンのチェックポイントだけが正しいと仮定される部分で、間違っているだろうか? 他の方法によるブロックチェーンのダウンロードを防ぐ正当な理由はない。Bitcoinネットワークが容量に近い状態で動いている将来では、P2Pネットワーク経由でブロックチェーン全体をダウンロードすることは有害になる。

マークルツリーが剪定されたチェーンでも最初から検証できるはずであり、そうでなければマークルツリーを使う意味は何だろうか?

他の議論にもかかわらず、現在の次のステップは:

サトシ・ナカモトの投稿(2010年11月26日17:32 UTC)

誰かが異なるBerkeley DB設定で実験して、ダウンロードを大幅に速くするものがないか確認すべきだ。大幅な改善が見つかれば、詳細を詰めることができる。

特に、読み取りキャッシュを増やすと大いに役立つのではないかと思う。

ジェフ・ガージックの投稿(2010年11月28日02:33 UTC)

IRCの別の新規ユーザー(今回はLinux)が、1ブロックあたり4秒の速度でダウンロードしていた——推定合計ダウンロード時間は約4日間。

それなら何かもっと具体的な問題があった。通常の初回ダウンロード時間によるものではない。より詳細がなければ診断できない。遅いダウンロードが原因だったなら、次のブロックブロードキャストでより速いソースに切り替わるはずの10〜20分後に速くなった? debug.logに手がかりがあるかもしれない。インターネット接続はどのくらいの速さ? ずっと遅かった? それとも、ある時点で遅くなっただけ?

ジェフ・ガージックの投稿(2010年11月28日02:33 UTC)

Genesisブロックからブロック74000までのハッシュがBitcoinにハードコード(コンパイル)されているので、ブロックデータベースの圧縮zipファイルをどこからでも自動的にダウンロードし、展開し、検証し、実行を開始できない理由はない。

74000チェックポイントは保護に十分ではなく、ダウンロードがすでに74000を過ぎていれば何もしない。-checkblocksはより多くのことをするが、依然として簡単に突破される。zipファイルの提供者を信頼しなければならない。

「検証する」ステップがあれば、現在の通常の初回ダウンロードと同じくらい時間がかかるだろう。データダウンロードではなく、インデックス作成がボトルネックなのだ。

ジェフ・ガージックの投稿(2010年11月28日07:33 UTC)

おそらくある時点でブロックヘッダーのみをダウンロードする軽量クライアントが登場するだろうが、それでも数十万のヘッダーがある……

ヘッダーあたり80バイトでインデックス作成作業なし。1分程度で済むかもしれない。

ジェフ・ガージックの投稿(2010年11月28日07:33 UTC)

一括データ転送用に設計されていないプロトコル(bitcoin P2P)を使用した非圧縮データ。

データの大部分はハッシュ、鍵、署名で、圧縮不可能だ。

初回ダウンロードの速度は、プロトコルの一括データ転送レートを反映していない。制限要因はダウンロード中のインデックス作成だ。

サトシ・ナカモトの投稿(2010年11月28日17:13 UTC)

「検証する」ステップがあれば、現在の初回ダウンロードと同じくらい時間がかかるだろう。データダウンロードではなく、インデックス作成がボトルネックなのだ。 […] 初回ダウンロードの速度は、プロトコルの一括データ転送レートを反映していない。制限要因はダウンロード中のインデックス作成だ。

申し訳ないが、これらのユーザーのディスクとCPUは100%ではなかった。多くのユーザーにとってボトルネックがデータベースやインデックスではないことは明らかだ。

bzip2は33%の圧縮率を提供し、ダウンロードから数メガバイトを節約する:

[jgarzik@bd data]$ tar cvf /tmp/1.tar blk0001.dat 
blk0001.dat

[jgarzik@bd data]$ tar cvf /tmp/2.tar blk*.dat blk0001.dat blkindex.dat

[jgarzik@bd data]$ bzip2 -9v /tmp/[12].tar /tmp/1.tar: 1.523:1, 5.253 bits/byte, 34.34% saved, 55439360 in, 36402074 out. /tmp/2.tar: 1.512:1, 5.291 bits/byte, 33.86% saved, 103690240 in, 68577642 out.

33%を「圧縮不能」とは呼ばない

ジェフ・ガージックの投稿(2010年11月28日18:59 UTC)

すみません、これらのユーザーのディスクとCPUは100%ではなかった。多くのユーザーにとって、ボトルネックはデータベースやインデックス作成ではないことは明らかだ。

自分にはディスクの問題か彼のほうのネットワーク状態の問題のように見えた。IRCログからの一部の引用:

<manveru> それと、起きたらdebug.logに何千件も、こんな記録が出ていた: trying connection lastseen=-135.6hrs lasttry=-358582.4hrs
<theymos> 接続はいくつある?
<manveru> 今は2
<theymos> ブロックはいくつ持ってる?
<manveru> blockcountもblocknumberも29124
<theymos> どれくらいの速さで増えてる?
<manveru> だいたい4秒に1つ
<jgarzik> manveru: 32ビットと64ビット、どっちのLinux?
<manveru> 64
<manveru> 今度は ‘blkindex.dat flush’ に数分かかる :|
<manveru> まだflushで止まってる
<theymos> manveru: 何かのネットワークファイルシステムを使ってる?
<manveru> いや、普通のハードディスク
<manveru> 5200rpmだけどね

また、ブロックの置き換えによって取引に気づかなくなった可能性もある。

<manveru> jgarzik: ブロックを送ってもらったけど、残高は変わらなかった
<mt`away> manveru: getinfoだとブロック94236にいるのか?
<manveru> うん

サトシ・ナカモトの投稿(2010年11月25日17:51 UTC)

blkindex.datのビルドこそがすべてのディスクアクティビティを引き起こしている。 […] Berkeley DBの設定を調整して、キャッシュメモリーを有効化または増加できるかもしれない。

AddToBlockIndex(main.cpp) の以下のコードは恐ろしく非効率で、初回ブロックダウンロードを劇的に遅くしている:

    CTxDB txdb;
    txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));

    // New best
    if (pindexNew->bnChainWork > bnBestChainWork)
        if (!SetBestChain(txdb, pindexNew))
            return false;

    txdb.Close();

これでは、データベース(db4でもSQLでも何でも)に大量のレコードをロードする標準的な手法 ―― 複数のレコード挿入を1つのデータベーストランザクションでラップする ―― が使えない。理想的には、Bitcoinは初回ブロックダウンロード中、1000ブロックほどごとに1回だけTxnCommit() を発行すればよい。クラッシュが起きてもデータベースは一貫した状態を保つ。

さらに、新しいブロックごとにデータベースのopen + closeを行うのはとてつもなく高コストだ。データベースのopenとclose操作のたびに、db4は

  • データベースの健全性を診断して、リカバリが必要か判定する。このテストにはデータコピーが必要になる場合がある。
  • メモリープールを再初期化する
  • データベースファイルのメタデータを読む
  • ファイルロックを取得する
  • b-treeまたはハッシュ固有のメタデータを読み込んで初期化する。ハッシュテーブル/b-treeのルートを構築する。
  • たとえトランザクションがDB_TXN_NOSYNCで呼ばれていてもsyncを強制する
  • メモリープールをfsyncする

加えて、Bitcoinはデータベースチェックポイントを強制し、ログから全トランザクションをメインデータベースに押し込む。

そう、その長い一連の操作は、データベースのclose+openサイクルにおいて、environment単位(DB_ENV)ではなくデータベース単位(DB)で実行される。Bitcoinにとっては、つまり新しいブロックごとにこれをやっているということだ。途方もなく非効率で、db4が想定する使い方ではない。

推奨:

  1. Bitcoinはプログラム起動時にenvironmentだけでなくデータベースも開き、プログラム終了時にデータベースを閉じるべきだ。db4は適切なトランザクション利用が維持されていればクラッシュを扱えるように設計されている ―― そしてBitcoinはすでにdb4トランザクションを適切に使っている。

  2. 初回ブロックダウンロードでは、txnコミットはレコードごとではなくNレコードごとに1回行うべきだ。N=1000を提案する。

EDIT: いくつかの細部を更新し、いくつかのタイプミスを修正した。

実際よりも、すべてが間違っていると想定する傾向があるようだね。

ブロックインデックスの書き込みは軽い作業だ。txインデックスの構築は1ブロックあたりのランダムアクセスがはるかに多い。すべてのprev txinの読み取りが遅い原因ではないかと疑っている。読み取りキャッシュが役立つだろう。DBがそれを行うのが最善だ。キャッシュメモリーの使用量の設定があるかもしれない。

ジェフ・ガージックの投稿(2010年11月29日19:01 UTC)
  1. Bitcoinはプログラム起動時にenvironmentだけでなくデータベースも開き、プログラム終了時にデータベースを閉じるべきだ。

すでにそうしている。CDBを参照してくれ。(例えば)CTxDBオブジェクトの寿命は、データベーストランザクションのサポートと、シャットダウン時にまだデータベースを使用しているものがあるかどうかを知るためだけだ。

ジェフ・ガージック

加えて、Bitcoinはデータベースチェックポイントを強制し、ログから全トランザクションをメインデータベースに押し込む。

そうしていたらはるかに遅くなるだろう。1分または500ブロックに1回のみのはずだ:

    if (strFile == "blkindex.dat" && IsInitialBlockDownload() && nBestHeight % 500 != 0)
        nMinutes = 1;
    dbenv.txn_checkpoint(0, nMinutes, 0);

おそらくこれを追加すべきだ:

    if (!fReadOnly)
        dbenv.txn_checkpoint(0, nMinutes, 0);
ジェフ・ガージック
  1. 初回ブロックダウンロードでは、txnコミットはレコードごとではなくNレコードごとに1回行うべきだ。N=1000を提案する。

トランザクションコミットはフラッシュを意味するのかな? 私には意外に思える。トランザクションでラップされたデータベース操作は、他のデータベース操作と同様にログに記録されると思う。多くのデータベースアプリケーションでは、ほぼすべての操作のペアをトランザクションでラップする必要がある。例えば、あるアカウントから別のアカウントへの送金(aを借方、bを貸方)などだ。すべてを自分でバッチ処理することが求められるとは想像できない。

以下のケースで、ケース1は1回フラッシュし、ケース2は2回フラッシュするのだろうか?

ケース1:write write write write checkpoint

ケース2:begin transaction write write commit transaction begin transaction write write commit transaction checkpoint

データベースの使用方法を歪めるのは正しいアプローチではないだろう。BDBの設定とキャッシュで対応すべきだ。

ああ、すべてのC++コンストラクタに埋もれたデータベースオープンキャッシングを見落としていた。とんだ見当違いの手がかりで、申し訳ない。

db4キャッシュ制御はhttp://download.oracle.com/docs/cd/E17076_01/html/api_reference/CXX/dbset_cachesize.html

昨夜投稿された -initblock=FILEパッチを使ってインポートを計装し、TxnBegin、TxnCommit、TxnAbort、Read、Writeにprintfのトレースポイントを入れた:

ProcessBlock: ACCEPTED
CDB::Write()
DB4: txn_begin
CDB::Write()
CDB::Write()
CDB::Write()
DB4: txn_commit
SetBestChain: new best=000000005b5c1859db19  height=1751  work=7524897523416
ProcessBlock: ACCEPTED
CDB::Write()
DB4: txn_begin
CDB::Write()
CDB::Write()
CDB::Write()
DB4: txn_commit
SetBestChain: new best=00000000f396ab6b62ba  height=1752  work=7529192556249
ProcessBlock: ACCEPTED
CDB::Write()
DB4: txn_begin
CDB::Write()
CDB::Write()
CDB::Write()
DB4: txn_commit
SetBestChain: new best=000000000c6bcf972117  height=1753  work=7533487589082

つまり、トランザクションの外で発生するCDB::Write() があるようだ(vTxnが空??)。

txnid==NULLはdb4にとっては完全に合法だが、コールパスが ::TxnBegin() で設定されたDB_TXN_NOSYNCフラグの外で動作している可能性があるということでもある。したがって、トランザクション外のCDB::Write() はDB_AUTO_COMMITデータベースフラグの支配下で同期動作(DB_TXN_SYNC)になる可能性がある。

EDIT: WriteBlockIndex() をトランザクションでラップすると、ローカルディスクのインポート(-initblocks)が高速化されるようだ。

--- a/main.cpp
+++ b/main.cpp
@@ -1427,7 +1427,10 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned
     pindexNew->bnChainWork = (pindexNew->pprev ? pindexNew->pprev->bnChainWork 
 
     CTxDB txdb;
+    txdb.TxnBegin();
     txdb.WriteBlockIndex(CDiskBlockIndex(pindexNew));
+    if (!txdb.TxnCommit())
+       return false;
 

もちろんこれはbegin+commit+begin+commitが立て続けに起きる(SetBestChain)ことを意味するので、もう少し素朴でないアプローチ(ネストしたトランザクションか、両方のdb4書き込みを同じトランザクションでラップする)が望ましいかもしれない。

クリーンなデータディレクトリ(中身なし)、-noirc、-addnode=10.10.10.1、Linux 64ビットで2回のタイミングを測定した。ハードウェア:SATA SSD

メインライン、パッチなし:94660ブロックのダウンロードに32分。

メインライン + AddToBlockIndex()にTxnBegin/TxnCommit:94660ブロックのダウンロードに25分。

良い最適化だ。次にSVNを更新する際に追加する。

より一般的には、これも検討できるだろう:

        dbenv.set_lk_max_objects(10000);
        dbenv.set_errfile(fopen(strErrorFile.c_str(), "a")); /// debug
        dbenv.set_flags(DB_AUTO_COMMIT, 1);
+       dbenv.set_flags(DB_TXN_NOSYNC, 1);
        ret = dbenv.open(strDataDir.c_str(),
                         DB_CREATE     |
                         DB_INIT_LOCK  |
                         DB_INIT_LOG   |

そうすれば、ウォレット書き込み後のフラッシュにはCDB::Close()のdbenv.txn_checkpoint(0, 0, 0)に依存することになる。

サトシ・ナカモトの投稿(2010年12月1日21:25 UTC)

より一般的には、これも検討できるだろう:

    dbenv.set_lk_max_objects(10000);
    dbenv.set_errfile(fopen(strErrorFile.c_str(), "a")); /// debug
    dbenv.set_flags(DB_AUTO_COMMIT, 1);
  •   dbenv.set_flags(DB_TXN_NOSYNC, 1);
      ret = dbenv.open(strDataDir.c_str(),
                       DB_CREATE     |

あるいはDB_TXN_WRITE_NOSYNCだ。書き込むがsyncはしない。ほぼすべてのOS(そしてハードドライブ!)にライトバックキャッシュがあるので速いはずだ。

MrFlibble 2010年12月3日02:09 UTC
サトシ・ナカモトの投稿(2010年11月26日17:32 UTC)

7年前の遅いドライブでテストしたが、帯域幅とCPUは明らかにボトルネックではなかった。初回ダウンロードは1時間20分かかった。

それよりはるかに長く、特に24時間もかかるなら、非常に遅いノードからダウンロードしているか、接続速度が約15KB/秒(120kbps)よりかなり遅いか、何か他に問題があるはずだ。そのような場合にボトルネックが何であるように見えるかわかると良いのだが。

こちらの約24時間のダウンロードは、PATAアダプター経由の(安物の)Compact Flashへ、約4 Mbit/sのADSL回線、ルーターからのport 8333フォワーディング、Debian Lenny上の700MHz Pentium3と256MiB RAMという環境だった。ダウンロード速度はかなり激しく上下した。

このボロマシンの性能を把握するためにbonnie++を走らせた。

$ /usr/sbin/bonnie -d ~/bon/ -f -s 1500MiB -m clunker
[...noise...]
Version 1.03d       ------Sequential Output------ --Sequential Input- --Random-
                    -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
clunker       1500M            5396   6  5034   5           17658  11 601.4   5
                    ------Sequential Create------ --------Random Create--------
                    -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16 10494  60 +++++ +++   980   3  7919  45 +++++ +++  1106   4
clunker,1500M,,,5396,6,5034,5,,,17658,11,601.4,5,16,10494,60,+++++,+++,980,3,7919,45,+++++,+++,1106,4

$ /usr/sbin/bonnie -d ~/bon/ -f -s 1500MiB -m clunker -b
[...noise...]
Version 1.03d       ------Sequential Output------ --Sequential Input- --Random-
                    -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
clunker       1500M            4921   6  5382   6           19630  12  33.4   0
                    ------Sequential Create------ --------Random Create--------
                    -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16    68   0 +++++ +++    16   0    68   0 +++++ +++    17   0
clunker,1500M,,,4921,6,5382,6,,,19630,12,33.4,0,16,68,0,+++++,+++,16,0,68,0,+++++,+++,17,0

2回目は -b「書き込みバッファリングなし。各書き込み後にfsync()」付きで実行した。ブロック書き込みはあまり変わらない(ランダムシークは18倍遅くなった?)が、ファイルの作成と削除は60〜150倍遅くなる。%CPはCPU使用率、+++は「意味を持たないほど速い」を表す。

新しいバージョンのコンパイルとテストはできるが、現状リアルタイムのラグがかなり大きい。debug.logの中に伏せたり匿名化したい情報が含まれることはあるだろうか?