Repository yang sehat belum tentap cepat. git gc bisa mengecilkan .git sampai 95%, namun keluhan yang paling sering terdengar bukan soal disk: git status yang makan lima detik di monorepo 60 ribu berkas, atau git log yang menit lamanya sebelum menampilkan apa pun. Kinerja punya sumber beratnya sendiri — dan obatnya tidak sama dengan obat kebersihan. Disiplin yang kita pakai sejak git gc tetap berlaku: ukur dulu, ubah satu hal, ukur ulang. Chapter ini membangun baseline nyata, membedah empat sumber berat, lalu memasang akselerator bawaan Git satu per satu sambil jujur membaca angkanya.

Baseline: Mengukur Sebelum Mengubah #

Repository latihan-perf meniru proyek yang tumbuh teratur: 1200 berkas kecil tersebar di 12 direktori src/modul-*, diimpor sekali lalu direvisi 80 kali:

bash
mkdir latihan-perf && cd latihan-perf
git init -b main
git config user.name "Budi Santoso"
git config user.email "[email protected]"
mkdir -p src
for d in $(seq 1 12); do
  mkdir "src/modul-$d"
  for i in $(seq 1 100); do
    printf "berkas %d modul %d\nnilai %d\n" "$i" "$d" $((i + d)) > "src/modul-$d/data-$i.txt"
  done
done
git add .
git commit -m "Impor 1200 berkas"
for c in $(seq 1 80); do
  d=$(( ((c - 1) % 12) + 1 ))
  base=$(( (c * 5) % 100 ))
  for k in 0 1 2 3 4; do
    i=$(( ((base + k) % 100) + 1 ))
    printf "berkas %d modul %d revisi %d\nnilai %d\n" "$i" "$d" "$c" $((i + d + c)) > "src/modul-$d/data-$i.txt"
  done
  git add -u
  git commit -m "Revisi $c"
done

Tiga pengukuran pembuka — jumlah commit beserta durasi penelusurannya, durasi git status, dan ukuran .git:

bash
time git log --oneline | wc -l

Output yang Dihasilkan:

text
81

real	0m0.004s
user	0m0.003s
sys	0m0.002s
bash
time git status

Output yang Dihasilkan:

text
On branch main
nothing to commit, working tree clean

real	0m0.004s
user	0m0.000s
sys	0m0.003s
bash
du -sh .git

Output yang Dihasilkan:

text
8.9M	.git

Angka time selalu berbeda antar mesin dan antar eksekusi — 81 commit itu hasil wc -l (satu impor ditambah 80 revisi). Yang menjadikan angka ini berguna bukan nilainya yang absolut, melainkan posisinya sebagai baseline: setiap perubahan yang kita pasang berikutnya dibandingkan ke sini.

Empat Sumber Berat #

Sebelum mengobati, kenali dulu penyakitnya. Empat sumber berat paling umum dan mekanisme yang membuatnya pelan:

Sumber beratMengapa terasa
Berkas besar dalam riwayatblob biner nyaris tak terdelta — setiap clone dan CI membayar unduhan yang sama; bedah dan solusinya di File Besar dan Git LFS
Ribuan berkas kecilgit status memanggil lstat untuk setiap berkas terlacak dan menelusuri setiap direktori untracked — biaya per berkas kecil, tetapi jumlahnya raksasa
Struktur direktori dalamsetiap jenjang direktori adalah objek tree tersendiri; penelusuran riwayat men-dekompresi banyak objek kecil beruntun
Object database tidak optimalpack menumpuk tanpa digabung atau delta jarang terbentuk — obatnya repack, dibahas di Packfiles dan git gc

Repository latihan kita mewakili dua baris tengah: banyak berkas kecil di direktori berjenjang. Dua akselerator berikutnya menyerang masing-masing sisi — penelusuran riwayat dan pemindaian working directory.

commit-graph: Cache Penelusuran Riwayat #

git log --graph --oneline harus membaca setiap commit untuk mengetahui parent dan tanggalnya — dan setiap commit adalah objek terkompresi zlib yang harus dibuka satu per satu. Berkas commit-graph meringkas metadata itu menjadi satu berkas biner: hash, parent, tanggal, dan generation number seluruh commit yang reachable. Ukur dulu kondisi tanpa commit-graph:

bash
time git log --graph --oneline > /dev/null

Output yang Dihasilkan:

text

real	0m0.003s
user	0m0.003s
sys	0m0.000s

Tulis berkasnya — perintah ini berjalan senyap tanpa keluaran:

bash
git commit-graph write --reachable
ls .git/objects/info/

Output yang Dihasilkan:

text
commit-graph

Berkas commit-graph tercipta di objects/info/ — berkas packs yang kita kenal dari git gc tidak ada karena repository ini belum pernah menjalankan gc. Ukur ulang penelusuran yang sama:

bash
time git log --graph --oneline > /dev/null

Output yang Dihasilkan:

text

real	0m0.004s
user	0m0.001s
sys	0m0.003s

