プログラミング
Walgit: オブジェクトストアの前に単一バイナリで構成されるGitサーバー
Walgit: A Git server that is one binary in front of an object store (github.com)
要約
Walgitは、データベースやリーダー、ローカル状態に依存しない、オブジェクトストア(S3やGCS)の前に単一バイナリを配置するGitサーバーです。スマートHTTP(v0/v2)でのフェッチとプッシュ、バンドルURIクローン、Git LFS、Web UI、JSON API、プッシュポリシー、Webフックなどをサポートし、リポジトリサイズ以上のスケーラビリティを持ちます。各インスタンスは使い捨てキャッシュとして機能し、オブジェクトストアがリポジトリの真実の源となります。
全文翻訳
walgit — オブジェクトストアの前に単一バイナリで構成されるGitサーバー
walgitは、データベース、リーダー、ローカル状態に依存せずにGitリポジトリをホストします。単一のバイナリを実行し、S3またはGCSバケットを指し示すだけで、以下の機能が利用できます: スマートHTTP(v0/v2)でのフェッチとプッシュ、静的ファイルとして提供されるバンドルURIクローン、Git LFS、ブラウジング可能なWeb UI、SDK付きJSON API、リポジトリごとのプッシュポリシー、Webフック — そして、実行マシンよりも大きなリポジトリにスケールするサーバーです。walgitを実行するすべてのマシンは使い捨てキャッシュであり、バケットがリポジトリとなります。
# 1. バケット(S3互換ストアまたはGCS)と設定
cat > walgit.toml <<'EOF'
[server]
listen = "0.0.0.0:8080"
public_url = "https://git.example.com"
auto_create_on_push = true
[server.auth]
mode = "token"
anonymous_read = false
tokens = [ { principal = "me", token_env = "WALGIT_TOKEN_ME", write = true } ]
[store]
backend = "s3"
bucket = "my-walgit"
[store.s3]
endpoint = "https://s3.us-east-1.amazonaws.com"
region = "us-east-1"
EOF
# 2. 実行
WALGIT_TOKEN_ME=$(openssl rand -hex 24)
walgit serve --config walgit.toml
# 3. 使用
— 新しい名前へのプッシュでリポジトリが作成されます
git -c http.extraHeader="Authorization: Bearer $WALGIT_TOKEN_ME" push https://git.example.com/acme/app.git main
これがデプロイのすべてです。同じバケットを指すマシンを増やすと、それらは同じリポジトリを一貫して提供し、調整する必要はありません。それらをすべて停止しても、暖かさ以外は何も失われません。
これは、CursorがGit at any scale(彼らがContinuityと呼ぶシステム)で説明したアーキテクチャのRust実装であり、リポジトリよりも小さいマシンで実行するために必要な変更が加えられています。その投稿は最初に読む価値があり、docs/reference/cursor-git-at-any-scale.mdにそのまま保持されています。
なぜこの形状なのか
Gitは分散型であり、それがホスティングを一つの理由で悩ましいものにしています: packfilesです。リポジトリ内のすべては、小さいサイズになるようにレイアウトされた大きなバイナリパックに圧縮されており、順序通りに読み取るようにはなっていません。すべてのgit操作は、数ギガバイトのランダムウォークです。これは、ファイルがページキャッシュにあるラップトップでは問題ありませんが、ネットワークファイルシステム上では壊滅的です。そのため、「リポジトリをNFSに置くだけ」というアプローチは、試みたすべての大規模ホストで失敗しました。
生き残った設計(GitHubのSpokes)は、実際のレポジトリをローカルNVMeに保持してupstream gitに作業を行わせ、パックファイルレベルで厳密な一貫性でレプリケートします。これは、固定レプリカセット全体での3フェーズコミット、各リポジトリをマシンにマッピングするデータベース、およびペットのフリートによって実現されています。
Continuityの洞察は経済性を変えます: オブジェクトストレージ内のライトアヘッドログ(WAL)を真実の源とし、オンディスクリポジトリをすべてキャッシュとします。プッシュはバケット内の不変オブジェクトとして保存され、小さなマニフェストが比較・交換(CAS)で書き換えられたときにのみ表示されます。そのCASがコンセンサスです — 選挙なし、クォーラムなし、プライマリなし。どのインスタンスもプッシュを受け入れることができます。競合する2つのインスタンスが同時に勝つことはできません。
リポジトリを一度も見たことのないレプリカは、ログを読み取り、それを取得します。読み取りは、各読み取りが最初にストアに何か変更があったか(条件付きGET、通常は304)を尋ねるため、調整なしで一貫性があります。コンパクションは、リースを保持している誰かによって一度だけ実行され、ログに公開されるため、レプリカは再パックではなくコンパクトされたパックをダウンロードします。そしてWALが真実であるため、完全な来歴があります: すべてのプッシュとすべてのリパックは、任意の時点まで再生可能です。
walgitはそれをそのまま取り込み、小さなマシン上のモノリポジトリが必要とするもの — リポジトリのパックがインスタンスに収まることのない参照とWebページを提供すること(HTTPレンジリクエスト経由のリモートリーダー)、コミットとツリーをローカルに保持しブロブはバケットに残すこと(履歴パック)、クローンバイトをサーバーから完全に移動させること(バンドルURI: 新規クローンとキャッチアップはバケットまたはCDNが提供する静的ファイル) — を追加します。
機能
gitスマートHTTP v0/v2: プレフィックス付きls-refs、フィルター/シャロー/ディープン/サイドバンド-オール付きフェッチ、receive-pack(アトミック、削除、タグ、プッシュオプション、レポートステータスv2)、<owner>/<repo>名前空間、sha1およびsha256リポジトリ。upstream gitはupload-pack/repack/bundleを実行し、walgitはreceive-pack、WAL、および配管を実行します。
bundle-uri
バンドルはカレンダースロット(週次フル、デイリーチェーン、アワーリー)で純粋関数としてカットされます。新規クローンは最新のフルバンドルとそれ以前のチェーンをバケットからダウンロードし、サーバーには残りの部分のみを要求します。キャッチアップは、見逃したスロットのみをダウンロードします。リポジトリごとに2つのリスト: クローン用のbundles/list、フェッチ用のbundles/catchup。ブロブなしファミリーは--filter=blob:none用です。
LFS
Batch API + 基本転送、バケット内のオブジェクト、インポートされたリポジトリ用のアップストリームLFSサーバーからのオプションのリードスルー。
Web UI + API
React UI(ツリー、ブロブ、コミット、差分、WAL固有のヘルスページ)は、/{owner}/{repo}/api/*の下の読み取り専用JSON API上にあります。SHAアドレス指定された回答は不変で、どこでもキャッシュされます。長い回答はSSEとして進捗をストリーミングします。repos.jsはページ、エージェント、スクリプト用の依存関係のないSDKです。
ポリシー
リポジトリごとのプッシュルール(policy.json): 保護された参照、グループ、ファストフォワードのみ、バイパスリスト。docs/POLICY.mdを参照してください。
設定
リポジトリごとの設定(バンドルスケジュール、コンパクション、アップストリームフォロー)は、履歴と共にWALに公開されます。
イベント
小さなブリッジがWALをテールし、リファレンスイベントをWebフックにPOSTします。各(リポジトリ、シーケンス、リファレンス)ごとに正確に1回、耐久性のあるカーソルを使用します。docs/EVENTS.mdを参照してください。
メンテナンス
チェックポイント、バンドルビルド、幾何学的コンパクション、ベース再構築、接続監査と修復 — 各パスで(設定、WAL)から望ましい状態を計算し、最も重要な不足作業の1つのバウンドされた単位を実行する1つのループです。構築による自己修復: 障害があっても穴は残らず、削除されたアーティファクトは「欠落」とマークされ、同一に再構築されます。
認証
なし(ループバック)、トークン(静的トークン)、oidc(任意のOpenID Connect発行者: ブラウザサインイン、IDトークン、git用のwalgit発行アクセストークン)。/services/public/install.shは、開発者のマシンを1つの冪等なコマンドでセットアップします。
ストア
S3およびS3互換(AWS、MinIO、rustfs、R2、Cephなど)およびGCSを第一級としてサポート。テスト用のインメモリストア。
仕組み(概要)
リポジトリはバケット内のWALです。repos/<owner>/<repo>/の下:
manifest.pb(小さい、CAS書き換え済み: ヘッドシーケンス、ライブパックセット、チェックポイントポインタ、設定 — 線形化ポイント)、log/<seq>.pb(不変エントリ: PUSH、COMPACT、CHECKPOINT、SETTINGS)、wal/<checksum>.pack|.idx|.rev|.bitmap|.commit-graph(不変、コンテンツアドレス指定されたパックとそのサイドファイル)、checkpoints/<seq>/(折りたたまれた参照スナップショット+パックインベントリ、コールドスタートはスナップショット+テール)、bundles/、leases/(TTL付きCAS — 唯一のクロスインスタンスミューテックス)、policy.json、lfs/objects/、events/cursor.json。
プッシュ: receive-packはパックをインデックス化し(scratchディレクトリでgit index-pack --fix-thin --rev-index)、接続性とポリシーをチェックし、pack || idx || logエントリをアップロードし、次にマニフェストをCASします。412エラーの場合、再読み取りし、すべての参照の古い値を再検証して再試行します。単一リポジトリへの同時プッシュは、単一のCASにグループコミットされます。クライアントは、バケットが確認した後でのみOKを見ます。
読み取り: マニフェストの条件付きGETを1回実行します。304 → ローカルコピーから提供、200 → 新しいエントリを適用します。「適用」の意味は、リクエストが必要とするものによって異なります: 参照(スナップショット+ログ → packed-refs、パックなし: 広告、API、バンドルリスト)、提供(このマシンが保持できるパックセット: 小さなパックと履歴パックをローカルに、大きすぎるベースはレンジで読み取り)、フル(すべてローカル、リパック用)、オブジェクト(UI用のリモートリーダー、リポジトリが収まらない場合)。パックダウンロードは独自のランタイムで実行され、参照リクエストをブロックしません。
配置は設定によります。[placement] serve / maintain globs は、ホストがどのリポジトリでオブジェクト作業を行うかを指定します。参照レベルの読み取りはどこでも機能します。1台のマシン: デフォルトのままにします。複数台: モノリポジトリをSSDを持つホストに配置し(cache.mode = "disk")、他はすべて小さなマシンに配置し、/ <owner>/<repo>でルーティングします。何も静かに待つことはありません。何でもs