コンテンツにスキップ

デスクトップアプリ P2P 配布加速の実践:コンシューマー側からパブリッシャー側までの全パス接続

ページを編集
HagiCode for Windows Microsoft Store artwork
HagiCode for Windows is now on Microsoft Store
HagiCode for Windows is officially live on Microsoft Store. Windows users can install it directly from the storefront and stay on the store-managed update path. Open the listing and take a look.
Open Microsoft Store

デスクトップアプリ P2P 配布加速の実践:コンシューマー側からパブリッシャー側までの全パス接続

デスクトップアプリの大容量ファイル配布は常に頭の痛い問題です—帯域コストが高く、ダウンロード速度が遅く、ユーザー体験が悪い。本文では、HagiCode Desktop で実装したハイブリッド配布ソリューションを共有します。P2P 技術によりダウンロードを加速し、HTTP フォールバック機能を維持することで、最終的にパブリッシャー側とコンシューマー側の完全なクローズドループを実現しました。

背景

デスクトップアプリの配布パッケージは通常、それなりに大きく、数百 MB に達することもあります。これは実際には正常なことです。現在のアプリケーションは機能が増え続けているため、サイズも自然と増えています。HagiCode Desktop のようなアプリケーションの場合、各バージョンの更新は大量のユーザーに大容量ファイルを配布することを意味し、サーバーの帯域幅にとって小さな試練ではありません。

従来の方法は HTTP ダウンロードを直接使用することで、シンプルで直接的ですが問題も明らかです:ピーク時にサーバー負荷が高く、ユーザーのダウンロード速度が遅い、特に海外ユーザーにとって。これも仕方がありません。物理的な距離があるからです。P2P 技術はこの問題をうまく解決できます—ユーザー同士がファイルの断片を共有し合うことで、サーバー負荷を軽減し、ダウンロード速度を向上できます。

ただ、そう簡単ではありません。HagiCode Desktop の開発中に私たちが発見した興味深い現象があります:コンシューマー側(デスクトップアプリ)はすでにハイブリッドダウンロードの能力を持ち、torrentUrl、infoHash、webSeeds、sha256 などのフィールドを解析し、ハイブリッドダウンロードコーディネーターを通じて P2P 加速ダウンロードを優先的に使用できます。しかし、パブリッシャー側(ビルドツールチェーン)はこれらのフィールドを Azure Blob の index.json に安定して出力していませんでした。

これは実際には断層を形成していました:クライアントはより効率的な配布方式を期待していましたが、パブリッシャー側はまだ従来のフラットファイルリストでインデックスを構築していました。P2P 加速のポテンシャルがこのように無駄になっていました。

このクローズドループを接続するため、私たちは完全な改造案を行いました—パブリッシャー側のメタデータ生成から、コンシューマー側のハイブリッドダウンロード調整まで、配布パス全体を真に動作させました。次に、このソリューションの設計思想と実装の詳細を詳しく共有し、同様の問題に直面している友人への参考になればと思います。

HagiCode について

本文で共有するハイブリッド配布ソリューションは、HagiCode プロジェクトでの実践経験から来ています。HagiCode Desktop は私たちのデスクトップアプリで、Windows、macOS、Linux のマルチプラットフォームをサポートしています。AI コードアシスタントプロジェクトとして、デスクトップ側は頻繁に配布パッケージを更新する必要があり、これは私たちにより効率的な配布方式を探求させました。結局のところ、誰も各更新のたびに長時間待ちたくはないでしょう?

分析

問題の本質

表面的には、これは「torrent ファイル生成を追加する」機能要件のように見えます。しかし、深く分析した後、私たちはこれが実際には producer-consumer 契約の不一致 問題であることを発見しました。この状況も比較的よくあることで、開発と運用の理解が同じチャンネルにないことがあります。

コンシューマー側が期待するのはアセットレベルのハイブリッド配布フィールドです:

{
"torrentUrl": "https://...",
"infoHash": "<sha1 infohash>",
"webSeeds": ["https://..."],
"sha256": "<package digest>"
}

