
GitHub は、リポジトリをホストする単なるプラットフォームではありません。毎日何百万ものトランザクションを処理する、高度にスケーラブルな分散システムです。Git プッシュ リクエストの処理からファイルの差分の効率的な計算まで、GitHub は堅牢なアルゴリズムとアーキテクチャに依存して、高いパフォーマンスと信頼性を確保しています。
この記事では、GitHub が膨大な量のデータを処理し、数百万のトランザクションを処理できるように拡張し、差分アルゴリズムを使用してファイルの変更を効率的に追跡する方法について説明します。また、バージョン管理システムで使用されるコア アルゴリズムの詳細な JavaScript 実装も含まれています。
最新のバージョン管理システムは、いくつかの重要な課題に対処する必要があります。
これらの原則は GitHub に限定されるものではありません。大規模なバージョン管理を扱う GitLab、Bitbucket、その他のプラットフォームでも同様のアーキテクチャとアルゴリズムが使用されています。
ファイルの変更を追跡する際、GitHub (および Git 自体) は diff アルゴリズムを使用して、ファイルのあるバージョンを別のバージョンに変換するために必要な編集の最小数を計算します。このために広く使用されているアルゴリズムは、Myers の Diff アルゴリズムです。
マイヤーズ アルゴリズムは、あるファイルを別のファイルに変換するために必要な挿入と削除の最短シーケンスを見つけます。このアルゴリズムは、可能な編集距離 (d) を反復処理し、編集グラフの「対角線」に沿って可能な変換を計算することによって動作します。
/** * Computes the minimum edit distance between two arrays using Myers' Diff Algorithm. * @param {Array} a - The original array (eg, characters of a file) * @param {Array} b - The modified array * @returns {number} The minimum number of edit operations required */ function myersDiff(a, b) { const N = a.length; const M = b.length; const maxD = N + M; let v = { 1: 0 }; for (let d = 0; d <= maxD; d++) { for (let k = -d; k <= d; k += 2) { let x; if (k === -d || (k !== d && (v[k - 1] || 0) < (v[k + 1] || 0))) { x = v[k + 1] || 0; } else { x = (v[k - 1] || 0) + 1; } let y = x - k; while (x < N && y < M && a[x] === b[y]) { x++; y++; } v[k] = x; if (x >= N && y >= M) { return d; } } } return maxD; } // Example usage: const oldVersion = Array.from("Hello World"); const newVersion = Array.from("Hello GitHub World"); const operations = myersDiff(oldVersion, newVersion); console.log(`Minimum number of edits: ${operations}`);
コードの内訳:
初期化: アルゴリズムは、編集グラフ内の各対角線の最大 x 値を格納する配列 v を初期化します。
可能な編集距離をループする (d): 必要な編集の可能な回数ごとに繰り返します。
最適パスの計算: v[k] 値に基づいて挿入するか削除するかを決定します。
「貪欲な一致」ステップ: 文字が一致する限り斜めに移動し、不要な操作を最小限に抑えます。
数百万のトランザクションを処理するために、GitHub は多層アーキテクチャを採用しています。典型的なトランザクションの流れは次のとおりです。
このアーキテクチャにより、GitHubは効率的に拡張でき、単一のコンポーネントがボトルネックになることがなくなります。
GitHub は、高トラフィックを処理するためにトランザクションを非同期的に処理します。次の JavaScript コードは、Promise を使用してトランザクションの並列処理をシミュレートします。
/** * Simulates a transaction in a version control system. */ class Transaction { constructor(id, action, payload) { this.id = id; this.action = action; this.payload = payload; } } /** * Simulates processing a transaction step-by-step. * @param {Transaction} tx - The transaction to process * @returns {Promise<string>} The result of processing */ function processTransaction(tx) { return new Promise((resolve) => { console.log(`Processing transaction ${tx.id}: ${tx.action}`); setTimeout(() => { console.log(`Indexing ${tx.id}...`); setTimeout(() => { console.log(`Computing diff for ${tx.id}...`); setTimeout(() => { console.log(`Updating database for ${tx.id}...`); resolve("success"); }, 100); }, 50); }, 100); }); } /** * Simulates processing multiple transactions in parallel. */ async function processTransactions() { const transactions = [ new Transaction("tx001", "commit", "Modified file A"), new Transaction("tx002", "commit", "Fixed bug in file B"), new Transaction("tx003", "merge", "Merged branches"), ]; const promises = transactions.map(async (tx) => { const result = await processTransaction(tx); console.log(`Transaction ${tx.id} result: ${result}`); }); await Promise.all(promises); console.log("All transactions processed."); } // Run transaction processing processTransactions();
このコードから得られる重要なポイント:
GitHub が 1 日に数百万件のトランザクションを処理できるのは、以下の要素の組み合わせによるものです。
これらのテクニックは GitHub に限定されるものではなく、GitLab、Bitbucket、大規模データ処理システムで広く使用されています。これらの原則を理解することで、開発者は大量のトランザクションを処理するための効率的でスケーラブルなアプリケーションを構築できるようになります。