パックファイルを再作成すればオブジェクトストレージ上でGitを実行できる
You can run Git on object storage if you re-make packfiles (tigrisdata.com)
要約
オブジェクトストレージ上でGitを効率的に実行するためには、従来のパックファイル形式を再設計する必要があることが示されています。本記事では、Gitのオブジェクト構造とパックファイルの課題を解説し、オブジェクトストレージネイティブな新しいパックファイル形式の提案とその利点について説明しています。
全文翻訳
オブジェクトストレージ上でGitを実行するには、パックファイルを再構築する必要がある
最近、多くの企業が何らかのGit製品をリリースしようとしているようです。なぜでしょうか。
いずれにせよ、私はオブジェクトストレージをバックエンドとするGitサーバーをオープンソースプロジェクトとして構築しています。当初はシンプルに聞こえました。Gitはファイルシステムのように見えるので、オブジェクトストレージ上でGitがオブジェクトストレージと通信できるように、オブジェクトストレージの上にファイルシステムの翻訳レイヤーを使用しましょう。このモデルは…まあ、そこそこ機能しました。
しかし、実世界のサイズのレポジトリでは機能しなかったので、別のアプローチが必要でした。Gitはすべてをオブジェクトに保存するので、それらをTigrisのオブジェクトとして保存するのはどうでしょうか。
結果として、Gitのパックファイルと、それが私の(確かに多少ひどい)ファイルシステムシムとどのように相互作用していたかが、遅延の主な原因であることが判明しました。最終的には、オブジェクトストレージネイティブなカラム型ストアを持つ独自のパックファイル形式を考案する必要がありました。これは、私のパフォーマンス分析、メトリクス注釈、そしてk8sクラスターがスティックを振るよりも多くのテキサス風分散システム作業の成果です。
このアプローチは、本番環境サイズのレポジトリでも驚くほどうまく機能したので、現時点ではこの新しいパックファイル形式を採用しています。クライアント側の変更なしに、Gitオブジェクトをオブジェクトストレージオブジェクトのように感じさせるための、最も邪魔にならない変更のように思えます。
Gitとは何か? オブジェクトの惨めな小山!
コミットを行うと、Gitは変更を.git(バックティックを少なくするために「ドットギット」と呼びます)フォルダ内のオブジェクトとして保存します。
Gitを、オブジェクトの海と個々のオブジェクトへの名前付き参照の2つのものとして想像してください。各オブジェクトは、コンテンツアドレス指定され圧縮されたファイルです。小さなGitリポジトリの例を以下に示します。
$ mkdir ~/tmp/gitexample$ git init && git branch -m main$ echo "Hello, blog!" >> hello.txt$ git add .$ git commit -sm "chore: initial commit"
これにより、ディスク上に次のような複数のオブジェクトが生成されます。
FIG 01 オブジェクトの海と、そこへのいくつかの名前
.git/objects/ refs/heads/main│├── 1c/7a26a901..ec7966 ─────▶ commit 1c7a26a│ │├── 8e/67afbb2e..857bd3 ─────▶ tree 8e67afb│ │ hello.txt└── 9c/c9867337..09fe26 ─────▶ blob 9cc9867"Hello, blog!"ファイル名はファイル内のバイトのsha1なので、同じコンテンツは常に、どこでも、全く同じオブジェクトです。
オブジェクトの内容を読み取るには、圧縮されているため、かなり邪悪に見えるPythonのワンライナーを使用して、美味しい中身をすくい出す必要があります。
$ file .git/objects/**/* | grep -v directory.git/objects/1c/7a26a901724b4ce766655ac387413fb9ec7966: zlib compressed data.git/objects/8e/67afbb2ee6bdcbb79061dfdfb93febce857bd3: zlib compressed data.git/objects/9c/c9867337c2ebae85ba2350f901e0bcc209fe26: zlib compressed data$ python3 -c "import sys, zlib; sys.stdout.buffer.write(zlib.decompress(sys.stdin.buffer.read()))" < .git/objects/9c/c9867337c2ebae85ba2350f901e0bcc209fe26blob 13Hello, blog!
ご覧のとおり、オブジェクトは単なる生のファイルです。LinuxカーネルのGitリポジトリを見て、任意のコミットを抽出してみましょう。すべてがディスク上の億万個の生のオブジェクトファイルであるはずですよね?IDをディスク上で探すだけで、単一のコミットを見つけるのは簡単はずですよね?
現実はそれほど単純であればいいのですが。
$ cd ~/Code/linux.git/$ tree objectsobjects├── info└── pack ├── pack-45986f41063f286029742ec12e2c2882b88c5786.idx ├── pack-45986f41063f286029742ec12e2c2882b88c5786.pack └── pack-45986f41063f286029742ec12e2c2882b88c5786.rev3 directories, 3 files
ええ、お察しの通り、すべてを個別のファイルに入れるだけでは、Linuxカーネルのような規模には対応できません。私は、フラクタルなnode_modulesフォルダの時代に皆が遭遇したような、inode制限にぶつかるだろうと確信しています。
余談
Nodeを長く使っていてそれを覚えている方は、大腸内視鏡検査を受けてください。大腸がんは、多くの人が長期間見過ごしている現実的な懸念であり、あまりにも多くの命を早期に奪っています。
Gitはこの問題を回避するために、オブジェクトをパックファイルに入れています。これは、それらをすべて同じファイルに格納するオブジェクトの圧縮バンドルです。objgitのチェックアウトにおけるパックファイルの効率の例を以下に示します。
$ git count-objects -vcount: 756size: 3500in-pack: 448packs: 1size-pack: 321prune-packable: 0garbage: 0size-garbage: 0
もしGitに生のオブジェクトをパックファイルに入れることを「強制」する必要がある場合は、git gcを実行できます。
$ git gc[omitted for brevity]$ git count-objects -vcount: 0size: 0in-pack: 1203packs: 2size-pack: 848prune-packable: 0garbage: 0size-garbage: 0
余談
私がやっているように、オブジェクトストレージの上にGitを実装する上で美しいことの一つは、オブジェクトデータがオブジェクトの海であり、FoundationDBに格納されたその海のポイントへの名前付き参照であるプラットフォームを使用していることです。これは、その美しさをどのように説明すればよいのかわからない、神聖な再帰の一種です。上にあるものが下にもあるように。
Yo dawg, herd you like objects
Linuxカーネルのコピーのオブジェクト数を以下に示します。
xe@zohar:~/Code/linux.git$ git count-objects -vcount: 0size: 0in-pack: 11827138packs: 1size-pack: 3876775prune-packable: 0garbage: 0size-garbage: 0
これは1100万個のオブジェクトであり、1つのGetObject呼び出しあたり10ミリ秒という非常に寛大な仮定の下では、それらすべてを取得するのに1時間以上かかります。実際には、ディスク上に1100万個のオブジェクトが個別のファイルとして存在するわけではなく、それらは1つの大きなハッピーな3.4Giのパックファイルにバンドルされています。通常のGitリポジトリは、それが意味をなすにつれてそれらを蓄積していきます。私のTigrisブログのローカルコピーには4つのパックファイルと約290個の生のオブジェクトがあります。
そこで、あなたは「ああ、Gitにはパックファイルがあるなら、なぜこの記事の残りは存在するんだ?」と思うでしょう。
さて、分散システムでは多くのことがそうであるように、それは複雑です。パックファイルは、ローカルストレージやmmapを念頭に置いて設計されているため、扱いにくいです。Gitはパックファイルをディスクに継続的に書き込み、その後再読み込みします。その場合、ファイルシステムの読み取りは最大でも10ナノ秒です(ファイルシステムキャッシュが非常に役立ちます)が、ネットワークラウンドトリップを行うと最低でも10ミリ秒かかります。現実がどのように機能するかにより、少なくとも100万倍遅くなります。
注意
/usr/bin/gitが行うことで、オブジェクトストレージへの統合を困難にする一つのことは、ファイルに書き込み、その後すぐにそのファイルから読み込んでハッシュを計算するというUnix的な慣習です。オブジェクトストレージでは、PutObject呼び出しが完了していないものをGetObjectすることはできません。以前はディスクに書き込んでからその方法で行うことで回避していましたが、実際にはその経験はあまり良くありませんでした。
パックファイルいじり
各パックファイルには、その内容を説明するインデックスがあります。以前の小さなGitリポジトリから作成されたパックファイルのインデックスのビューを以下に示します。
$ git gc # force objects into a packfile$ git verify-pack -v .git/objects/pack/pack-3971f5085c23c38be00e517ed0c64ca7df19b746.idx1c7a26a901724b4ce766655ac387413fb9ec7966 commit 526 366 129cc9867337c2ebae85ba2350f901e0bcc209fe26 blob 13 22 3788e67afbb2ee6bdcbb79061dfdfb93febce857bd3 tree 37 48 400non delta: 3 objects.git/objects/pack/pack-3971f5085c23c38be00e517ed0c64ca7df19b746.pack: ok
コミットはツリーを指し、そのツリーのファイル「hello.txt」はブロブを指し、そして、あなたはリポジトリを手に入れます。Gitはこれらのバイナリインデックスを使用して、どこを見ればよいか、パックファイル内のどこまでシークする必要があるかを把握します。
FIG 02 1100万個のオブジェクト、1つのパックファイル、1つのインデックス
$ git count-objects -v .git/objects/pack/ count: 0 └── pack-45986f41..c5786.pack 3.7 GiB in-pack: 11827138 packs: 1 eleven million loose files would be size-pack: 3876775 eleven million inodes. so: one file. .idx .pack ┌───────────────────────┐ ┌──────────────────────────────────────┐ │ 0002ff4c.. 0x0000c │ │███ ██ ████ █ ███████ ██ █ ████ ██████│ │ 0031ab90.. 0x0a13f │ └──────────────────────────────────────┘ │ 1c7a26a9.. 0x1f3a4 │ │ ... │ └───────────────────────┘ each row's colour is the run of bytes its offset points at, so a read is a seek to an offset inside one very big file
ALL/03 3行、3つのオフセット、3つのバイトの範囲
すべてがファイルシステム上にある場合、これは非常にうまく機能します。Gitはパックファイルをmmapするため、カーネルはディスクの内容をメモリページとして扱います。これは、「メモリ内」にあるものを超えて読み取ろうとすると、カーネルがそれをロードすることを意味し、ユーザー空間よりも高速です。この設計がオブジェクトストレージで機能しないのは残念です。
パックファイルからRangeリクエストを構築できればいいのに
あるレベルでは、これはオブジェクトストレージにとって非常に素晴らしいことのように思えますよね?パックファイルへのオフセットがあり、それからパックファイルから単一のオブジェクトをHTTP Rangeリクエストで「ただ」取得できるだけですよね?オブジェクトはパックファイル内にランダムに配置されており、Gitをオブジェクトストレージに格納しようとする他の試みはここで問題を抱えています。Tigrisはランダムアクセススキャンに非常に優れているため、難しい部分のほとんどは、バケットから正しいデータを取得する方法を理解することです。
余談
HTTP Rangeリクエストにより、クライアントはファイルの一部をダウンロードできます。それらが構築された主なユースケースは、ダイヤルアップインターネットの時代に遡ります。あなたは常にオンラインではありませんでした。インターネットへの主な経路は、電話の送受信と同じ方法でした。そのため、オンライン中に誰かから電話がかかってくると、すべてのダウンロードが中断されました。Rangeリクエストにより、Internet Explorerは中断された場所からダウンロードを再開でき、最初からやり直す必要がなくなりました。
それらは現代にも維持されていますが、私がやっているようなギャラクシーブレインのフォーマット乱用やオンラインビデオストリーミング以外では、あまり使われていません。
さて、それは複雑です。私が示した例では、インデックスエントリがすべて順番に並んでいますが、実際の世界ではインデックスエントリを順番に処理すると、パックファイル内の単一オブジェクトの解凍済みサイズはわかりますが、圧縮済みサイズはわかりません。これは、HTTP Rangeリクエストを構築するのに十分な情報がないことを意味します。
FIG 03 1つのRange GET、128MiBの中央から366バイト
packs/019a7f3c..bin┌────────────────────────────────────────────────┐ │ ██│└───────────────────────────────────▲────────────┘ 0 │ 128 MiB└── 366 bytes, right hereyou know where it starts and how long it is, so ask forexactly that span and nothing else:GET /packs/019a7f3c..binRange: bytes=100663296-100663661and that is all the bucket sends back:206 Partial ContentContent-Range: bytes 100663296-100663661/134217728┌────┐ │████│ 366 B on the wire, not 128 MiB└────┘
この中心的な問題が、私が独自のオブジェクトストレージネイティブGitパックファイル形式を作成する必要があった理由の半分です。
SEND CUE SHEET
物理メディアの時代、最も一般的な形式の一つはCD-ROM(Compact Disc Read-Only-Memory、またはCD)でした。CDは700Miのコンテナで、データをセッションに格納し、各セッションには最大99トラックのオーディオまたはデータが含まれます。CDは元々、アナログカセットテープよりも高品質で約1時間の音楽を聴けるように、音楽オーディオを格納するために発明されました。CDはまた、プレーヤーがトラックからトラックへスキップできるようにするため、好きな曲や大きな作品の楽章に直接移動できます。
これもまた、CDやDVDメディアの合法的なバックアップをロスレスな.isoファイルに保存するようにというガイドがある理由です。.isoファイルには、データまたはオーディオを含む可能性のある1つの記録セッションが含まれています。
FIG 04 CDキューシートとobjgitキューシートは同じ問題を解決します
CD AUDIO DISC OBJGIT PACKFILE────────────────────────────────────────── ──────────────────────────────────────────U0008_0000002_0000147.WAV one big file objects.bin one big file┌──────────┬─────────────┬─────────────────┐ ┌──────────┬─────────────┬─────────────────┐│ track 01 │ track 02 │ ... │ │ blob A │ tree B │ ... │└──────────┴─────────────┴─────────────────┘ └──────────┴─────────────┴─────────────────┘ ▲ ▲ ▲ ▲ │ 00:00 │ 00:13 │ @0 │ @142FILE.cue plain text, parsed top-down objects.cue packed records, fixed width┌──────────────────────────────────────────┐ ┌──────────────────────────────────────────┐│ FILE "U0008_0000002_0000147.WAV" WAVE │ │ HEADER 16 B ││ │ │ magic version rec_size record_count ││ TRACK 01 AUDIO │ ├──────────────────────────────────────────┤│ INDEX 01 00:00 │ │ RECORD 0 58 B ││ TITLE "U0008_0000002_0000147_0001" │ │ hash 3a7f…c21 type blob ││ │ │ comp zstd size 311 ││ TRACK 02 AUDIO │ │ bin_offset 0 bin_length 142 ││ INDEX 01 00:13 │ │ delta_base 0000…0000 ││ TITLE "U0008_0000002_0000147_0002" │ ├──────────────────────────────────────────┤└──────────────────────────────────────────┘ │ RECORD 1 58 B │ └──────────────────────────────────────────┘to reach track 2 a player parses everyline above it. it already has the disc. record N sits at 16 + N*58. one seek,then one ranged GET into objects.bin.
しかし、CDのバックアップを簡単にバックアップできるというこの簡単な方法は、一つ問題があります。それは、複数の記録セッションを同じディスクに格納できることです。ほとんどの場合、これは90年代後半から00年代初頭の一部のゲーム機での海賊版対策をより煩雑にする以外では使用されませんでしたが、Ricoh Encryptease製品のように、ユーザー記録可能領域と工場出荷時印刷領域を組み合わせたものがありました。これにより、復号化ソフトウェアを同梱して共有するCDにファイルを暗号化できます。これは、聞くほどに呪われたものです。
注意
複数のセッションを使用するトリックは、DreamcastゲームがオーディオCDとして再生され、Dreamcastに挿入するように指示したり、Xbox 360ゲームがDVDとして再生され、Xbox 360に挿入するように指示したりする方法です。追加のボーナスとして、コンピューターに挿入すると、オーディオCDまたはDVDとして認識されるため、怠惰な海賊はゲームファイルをハードドライブに簡単にコピーできません。
その結果、アーカイブ目的でこれを適切に処理する方法が必要になりました。最終的な結果は、バイナリデータブロブと一緒に保存するためにキューシートを作成することでした。.cueシートは、デコーダーが.binファイル内の任意のポイントにシークできるように使用する情報を格納します。これにより、CDイメージ全体を読み取る必要なしに、曲やデータビットなどを簡単に抽出できます。追加のボーナスとして、複数の記録セッションを処理してくれます。
Packfiles v2: オブジェクトストレージ ブーガルー
これは私に考えさせました。これらの教訓をすべて心に留めて、オブジェクトストレージに最適化された新しいGitパックファイル形式をどのように構築できるでしょうか?
待ってください、あなたが何を考えているかわかっています。あなたは私がチェスタートンのフェンスの違反を犯そうとしていると考えているでしょう。会社のコードリポジトリの履歴を保存するという、非常に大切で貴重なものに対して、自分でフォーマットを「ロールする」のは、おそらくあなたが犯す可能性のある最悪の決断の一つですよね?
通常はそうです。これは悪い考えです。しかし、Gitは分散バージョン管理システムです。リポジトリをクローンするとき、すべてのブランチのすべての変更を同時にクローンします。これは、誰もがそのリポジトリの履歴全体をコピーしていることを意味します。つまり、最悪の事態が発生し、私の手作りのフォーマットがひどいものになったとしても、すべてのデータを再作成するのは簡単です。もう一度プッシュするだけです。
では、このフォーマットはどのようなものになるでしょうか?
まず、フォーマットはRangeリクエストネイティブである必要があります。あなたは、パックファイルから任意のオブジェクトを、あなたが望むオブジェクト以外のものをダウンロードしたり処理したりすることなく、すくい取ることができるはずです。繰り返しになりますが、Tigrisはこの点に優れているので、このユースケースを直接念頭に置いてフォーマットを設計すべきです。フォーマットは、zlibよりも高速でデータ効率の良いzstdのような最新の圧縮ライブラリを活用すべきです。最後に、デルタオブジェクトは、オブジェクトとそのデルタを読み取る前にオブジェクトを読み取る必要がないように、それがデルタであるオブジェクトの末尾に貼り付けるのではなく、パックファイル内の独自のオブジェクトとして格納されるべきです。
注意
時間を節約するために以前は省略しましたが、Gitはファイルリビジョン(特定の時点でのファイルの完全なコピー)とそれらの間の差の両方を最適化のために保存します。あるレベルでは、このミームは正確でもあり間違ってもいます。
[画像: Expanding brain meme. Small brain: git stores diffs against an empty folder. Bigger brain: git stores the entire files for every version. Galaxy brain: git stores diffs against an empty folder.]
おそらく名前が必要なobjgitのパックファイル形式
私が思いついた形式は、CDバックアップの.binおよび.cue形式から直接インスパイアされています。オブジェクトは、通常128Mi(奇妙に具体的な数値は、丸く見えたので選択されました)までの.binファイルに次々と格納され、そこに含まれるオブジェクトのメタデータは、バイナリエンコードされた.cueシートに別途格納されます。これらを組み合わせることで、TigrisでのGitリポジトリストレージはカラム型ストアになります。
FIG 05 objects.cue on the wire: one header, then fixed-width records
objects.cue HEADER 16 bytes, once at the top of the file┌──────────────┬─────────┬──────────┬────────────────────┐│ magic "OGCU" │ version │ rec_size │ record_count ││ 4 B │ u16 2 B │ u16 2 B │ u64 8 B │└──────────────┴─────────┴──────────┴────────────────────┘0 4 6 8 16objects.cue RECORD 58 bytes, repeated record_count times┌──────────────┬──────┬──────┬────────────┬────────────┬────────┬──────────────┐│ hash │ type │ comp │ bin_offset │ bin_length │ size │ delta_base ││ sha1 20 B │ u8 1B│ u8 1B│ u64 8 B │ u32 4 B │ u32 4B │ sha1 20 B │└──────────────┴──────┴──────┴────────────┴────────────┴────────┴──────────────┘0 20 21 22 30 34 38 58identity hash, delta_base location bin_offset, bin_length decode type, comp, size
私が大きく行ったことは、パックファイル内のオフセットとともに、オブジェクトの圧縮済みおよび非圧縮済みの両方のサイズを格納したことです。これにより、パックファイルがバックグラウンドでダウンロードされている間に、Tigrisから個々のオブジェクトをすくい取るための適切なHTTP Rangeリクエストを簡単に構築できます。
レイテンシの最適化として、Gitライブラリがパックファイルから任意のオブジェクトを要求するたびに、パックファイル全体がオブジェクトストレージから一時フォルダにダウンロードされます。パックファイルの「遠端」にあるものは、ダウンロードされたパックファイルが「追いつく」までTigrisからRangeリクエストされます。実際には、これはパックファイルが読み取るのに役立つタイミングでダウンロードされ、使用しているGitライブラリで避けられないもの以上のレイテンシが全体的にほとんどないことを意味します。「遠端からオブジェクトをすくい取る」問題が実際に問題となる場合は、リポジトリのプッシュまたはプルを開始するときに、最も最近のパックファイルをバックグラウンドでフェッチするようにします。
FIG 06 レーシング・ザ・ビーム:コンテナが2MiB進んでいる間に4つのRange GET
git wants four objects out of packs/019a7f3c..bin A B C D ▼ ▼ ▼ ▼ ┌────────────────────────────────────────────────┐ container │░░░░░░█████░░░░█████░░░░░░░░░█████░░░░░░█████░░░│ └────────────────────────────────────────────────┘ five requests go out at once: A 16 MiB │ ░░░░░ │ in flight B 40 MiB │ ░░░░░ │ in flight C 77 MiB │ ░░░░░ │ in flight D 107 MiB │ ░░░░░ │ in flight whole file │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ in flight four ranged GETs race the background download of the whole file. whatever the download reaches stops needing a GET.
01/07 128MiBのパックファイル;GitはそこからA、B、C、Dを求めています
注意
パックファイル形式の定義には、パックファイルを128Miに制限するものは何もありません。単にダウンロードに時間がかかりすぎるのを防ぐためにそうしています。理論的には、大きなバイナリブロブ(3Dモデル、完全に合法的なバックアップなど)をGitリポジトリに直接格納すると、128Miを超えるパックファイルが発生する可能性があります。これは、近い将来Git Large File Storageを実装することで解決する予定です。
しかし、現時点では、Gitリポジトリに大きなバイナリファイルを格納するワークフローがあり、それに対してobjgitを使用したい場合は、異なるアーキテクチャを検討してください。
面白い数字
Gitに関する最も驚くべきことの一つは、基本的にすべてのやり取りが高価であるということです。そのため、リポジトリのプッシュ/プルにかかる時間をベンチマークすることで、かなりの距離を稼ぐことができます。そのため、いくつかの興味深い特性を持つGitリポジトリと比較することにしました。
- objgit自体:https://github.com/tigrisdata/objgit、プロトタイピングのためのAI支援開発を備えた新しいリポジトリ。
- 私のe/x/perimentalモノレポ:https://github.com/Xe/x/、10年以上前から存在し、多くの実験を保持しているリポジトリ。
- Tigrisブログのリポジトリ:https://github.com/tigrisdata/tigris-blog、多くの画像やその他の圧縮不可能なオブジェクトが含まれているリポジトリ。
テストを実行した条件を以下に示します。
| Item | Value |
|---|---|
| Started | 2026-09-11T12:28:59-04:00 |
| Finished | 2026-09-11T13:06:52-04:00 |
| Host | Mac, 16 CPUs |
| Go | go1.26.5 |
| Git | git version 2.55.0 |
| Bucket | xe-objgit-devel |
| Build "old" | v1.0.2 |
| Build "fixed" | origin/main |
プッシュテスト
私が修正したかった大きな点は、オブジェクトストレージ呼び出しの数を減らすことでした。オブジェクトストレージ呼び出しが少ないほど、解決を待つレイテンシは少なくなります。それは驚くほど効果的でした。オブジェクトストレージの呼び出し回数は急落しました。
注意
この記事のすべてのチャートは対数スケールであり、よりきれいにレンダリングされ、緑色のバーが見えるようになっています。
FIG 07 フォーマット変更の前後のプッシュに対するS3リクエスト(対数スケール)
1 10 100 1,000 10,000├─────────┼─────────┼─────────┼─────────┤objgitold ████████████████████████ 231new █████████████ 18Xe/xold ████████████████████████████████████████ 9,236new ███████████████ 30tigris-blogold ███████████████████████████████████ 3,324new █████████████████████ 136██ old git packfiles behind a filesystem shim██ new .bin/.cue columnar packfiles
| Repo | Build | Wall | S3 requests | PUT | GET | HEAD | LIST | Keys | Bucket bytes |
|---|---|---|---|---|---|---|---|---|---|
| objgit | old | 8.7s (8.7s-15.9s) | 231 | 46 | 47 | 0 | 138 | 42 | 829.27 KiB |
| objgit | new | 2.2s (1.9s-2.5s) | 18 (17-20) | 5 | 10 (9-12) | 0 | 3 | 4 | 902.98 KiB |
| x | old | 3m29.4s (3m29.4s-3m40.6s) | 9,236 (9,236-9,304) | 1,087 | 2,170 | 0 | 5,979 (5,979-6,047) | 1,082 | 54.96 MiB |
| x | new | 14.3s (14.3s-14.7s) | 30 | 6 | 20 | 0 | 4 | 4 | 46.38 MiB |
| tigris-blog | old | 2m13.4s (1m29.1s-2m30.5s) | 3,324 (2,687-3,417) | 515 | 522 | 0 | 2,287 (1,650-2,380) | 511 | 354.67 MiB |
| tigris-blog | new | 26.5s (19.2s-27.4s) | 136 (113-146) | 9 | 123 (100-133) | 0 | 4 | 8 | 360.75 MiB |
プッシュの壁時計時間で最大の改善が見られました。
FIG 08 プッシュまでの壁時計時間、対数スケール、右側に速度向上
1s 10s 100s 1,000s├───────────┼───────────┼───────────┤objgitold ███████████ 8.7snew ████ 2.2s 4.0xXe/xold ████████████████████████████ 3m29.4snew ██████████████ 14.3s 14.6xtigris-blogold ██████████████████████████ 2m13.4snew █████████████████ 26.5s 5.0x██ old git packfiles behind a filesystem shim██ new .bin/.cue columnar packfiles
objgitが以前遅かった最大の要因の一つは、プッシュに予想以上に時間がかかっていたことです。Tigrisへのラウンドトリップを排除したことで、プッシュははるかに応答性が高くなりました。
クローンテスト
クローン時間への影響も確認したかったのです。プッシュと同様の利点がありました。
FIG 09 クローンに対するS3リクエスト、フォーマット変更の前と後(対数スケール)
1 10 100 1,000 10,000├─────────┼─────────┼─────────┼─────────┤objgitold █████████████████████████ 323new ████████████ 17Xe/xold ██████████████████████████████████████ 6,428new ████████████ 17tigris-blogold ████████████████████████████████████ 3,675new ██████████████████████ 158██ old git packfiles behind a filesystem shim██ new .bin/.cue columnar packfiles
| Repo | Build | Wall | S3 requests | GET | HEAD | LIST | Wire bytes |
|---|---|---|---|---|---|---|---|
| objgit | old | 11.8s (11.5s-19.5s) | 323 | 51 | 0 | 272 | 763.63 KiB |
| objgit | new | 2.6s (1.5s-5.7s) | 17 (16-17) | 13 (12-13) | 0 | 4 | 767.96 KiB |
| x | old | 3m23.5s (3m23.5s-3m33.6s) | 6,428 (6,428-6,780) | 1,091 | 0 | 5,337 (5,337-5,689) | 40.68 MiB (40.68 MiB-40.70 MiB) |
| x | new | 54.4s (54.4s-57.8s) | 17 (17-71) | 13 (13-67) | 0 | 4 | 42.22 MiB (42.22 MiB-42.36 MiB) |
| tigris-blog | old | 2m23.6s (2m19.4s-2m50.2s) | 3,675 (3,601-4,123) | 520 | 0 | 3,155 (3,081-3,603) | 350.94 MiB (350.93 MiB-351.07 MiB) |
| tigris-blog | new | 1m22s (1m20.8s-1m38.7s) | 158 (137-317) | 155 (134-314) | 0 | 3 | 349.04 MiB (348.00 MiB-352.67 MiB) |
FIG 10 クローンまでの壁時計時間、対数スケール、右側に速度向上
1s 10s 100s 1,000s├───────────┼───────────┼───────────┤objgitold █████████████ 11.8snew █████ 2.6s 4.5xXe/xold ████████████████████████████ 3m23.5snew █████████████████████ 54.4s 3.7xtigris-blogold ██████████████████████████ 2m23.6snew ███████████████████████ 1m22.0s 1.8x██ old git packfiles behind a filesystem shim██ new .bin/.cue columnar packfiles
ああ、イーサネットを備えたマシンでこれをテストすれば、時間的な数字はもっと良くなったでしょう。私はWi-Fiでコーポレートラップトップを使ってすべてのテストを行ったので、これは可能な限り最悪の条件の一つに意図的に置きました。
結論セクション
私はまだこれを積極的に取り組んでいます。まだ自分のプロジェクトに使用するほど自信はありませんし、あなたもまだこれを使いたくないと言われても責められません。まだ認証、認可、API(私の長文のSigV4認証投稿は実際にはobjgitの投稿になる予定でした!)、またはマシンが物理的に処理できる以上のレート制限を実装していません。もしあなたがこれを取り、実行し、インターネットに公開した場合、そのサーバーに接続できる人は誰でも好きなものをプルまたはプッシュできます。それはしないようにしてください。
Objgitのパックファイルも現在、無期限に蓄積されるため、小さなプッシュがたくさんある場合、バケットには小さなパックファイルがたくさん蓄積されます。それらを時々より大きなパックファイルに圧縮することを検討していますが、それは後でできることです。
少なくとも:Gitのパックファイル形式は、実際のファイルシステムにGitリポジトリを格納するという制約にとって優れた形式です。ネットワークラウンドトリップをミックスに入れた瞬間、すべてがうまくいかなくなります。
私はこれを続け、さらに学ぶにつれてこのようなレポートを公開していきます。これが興味深いものであったことを願っています!安全に。
名前を付ける前にパックファイルのコードを読む
objgitはTigrisにGitリポジトリを直接格納します。この記事の.bin/.cueコンテナ、カラム型インデックス、および4層の読み取りラダーはすべてリポジトリ内にあります。