一方、パブリッシャー側が提供するのはファイルレベルのフラットリストです:

{
"files": [
{"name": "hagicode-1.2.3-win-x64.zip", "url": "https://..."},
{"name": "hagicode-1.2.3-win-x64.zip.torrent", "url": "https://..."}
]
}

この両者は意味的に完全に一致しません。コンシューマー側はフラットリストからどのファイルがメインファイルで、どれが sidecar かを判断できず、それらの間の関連関係も確立できません。これはある人が探したいのに、電話帳だけ渡されて自分で探させるようなもので、かなり面倒です。

重要な制約

ソリューションを設計する際、私たちは満たす必要のあるいくつかの制約を明確にしました:

閾値の一貫性:パブリッシャー側とコンシューマー側は同じファイルサイズ閾値を使用する必要があります。私たちは 100 MB に設定しました—このサイズに達したファイルのみが P2P メタデータを生成します。これにより、「パブリッシャー側は加速可能と判定、コンシューマー側は加速しないと判定」という戦略のずれを回避できます。これは実際にはかなり重要です。両側が一致しない場合、様々な奇妙なバグが発生するからです。

フォールバック保証:webSeeds には directUrl を含める必要があります。これは P2P 接続がない場合(例えば最初のダウンローダーとして)、ユーザーが HTTP を通じて完全にファイルをダウンロードできることを保証するためです。P2P は加速手段であり、代替案ではありません。これは車の運転のようなもので、P2P は高速道路ですが、高速道路が渋滞する場合に備えて一般道路も残す必要があります。

互換性ウィンドウ:index.json は assets と files の両方の投影を同時に出力する必要があります。古いクライアントは assets フィールドを認識しない可能性があるため、files を互換性投影として残し、サーバー側のアップグレードによるクライアントの中断を回避する必要があります。これは実際には比較的よくあることで、すべてのユーザーがタイムリーにクライアントを更新するわけではないからです。

技術的決定

具体的な実装では、AzureBlobAdapter で torrent 生成を直接実装するのではなく、「独立メタデータビルダー + オプション Node ブリッジスクリプト」のアーキテクチャを採用しました。

这样做有几个好处:

  1. 責任の明確化:メタデータ構築ロジックはストレージアダプターから独立しており、テストと保守が容易
  2. プラットフォームの分離:C# 環境から Node スクリプトを呼び出して torrent を生成し、既存の torrent ライブラリを活用
  3. 移行の親和性:将来的に他のストレージバックエンドに移行する必要がある場合、メタデータビルダーを再利用できる

これは実際には良い選択です。責任が明確であれば、後の保守も楽になります。

解決策

1. メタデータ構築フロー

完全なメタデータ構築フローは次のようになります:

パッケージ完了 → 大容量ファイルの識別(≥100MB) → sha256 の計算 → .torrent sidecar の生成
→ infoHash の抽出 → メタデータの組み立て → ZIP + .torrent のアップロード → index.json への書き込み

各ステップには明確な責任があります:

ファイル識別:ビルド成果物を走査し、サイズ ≥ 100 MB のファイルをふるい分けます。この閾値はコンシューマー側の HYBRID_THRESHOLD_BYTES と一致しています。これは実際にはかなり重要です。閾値が一致しない場合、様々な奇妙な問題が発生するからです。

SHA256 計算:メインファイルの SHA256 ダイジェストを計算し、ダウンロード後の完全性検証に使用します。これはセキュリティ防御ラインであり、ユーザーがダウンロードしたファイルが改ざんされていないことを保証します。これはファイルに指紋を付けるようなもので、万一改ざんされても、すぐに発見できます。

Torrent 生成:Node スクリプトを使用して torrent ライブラリを呼び出し、.torrent sidecar ファイルを生成します。命名は {artifact}.zip.torrent 形式を採用し、ZIP ファイル名から sidecar を逆引きできるようにします。これは実際には小さなコツで、命名を規範的にすることで、後の処理も便利になります。

