Pertanyaan yang kita tinggalkan di akhir Index dan Cache masih menggantung: database objek menyimpan setiap versi setiap berkas, jadi mengapa repository tidak membengkak setelah ratusan commit? Jawabannya ada dua lapis: pertama, setiap versi berkas yang tidak berubah memakai ulang objek lama (hash isinya sama); kedua, objek-objek yang bertumpuk itu secara berkala dikemas menjadi satu berkas besar bernama packfile dengan kompresi delta. Chapter ini membedah isi pack — dari ribuan berkas kecil menjadi satu berkas, lalu kita buktikan bahwa satu objek berukuran puluhan ribu byte dapat tersimpan sebagai delta lebih kecil dari 1% ukuran aslinya.
Ribuan Berkas Kecil Itu Mahal #
Penyimpanan lepas (loose objects) yang kita lihat di Object Model
sederhana tetapi boros. Setiap objek menjadi satu berkas di .git/objects/xx/ yang masing-masing dikompresi zlib secara terpisah. Repository aktif dengan ribuan commit mudah menghasilkan puluhan ribu berkas kecil, dan sistem operasi harus membuka-menutup semuanya satu per satu setiap kali Git menelusuri riwayat — pada harddisk atau berkas jaringan, biaya per berkas inilah yang terasa. Dua versi berkas yang hampir identik juga dikompresi sendiri-sendiri, padahal selisihnya cuma beberapa baris.
Repository latihan-pack memperlihatkan keadaan sebelum dikemas: satu berkas katalog.txt berisi 1200 baris produk, lalu enam commit yang masing-masing mengganti satu baris.
mkdir latihan-pack && cd latihan-pack
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
for i in $(seq 1 1200); do
printf "produk-%04d kategori-%d harga-%d stok-%d\n" "$i" $((i % 7)) $((1000 + i * 3)) $((i % 13))
done > katalog.txt
git add katalog.txt
git commit -m "Impor katalog awal"
for i in 1 2 3 4 5 6; do
sed -i "${i}00c produk-REVISI-$i diskon-$i" katalog.txt
git add katalog.txt
git commit -m "Revisi baris $((i * 100))"
doneHasilnya 21 objek lepas — 7 commit, 7 tree, 7 blob — dan direktori pack masih kosong:
git count-objects -vOutput yang Dihasilkan:
count: 21
size: 140
in-pack: 0
packs: 0
size-pack: 0
prune-packable: 0
garbage: 0
size-garbage: 0Kolom count menghitung objek lepas dan in-pack menghitung objek yang sudah berada di dalam pack; size dan size-pack adalah total ukurannya (dalam kibibyte), sementara packs jumlah berkas pack. prune-packable dan garbage berhubungan dengan pembersihan yang kita bahat nanti di Garbage Collection
. Mari kita kemas:
git gc
ls .git/objects/pack/Output yang Dihasilkan:
pack-a20124523692868f582b569d09ebc652d24c7727.idx
pack-a20124523692868f582b569d09ebc652d24c7727.pack
pack-a20124523692868f582b569d09ebc652d24c7727.revNama berkas pack-<hash> dihasilkan dari isi pack, jadi nilainya berbeda di mesin pembaca. Cek ulang ukurannya:
git count-objects -v
du -sh .gitOutput yang Dihasilkan:
count: 0
size: 0
in-pack: 21
packs: 1
size-pack: 11
prune-packable: 0
garbage: 0
size-garbage: 0184K .gitSeluruh 21 objek lepas lenyap (count: 0) dan bergabung ke satu pack (in-pack: 21, packs: 1). Total isi objek mengecil dari 140 KiB menjadi 11 KiB; du melaporkan .git turun dari 372K ke 184K karena 21 berkas kecil kini menjadi tiga berkas rapat.
Bedah Kolom verify-pack -v #
Isi pack dibedah dengan git verify-pack -v — perintah ini juga memeriksa integritas setiap objek, jadi sekaligus alat diagnosa:
git verify-pack -v .git/objects/pack/*.idx | head -14Output yang Dihasilkan:
d5ec97e2923d59f89ed62af7c9e90b06034ebd07 commit 227 156 12
bd7304e07cd9e21b179438c1eba83ecd01eb5768 commit 227 157 168
b625df661d9d453df16b3bfa0a919e7b0b0305fd commit 227 157 325
3c86abf449fc4fca22571cf2f2368c08f5cb2214 commit 227 156 482
dbebe05b6922fd9faee31a76e7e1dc64b9503fda commit 227 155 638
2be666605e5158b3e740adae1f33483109a97693 commit 227 155 793
7df89e015256c5ff162a163452eea735d5451d34 commit 181 131 948
f34be23fe601e32c38135f3f19b01b523f1171fb blob 49476 7910 1079
0cb63c1e56db79a793fbbe4e0f8a71a0a95c6cbd blob 32 44 8989 1 f34be23fe601e32c38135f3f19b01b523f1171fb
9246f341abb8c21df5e0cc89b6e275574c31f3b2 blob 47 59 9033 2 0cb63c1e56db79a793fbbe4e0f8a71a0a95c6cbd
c7c23e4337874e2d689d464c1ca32df963ae0bd3 blob 32 43 9092 3 9246f341abb8c21df5e0cc89b6e275574c31f3b2
35268e8258a56d5f2ce637e72c724173b96ae15a blob 32 43 9135 4 c7c23e4337874e2d689d464c1ca32df963ae0bd3
7ab0e3b56136853f391c1e5558da4683a813fa98 blob 47 58 9178 5 35268e8258a56d5f2ce637e72c724173b96ae15aLima kolom pertama berlaku untuk semua objek; dua kolom terakhir hanya muncul pada objek delta:
| Kolom | Arti |
|---|---|
| hash | alamat objek (SHA-1), sama dengan yang dipakai refs dan index |
| type | commit, tree, blob, atau tag |
| size | ukuran data yang tersimpan — ukuran asli untuk objek penuh, ukuran delta untuk objek delta |
| size-in-pack | byte yang benar-benar dipakai di dalam berkas .pack |
| offset | posisi byte objek di dalam .pack |
| depth | panjang rantai delta (objek ini delta terhadap basis, yang mungkin juga delta) |
| base hash | hash objek basis yang dijadikan acuan delta |
Perhatikan baris ke-9: blob f34be23... menyimpan katalog.txt versi pertama — ukuran asli 49476 byte, terkompresi zlib menjadi 7910 byte di dalam pack. Enam baris setelahnya adalah keenam revisi berkas yang sama, dan di situlah keajaiban delta muncul.
Kompresi Delta: Satu Objek Jadi di Bawah 1% #
Blob 0cb63c1... adalah revisi pertama katalog.txt — versi berbeda satu baris dari versi awal. Git tidak menyimpan salinan 49 ribu byte itu lagi; Git menyimpan delta terhadap basis f34be23...: urutan instruksi “ganti baris 100 dengan teks baru”. Ukuran delta hanya 32 byte, dan setelah dikompresi menempati 44 byte di pack. Bandingkan dengan ukuran asli blob tersebut:
git cat-file -s 0cb63c1e56db79a793fbbe4e0f8a71a0a95c6cbdOutput yang Dihasilkan:
49460Berkas 49.460 byte tersimpan sebagai 44 byte di dalam pack — sekitar 0,09% dari ukuran aslinya. Revisi kedua menjadi delta terhadap revisi pertama (depth 2), ketiga terhadap kedua (depth 3), dan seterusnya hingga depth 6 — membentuk rantai delta yang panjang. Ringkasan rantainya terbaca dari verify-pack -s:
git verify-pack -s .git/objects/pack/pack-*.idxOutput yang Dihasilkan:
non delta: 15 objects
chain length = 1: 1 object
chain length = 2: 1 object
chain length = 3: 1 object
chain length = 4: 1 object
chain length = 5: 1 object
chain length = 6: 1 objectLima belas objek disimpan utuh (commit dan tree terlalu kecil dan berbeda isi sehingga delta tidak menguntungkan), sedangkan keenam blob katalog membentuk rantai bercabang dari basis yang sama. Batas kedalaman default rantai adalah 50 (pack.depth); melebihi itu Git memotong rantai dan menyimpan objek utuh agar pembacaan satu objek tidak perlu menelusuri rantai terlalu panjang. Membaca objek delta memang menuntut rekonstruksi — delta diterapkan berlapis dari basis — tetapi itu jauh lebih murah daripada membaca ribuan berkas kecil.
Berkas .idx: Indeks Hash ke Offset #
Pack terdiri dari pasangan berkas: .pack berisi deretan objek yang ditempel satu sama lain, dan .idx adalah indeksnya. Isi .idx bisa dilihat langsung dengan git show-index:
git show-index < .git/objects/pack/pack-*.idx | head -5Output yang Dihasilkan:
9329 0b47ae626570af318861b61c93f5b8d729477eb8 (4f841f20)
8989 0cb63c1e56db79a793fbbe4e0f8a71a0a95c6cbd (78433b35)
9379 156162a6346f70aa9443e238025ec7356cb98c40 (8c537a27)
9428 1708a037816391308cfd59432ae756a0cda4ac39 (c1cf5c8c)
9528 207571b6822ee4fb3edb8c06219856c64c79ca5b (daf79f5a)Setiap baris memetakan hash objek ke offset byte di .pack — misalnya objek 0cb63c1... (delta 44 byte kita) tinggal di offset 8989 — ditambah nilai CRC32 dalam kurung untuk pemeriksaan integritas. Berkat .idx, mencari objek di pack berlangsung cepat: hash diurutkan lalu dicari dengan binary search, tanpa membaca seluruh .pack. Format indeks yang dipakai Git modern adalah versi 2:
file .git/objects/pack/pack-*.idxOutput yang Dihasilkan:
.git/objects/pack/pack-a20124523692868f582b569d09ebc652d24c7727.idx: Git pack index, version 2Berkas ketiga, .rev, adalah indeks terbalik (offset ke objek) yang mempercepat operasi yang berjalan mengikuti urutan fisik berkas. Ketiganya berpasangan dan diperlakukan satu kesatuan oleh Git.
Inilah jawaban pertanyaan pembuka: repository tetap ramping karena objek serupa menyimpan selisihnya, bukan salinannya, dan karena semuanya menumpuk dalam sedikit berkas besar. Namun git gc tidak hanya mengemas — gc juga menghapus objek yang sudah tidak dirujuk siapa pun. Kapan sebuah objek dinyatakan layak buang, dan jaring pengaman apa yang menahannya lebih dulu? Aturan masa hidupnya dibedah di chapter berikutnya: Garbage Collection
. Bedah packfile dalam bahasa Inggris bisa dibaca di Pro Git — Packfiles
, dan rincian kolom keluaran di dokumentasi git-verify-pack
.