Jujur saja: pada 81 commit tidak ada perbaikan yang terukur — selisih 0,003 dan 0,004 detik lebih kecil daripada derau pengukuran. Metadatanya memang cuma 81 baris; membukanya dari objek lepas maupun dari satu berkas sama murahnya. Efek commit-graph baru bersinar pada riwayat puluhan ribu hingga jutaan commit, ketika penghematan dekompresi menumpuk dan generation number memungkinkan penelusuran berhenti lebih awal tanpa mengunjungi seluruh cabang. Dua fakta pelengkap: git gc menulis berkas ini otomatis (gc.writeCommitGraph bawaan true), dan tugas commit-graph dari git maintenance menulisnya secara bertahap. Rincian format berkasnya ada di dokumentasi git-commit-graph .

Mempercepat git status: untrackedCache dan fsmonitor #

Sumber lambat git status berbeda sama sekali: bukan object database, melainkan pemindaian working directory — lstat seluruh berkas terlacak plus penelusuran direktori untracked. Kita uji dengan menimbuni repository 300 berkas draf yang tak terlacak:

bash
mkdir -p draf
for i in $(seq 1 300); do printf "draf %d\n" "$i" > "draf/catatan-$i.txt"; done
time git status

Output yang Dihasilkan:

text
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	draf/

nothing added to commit but untracked files present (use "git add" to track)

real	0m0.005s
user	0m0.002s
sys	0m0.003s

Lapisan obat pertama, untracked cache:

bash
git config core.untrackedCache true
time git status
time git status

Output yang Dihasilkan (perintah pertama):

text
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	draf/

nothing added to commit but untracked files present (use "git add" to track)

real	0m0.004s
user	0m0.002s
sys	0m0.003s

Output yang Dihasilkan (perintah kedua):

text
On branch main
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	draf/

nothing added to commit but untracked files present (use "git add" to track)

real	0m0.004s
user	0m0.002s
sys	0m0.003s

Untracked cache menyimpan ringkasan isi direktori untracked di dalam index, dengan waktu modifikasi direktori sebagai penanda keaslian: direktori yang tidak berubah tidak di-scan ulang. Status pertama mengisi cache, status kedua memetiknya. Pada 1500 berkas semuanya masih milidetik — penghematan besar baru terasa ketika working directory berisi puluhan ribu berkas dengan direktori draf yang jarang berubah.

Lapisan kedua menyerang biaya lstat itu sendiri. fsmonitor adalah daemon yang berlangganan event perubahan berkas dari sistem operasi — inotify di Linux, FSEvents di macOS — sehingga git status cukup bertanya “apa yang berubah?” alih-alih men-stat semuanya:

bash
git config core.fsmonitor true
git fsmonitor--daemon status

Output yang Dihasilkan:

text
fatal: fsmonitor--daemon not supported on this platform

Sandbox container ini tidak menyediakan fasilitas event yang dibutuhkan daemon, dan Git melaporkannya dengan jujur. Di Linux dan macOS yang normal, daemon hidup dengan sendirinya: begitu core.fsmonitor bernilai true, perintah seperti git status memanggil daemon dan menjalankannya bila belum ada — perilaku ini tercantum di dokumentasi git-fsmonitor–daemon . Meski daemonnya absen di sandbox, git status tetap berjalan dengan pemindaian biasa (0m0.004s) — fsmonitor adalah akselerator, bukan prasyarat. Kedua pengaturan kita kini tercatat di konfigurasi lokal:

ini
[core]
	repositoryformatversion = 0
	filemode = true
	bare = false
	logallrefupdates = true
	untrackedCache = true
	fsmonitor = true

Prefetch dan Partial Clone #

Dua obat sisanya untuk keluhan jaringan. Prefetch sudah kita jalankan di git maintenance : tugas terjadwal menarik objek terbaru dari remote ke refs/prefetch/ setiap jam, sehingga git fetch yang kita jalankan sendiri tinggal memutakhirkan remote-tracking branch tanpa transfer besar — berharga untuk remote sibuk atau koneksi lambat. Partial clone menyerang dari arah lain: git clone --filter=blob:none <url> mengunduh riwayat tanpa isi berkas, lalu blob diambil saat pertama kali dibutuhkan — ekstensi ini cocok untuk repository besar dan dibahas sebagai bagian dari strategi clone ringan di Shallow Clone .

Memetakan Gejala ke Obat #

GejalaObat pertamaRujukan
git clone dan CI makin lama, unduhan raksasakeluarkan berkas besar dari riwayat, Git LFSFile Besar
git status lambatcore.fsmonitor + core.untrackedCachechapter ini
git log / git diff lambatgit commit-graph write --reachablechapter ini
git fetch lambat tiap pagiprefetch terjadwalgit maintenance
Disk .git membengkak, objek lepas menumpukgit gc, maintenancegit gc
Working tree raksasa menghambat checkoutsparse-checkout, partial cloneSparse Checkout

Semua obat di atas untuk repository yang sehat namun lambat. Bagaimana dengan repository yang sudah terasa sakit — ukurannya aneh, operasinya mendadak pelan, dan kita tidak tahu mulai dari mana? Chapter penutup section ini merangkum kotak peralatannya: Diagnostik Repository .