InfoHash 抽出:torrent ファイルから infoHash(SHA1 形式)を抽出します。これは P2P ネットワークでリソースを識別する一意の識別子です。これは各人の身分証番号のようなもので、これがあれば、P2P ネットワークは対応するリソースを見つけられます。

メタデータ組み立て:directUrl、torrentUrl、infoHash、webSeeds、sha256 を完全なアセットメタデータオブジェクトに組み立てます。

2. インデックス構造のアップグレード

フラットな files 投影からアセットレベルの assets オブジェクトにアップグレードします:

{
"versions": [{
"version": "1.2.3",
"assets": [{
"name": "hagicode-1.2.3-win-x64.zip",
"directUrl": "https://hagicode.blob.core.windows.net/releases/v1.2.3/hagicode-1.2.3-win-x64.zip",
"torrentUrl": "https://hagicode.blob.core.windows.net/releases/v1.2.3/hagicode-1.2.3-win-x64.zip.torrent",
"infoHash": "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0",
"sha256": "1a2b3c4d5e6f7g8h9i0j1k2l3m4n5o6p7q8r9s0t1u2v3w4x5y6z7a8b9c0d1e2f",
"webSeeds": [
"https://hagicode.blob.core.windows.net/releases/v1.2.3/hagicode-1.2.3-win-x64.zip"
]
}],
"files": [ // 互換性投影
{"name": "hagicode-1.2.3-win-x64.zip", "url": "https://..."}
]
}]
}

この構造にはいくつかの設計上の考慮があります:

二重投影の共存:assets は完全なハイブリッド配布メタデータを提供し、files は簡素化された互換性ビューを提供します。新しいクライアントは assets を優先的に使用し、古いクライアントは files にフォールバックします。これは実際には妥協案で、古いユーザーを切り捨てられないためです。

WebSeeds はデフォルトで DirectUrl を含む:P2P 接続がない場合でも、ユーザーが HTTP を通じて完全にダウンロードできることを保証します。これはフォールバックソリューションであり、100% の可用性を保証します。これは車の運転のようなもので、P2P は高速道路ですが、高速道路が渋滞する場合に備えて一般道路も残す必要があります。

命名規約の明確化:{artifact}.zip.torrent の命名により、コンシューマー側は sidecar を自動的に発見でき、追加の設定が不要です。これは実際には小さなコツで、命名を規範的にすることで、後の処理も便利になります。

3. パブリッシュオーケストレーション

Build.AzureStorage.cs は AzureReleasePublishOrchestrator を通じて完全なフローをオーケストレーションします:

var orchestrator = new AzureReleasePublishOrchestrator(
new ArtifactHybridMetadataBuilder(), // ハイブリッドメタデータの構築
adapter);
summary = await orchestrator.PublishAsync(
downloadedFiles,
publishOptions,
outputPath,
UploadIndex,
MinifyIndexJson,
EffectiveGitHubRepository);

オーケストレーターは sidecar が index より前にアップロードされることを保証し、サマリーに診断情報を出力します。こうすることで、パブリッシュが失敗した場合、sidecar 生成の失敗、アップロードの欠落、インデックス書き込みの失敗のどれかを迅速に特定できます。これは実際にはかなり重要です。パブリッシュが失敗した場合、問題を迅速に特定でき、時間を無駄にしません。

実践

重要なコードモジュール

1. メタデータコンシューマー側

コンシューマー側は index.json のアセットオブジェクトからハイブリッド配布メタデータを構築します:

