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 を稼げるようにしてもよい。別のソースから無料でブロックを取得できるようになれば、例えば本体配布物と一緒に無料ファイルホスティング上の二つ目のパッケージを置いたり、新規クライアントの初期化方法をもっとネットワークに優しい形に工夫したりと、もう少し創意のある方法が出てくるかもしれない。

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

witchspaceの投稿(2010年11月25日 04: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(?)を無効にすることが最も効果的だろう。

witchspaceの投稿(2010年11月25日 04:37 UTC)

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

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

選択肢がある、

  • SF の利用規約の範囲内で妥当な限り、SF からブロックチェーンを配布する。
  • SF から「小さな」バイナリを配布し、BitTorrent 経由でデータ付きの「大きな」リリースを配布する
  • 「小さな」リリースを配布し、ブロックチェーン用の.torrent とフェッチャースクリプトを含める。

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

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

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

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

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

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

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

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

サトシ・ナカモトの投稿(2010年11月25日 08: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月25日 17:07 UTC)
サトシ・ナカモトの投稿(2010年11月25日 08: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月25日 16:47 UTC)

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

公式Bitcoinにblk0001.datを同梱するだけで、新規ユーザーが引き続き経験している多くの問題を解消できるだろう。

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

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

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

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

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

RHorning 2010年11月26日 19:17 UTC 原文 ·
zipslackの投稿(2010年11月26日 09:08 UTC)
RHorningの投稿(2010年11月26日 08:42 UTC)

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

どうか分からないが、自分がBitcoinをアップグレードする時、ブロックを再ダウンロードする必要はない。アップグレード前に中断したところからそのまま再開される。

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

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

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

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

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

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

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

tylerの投稿(2010年11月27日 22:23 UTC)
ジェフ・ガージックの投稿(2010年11月27日 17:33 UTC)

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

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

はい、その通り。

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

zipslack 2010年11月28日 08:53 UTC 原文 ·
RHorningの投稿(2010年11月26日 10:17 UTC)

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

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

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

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

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

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

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

zipslackの投稿(2010年11月27日 23: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日 01: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月27日 17:33 UTC)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

他の議論にもかかわらず、現在の次のステップは: 引用:誰かが異なるBerkeley DB設定で実験して、ダウンロードを大幅に速くするものがないか確認すべきです。大幅な改善が見つかれば、詳細を詰めることができます。 特に、読み取りキャッシュを増やすと大いに役立つのではないかと思う。

申し訳ないが、これらのユーザーのディスクと 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%を「圧縮不能」とは呼ばない

theymos 2010年11月28日 22:34 UTC 原文 ·
ジェフ・ガージックの投稿(2010年11月28日 09:59 UTC)

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

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

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

サトシ・ナカモトの投稿(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はプログラムの起動時に環境だけでなくデータベースを開き、プログラムのシャットダウン時にデータベースを閉じるべきです。

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

ジェフ・ガージックの投稿(2010年11月29日 19:01 UTC)

加えて、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);
ジェフ・ガージックの投稿(2010年11月29日 19:01 UTC)
  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 の中に伏せたり匿名化したい情報が含まれることはあるだろうか?