// http-index-source.ts:418-463
private buildHybridMetadata(asset: HttpIndexAsset, directUrl: string, assetKind: VersionAssetKind): HybridDistributionMetadata {
const torrentUrl = this.resolveOptionalUrl(asset.torrentUrl);
const hasTorrentMetadata = Boolean(torrentUrl || asset.infoHash);
// WebSeeds はデフォルトで directUrl を含み、フォールバックを保証
const webSeeds = [...legacyWebSeeds, ...structuredWebSeeds];
if (directUrl && !webSeeds.some((seed) => seed.toLowerCase() === directUrl.toLowerCase())) {
webSeeds.push(directUrl);
}
return {
torrentUrl,
infoHash: asset.infoHash,
webSeeds,
sha256: asset.sha256,
hasTorrentMetadata,
torrentFirst: hasTorrentMetadata, // P2P を優先使用
eligible: hasTorrentMetadata,
};
}

重要な設計ポイント:

  • torrentFirst フラグがダウンロード戦略を制御し、torrent メタデータがある場合は P2P を優先的に使用
  • webSeeds は directUrl を強制的に含み、フォールバック能力を保証
  • eligible フィールドはそのアセットがハイブリッド配布をサポートしているかどうかを示す

これは実際には小さなコツで、これらのフラグビットを通じて、ダウンロード戦略を柔軟に制御できます。

2. ハイブリッドダウンロードコーディネーター

ハイブリッドダウンロードコーディネーターは実際のダウンロードロジックの実行を担当します:

// hybrid-download-coordinator.ts:83-184
async download(...): Promise<HybridDownloadResult> {
const policy = this.policyEvaluator.evaluate(version, settings);
if (policy.useHybrid) {
try {
// Torrent エンジンによるダウンロードを優先
await this.engine.download(version, cachePath, settings, onProgress);
} catch (error) {
// Torrent が失敗した場合は HTTP/WebSeed にフォールバック
await this.downloadViaHttpSources(version, cachePath, packageSource, policy, ...);
}
} else {
// HTTP-only モード
await packageSource.downloadPackage(version, cachePath, onProgress);
}
// sha256 検証で完全性を保証
return await this.verify(version, cachePath, ...);
}

ダウンロード戦略:

  1. ユーザー設定とネットワーク環境を評価し、ハイブリッドモードを有効にするかどうかを決定
  2. Torrent ダウンロード(P2P)を優先的に試行
  3. 失敗時に自動的に HTTP/WebSeed にフォールバック
  4. ダウンロード完了後に SHA256 で完全性を検証

この設計により最高のユーザー体験が保証されます—P2P がある場合は加速し、ない場合でも正常にダウンロードできます。これは実際には良い戦略で、ユーザー体験が最も重要ですから。

3. パブリッシャー側オーケストレーション

パブリッシャー側はオーケストレーターを通じて全体のフローを調整します:

// Build.AzureStorage.cs:152-168
var orchestrator = new AzureReleasePublishOrchestrator(
new ArtifactHybridMetadataBuilder(),
adapter);
summary = await orchestrator.PublishAsync(
downloadedFiles,
publishOptions,
outputPath,
UploadIndex,
MinifyIndexJson,
EffectiveGitHubRepository);

オーケストレーターの責務:

  1. メタデータビルダーを呼び出して P2P メタデータを生成
  2. メインファイルと sidecar の両方が Blob ストレージにアップロードされることを保証
  3. index.json の assets と files 投影を更新
  4. パブリッシュサマリーを出力し、診断情報を含む

これは実際には良いアーキテクチャで、オーケストレーターを通じて全体のフローを繋ぎ、後の保守も便利です。

実践経験

このソリューションの実装プロセスで、私たちはいくつかの実践経験を蓄積しました:

命名規約は重要:{artifact}.zip.torrent を使用すると、ZIP から sidecar を逆引きしやすいです。この規約は一見シンプルですが、実際の運用では多くのトラブルを省けます—コンシューマー側は sidecar を自動的に発見でき、追加の設定が不要です。これは実際には小さなコツで、命名を規範的にすることで、後の処理も便利になります。

失敗診断は明確に:パブリッシュサマリーは sidecar 生成の失敗、アップロードの欠落、インデックス書き込みの失敗を明確に区別する必要があります。私たちは初期バージョンで苦い経験をしました。パブリッシュが失敗した後、どのステップで問題が発生したかわからず、調査に時間がかかりました。現在は各ステップに明確なエラー情報があり、問題の特定がはるかに高速になりました。これは実際にはかなり重要です。デバッグ時間も一種のコストですから。

安全なデグラデーション:条件を満たさないアセットは自動的に HTTP-only にフォールバックし、パブリッシュ全体をブロックしません。例えば、あるファイルが 100 MB 未満、または torrent 生成が失敗した場合、P2P メタデータを生成せず、直接 HTTP ダウンロードを使用します。こうすることで、P2P パスに問題が発生しても、基本機能に影響しません。これは実際には良い戦略で、一つの機能の失敗のためにパブリッシュプロセス全体に影響を与えるべきではありません。

閾値検証:パブリッシャー側の閾値はコンシューマー側の HYBRID_THRESHOLD_BYTES と一致している必要があります。私たちはこの値を定数として定義し、CI でコンシューマー側とパブリッシャー側の一貫性をテストします。一致しない場合、「パブリッシャー側は加速可能と判定、コンシューマー側は加速しないと判定」という厄介な状況が発生します。これは実際にはかなり重要です。両側が一致しない場合、様々な奇妙な問題が発生するからです。

SHA256 はセキュリティ防御ライン:どのチャネル(P2P、HTTP、WebSeed)でダウンロードしても、最後に SHA256 で検証します。これはファイルの改ざんを防ぐ最後の防御ラインであり、絶対に省略できません。これはファイルに指紋を付けるようなもので、万一改ざんされても、すぐに発見できます。セキュリティ問題については、どれだけ慎重すぎてもありません。

まとめ

デスクトップアプリの大容量ファイル配布は古典的な難問ですが、P2P 技術はエレガントなソリューションを提供します。このハイブリッド配布アーキテクチャを通じて、HagiCode Desktop はいくつかの重要な目標を達成しました:

配布コストの削減:P2P はサーバーの帯域負荷を分散し、ピーク時でも安定した配布能力を維持できます。これは実際には良好な収益で、帯域コストを少しでも節約できるのは良いことです。

ユーザー体験の向上:P2P 接続がある場合、ダウンロード速度が大幅に向上し、特に海外ユーザーにとって顕著です。P2P 接続がない場合でも HTTP を通じて正常にダウンロードでき、100% の可用性を保証します。これは実際には良い戦略で、ユーザー体験が最も重要です。

スムーズな進化パス:二重投影インデックス設計を通じて、サーバー側とクライアント側の独立アップグレードを実現しました。古いクライアントは影響を受けず、新しいクライアントは徐々に P2P 加速を有効にします。これは実際には良いアーキテクチャで、スムーズにアップグレードできれば、既存ユーザーに影響しません。

このソリューションの核心的なアイデアは「プログレッシブエンハンスメント」です—HTTP がベースラインで、P2P がエンハンスメントです。こうすることで信頼性を保証し、性能向上の余地も提供します。これは実際には良い理念で、性能を追求するために信頼性を犠牲にすべきではありません。

あなたもデスクトップアプリの配布を行っている、または同様の大容量ファイル配布の問題に直面している場合、このソリューションが何らかのインスピレーションを提供できることを願っています。P2P 技術は神秘的ではなく、鍵はパブリッシャー側とコンシューマー側の契約を適切に設計し、パス全体を動作させることです。これは実際には良い経験で、他人を助けることができれば、良いことだと思います。

参考資料


本文が役に立った場合は、GitHub で Star をいただけると幸いです:github.com/HagiCode-org/site。HagiCode Desktop のパブリックテストが開始されました。インストールして体験してください!これは実際には良い招待で、試用する人が一人増えれば、フィードバックも一つ増え、良いことだと思います。

开始使用 HagiCode

一次安装,几分钟上手

HagiCode for Windows 在 Microsoft Store 免费提供。打开商店即可安装并保持更新;也可以先对比各版本与定价,再决定从哪个渠道开始。