Storage ve Performans
Giriş — Git Nasıl Bu Kadar Hızlı?
Linux kernel'inin Git repository'si düşün: 1 milyondan fazla commit, 70.000'den fazla dosya, onlarca yıllık tarihçe. Tüm bu tarihçeyi birkaç gigabyte'ta saklıyor ve saniyeler içinde branch değiştirebiliyor. Git bunu nasıl başarıyor?
Cevap: akıllı depolama stratejileri. Git, nesneleri önce bağımsız dosyalar (loose objects) olarak saklar, sonra bunları verimli paketlere (packfiles) sıkıştırır. Delta compression ile benzer dosyaların sadece farklılıklarını kaydeder. Ve tüm bunları arka planda, senin fark etmeden yapar.
Bu derste Git'in depolama mekanizmasını, garbage collection'ı, performans optimizasyonlarını ve büyük repo'ları yönetme stratejilerini öğreneceksin.
Loose Objects — Gevşek Nesneler
Analoji — Masadaki Dağınık Kağıtlar
Çalışma masanda her notu ayrı bir kağıda yazıyorsun. Her kağıt kendi başına duruyor — kolayca eklenir, okunur, bulunur. Ama zamanla masa dolunca dağınık hale gelir. Aynı bilgiyi içeren kağıtlar bile ayrı ayrı yer kaplıyor.
Loose objects tam böyle çalışır: her Git nesnesi (blob, tree, commit) .git/objects/ dizininde ayrı bir dosya olarak saklanır.
# Loose object yapısı:
.git/objects/
├── ab/
│ └── c1234def5678ghi9012jkl3456mno78901 ← 1 nesne = 1 dosya
├── de/
│ └── f5678ghi9012jkl3456mno78901pqr1234
├── gh/
│ └── i9012jkl3456mno78901pqr1234stu5678
└── ...Loose Object Anatomisi
# Her loose object dosyası:
# 1. zlib ile sıkıştırılmış
# 2. Header: "<tür> <boyut>\0"
# 3. İçerik: nesnenin verisi
# Örnek: "Hello World\n" içerikli bir blob
# Sıkıştırılmamış hali:
# "blob 12\0Hello World\n"
# Bu hash'i üretir:
echo "Hello World" | git hash-object --stdin
# 557db03de997c86a4a028e1ebd3a1ceb225be238
# Dosya konumu:
ls .git/objects/55/7db03de997c86a4a028e1ebd3a1ceb225be238Loose Object'lerin Dezavantajları
1. Disk Alanı:
Her nesne ayrı dosya → filesystem overhead
Benzer dosyalar ayrı ayrı saklanır (deduplication yok)
Örnek: 1000 satırlık dosyanın 1 satırı değişti
→ Git 2 ayrı blob saklar (1000 + 1000 satır)
2. Performans:
Çok sayıda küçük dosya → filesystem yavaşlar
Özellikle Windows'ta ciddi performans sorunu
3. Transfer:
Her nesneyi ayrı ayrı göndermek → ağ verimsizliğiPackfiles — Paketlenmiş Nesneler
Analoji — Dağınık Kağıtları Dosyalama
Masandaki dağınık kağıtları alıp düzenli dosyalara yerleştiriyorsun. Aynı konudaki kağıtları birleştiriyorsun. Benzer kağıtlarda sadece farkları not ediyorsun. Sonuçta 100 kağıt yerine 3 düzenli dosya var — ve çok daha az yer kaplıyor.
Packfile Git'in bu dosyalama sistemidir. Birçok loose object'i tek bir dosyada paketler, delta compression ile sıkıştırır.
Packfile Yapısı
.git/objects/pack/
├── pack-abc1234.idx ← Index dosyası (nesnelerin konumları)
└── pack-abc1234.pack ← Pack dosyası (sıkıştırılmış nesneler)Pack dosyası içeriği:
┌───────────────────────────────────────────────┐
│ PACK header (sürüm, nesne sayısı) │
├───────────────────────────────────────────────┤
│ Object 1: blob (tam nesne, zlib compressed) │
├───────────────────────────────────────────────┤
│ Object 2: commit (tam nesne) │
├───────────────────────────────────────────────┤
│ Object 3: blob (delta — Object 1'den farkı) │ ← Delta!
├───────────────────────────────────────────────┤
│ Object 4: tree (tam nesne) │
├───────────────────────────────────────────────┤
│ ... │
├───────────────────────────────────────────────┤
│ SHA-1 checksum │
└───────────────────────────────────────────────┘
Index dosyası:
┌──────────────────────────────────────────────┐
│ Hash → Offset mapping │
│ abc1234... → byte 0 │
│ def5678... → byte 256 │
│ ghi9012... → byte 512 │
│ ... │
└──────────────────────────────────────────────┘Delta Compression — Farklılık Sıkıştırma
Delta compression, Git'in depolama verimliliğinin en büyük sırrıdır. Benzer nesneler arasındaki farklılıkları (delta) kaydeder:
Senaryo: 10.000 satırlık bir dosya, 1 satır değişti
Loose objects:
blob-v1: 10.000 satır (400 KB)
blob-v2: 10.000 satır (400 KB)
Toplam: 800 KB
Packfile (delta compression):
blob-v2: 10.000 satır (400 KB) ← Base (tam nesne)
blob-v1: "v2'ye göre farklar" (1 KB) ← Delta
Toplam: ~401 KB
Kazanç: %50 tasarruf!💡 İpucu: Git, delta compression'da daha yeni versiyonu base (tam nesne) olarak saklar, eski versiyonu delta olarak. Neden? Çünkü en son versiyona erişim daha sık olur — tam nesne olduğu için hızlı erişilir. Eski versiyonlar için delta'ları uygulamak gerekir ama bu nadiren yapılır.
Packfile'ı İnceleme
# Packfile istatistikleri
git count-objects -v
# count: 23 ← Loose object sayısı
# size: 92 ← Loose object toplam boyutu (KB)
# in-pack: 456 ← Pack'teki nesne sayısı
# packs: 1 ← Pack dosyası sayısı
# size-pack: 1024 ← Pack toplam boyutu (KB)
# prune-packable: 0 ← Pack'lenebilir loose object sayısı
# garbage: 0 ← Çöp nesne sayısı
# size-garbage: 0
# Pack içeriğini listele
git verify-pack -v .git/objects/pack/pack-*.idx | head -20
# abc1234 commit 234 156 12
# def5678 tree 512 234 168
# ghi9012 blob 10240 4096 402
# jkl3456 blob 128 96 4498 1 ghi9012 ← Delta! ghi9012'ye göreverify-pack çıktısı:
hash tür boyut sıkıştırılmış offset derinlik base
ghi9012 blob 10240 4096 402 ← Tam nesne
jkl3456 blob 128 96 4498 1 ghi9012 ← Deltagit gc — Garbage Collection
git gc (garbage collection), Git'in temizlik ve optimizasyon komutudur:
# Manuel gc
git gc
# Agresif gc (daha fazla optimizasyon, daha yavaş)
git gc --aggressive
# Sadece gereksiz nesneleri sil
git prune
# Ne yapacağını göster ama yapma
git gc --dry-rungit gc Ne Yapar?
1. PACK: Loose object'leri packfile'a sıkıştırır
2. PRUNE: Erişilemeyen nesneleri siler
3. REFS: Loose ref'leri packed-refs'e taşır
4. REFLOG: Eski reflog kayıtlarını temizler
5. RERERE: Eski rerere kayıtlarını temizlergit gc öncesi: git gc sonrası:
.git/objects/ .git/objects/
├── ab/abc123... (loose) ├── pack/
├── cd/cde456... (loose) │ ├── pack-xyz.idx
├── ef/efg789... (loose) │ └── pack-xyz.pack
├── ... (50+ dosya) └── info/
├── pack/ └── packs
│ └── (eski pack'ler)
└── info/ Sonuç: 50+ dosya → 2 dosya!Otomatik gc
Git, bazı işlemlerden sonra otomatik olarak gc çalıştırır:
# Otomatik gc ayarları
git config gc.auto
# 6700 (varsayılan: 6700 loose object'ten sonra otomatik gc)
git config gc.autoPackLimit
# 50 (varsayılan: 50 pack dosyasından sonra repack)
# Otomatik gc'yi kapat (önerilmez)
git config gc.auto 0💡 İpucu: Normal kullanımda
git gc'yi manuel çalıştırmana gerek yok. Git bunu otomatik yapar. Ama çok büyük repo'larda veya disk alanı sıkışıncagit gc --aggressiveyararlı olabilir.
Repack — Yeniden Paketleme
# Tüm nesneleri tek bir pack'e sıkıştır
git repack -a -d
# -a: Tüm nesneleri pack'e al
# -d: Eski, gereksiz pack dosyalarını sil
# Delta derinliğini ve pencere boyutunu ayarla
git repack -a -d --depth=50 --window=250
# depth: Delta zinciri maksimum derinliği
# window: Delta arama penceresi (büyük = daha iyi sıkıştırma, daha yavaş)Performans Ayarları
# pack.threads — Paralel sıkıştırma
git config --global pack.threads 4
# pack.windowMemory — Bellek limiti
git config --global pack.windowMemory 1g
# core.bigFileThreshold — Büyük dosya eşiği
git config --global core.bigFileThreshold 512m
# Bu eşiğin üzerindeki dosyalar delta compression'a alınmazTransfer Protocols — Veri Transferi
Git, repo'lar arasında veri transfer ederken (push/pull/clone) packfile kullanır:
git clone / fetch / pull:
1. Client ve server "neye ihtiyacım var?" müzakeresi yapar
2. Server, sadece client'ta olmayan nesneleri packfile'a paketler
3. Packfile client'a gönderilir
4. Client pack'i açar veya olduğu gibi saklar
git push:
1. Client, server'da olmayan nesneleri belirler
2. Bu nesneleri packfile'a paketler
3. Packfile server'a gönderilirClone transfer akışı:
Client Server
│ │
├── "Ben boşum, ne var?" ──────►│
│ │
│◄── "Şu ref'ler var:" ────────┤
│ main → abc1234 │
│ v1.0 → def5678 │
│ │
├── "Hepsini istiyorum" ───────►│
│ │
│◄── [PACKFILE: ~50MB] ────────┤
│ (tüm nesneler, delta │
│ compressed, tek dosya) │
│ │
├── Unpack & verify │
└── Done ✅ │Shallow Clone — Sığ Klonlama
Büyük repo'ların tüm tarihçesine ihtiyacın yoksa:
# Son N commit'i al
git clone --depth=1 https://github.com/torvalds/linux.git
# Sadece son commit! Tarihçe yok.
# Son 10 commit
git clone --depth=10 https://github.com/user/repo.git
# Daha sonra tarihçeyi derinleştir
git fetch --deepen=50 # 50 commit daha al
git fetch --unshallow # Tüm tarihçeyi alNormal clone:
Tüm tarihçe: A───B───C───D───E───F───G───H───I───J
Boyut: 500 MB
Süre: 5 dakika
Shallow clone (depth=1):
Sadece son commit: J
Boyut: 50 MB
Süre: 30 saniyePartial Clone — Kısmi Klonlama
Git 2.22+ ile daha akıllı klonlama:
# Blob'suz klonlama (dosya içerikleri lazım oldukça indirilir)
git clone --filter=blob:none https://github.com/user/repo.git
# Belirli boyutun üzerindeki blob'ları alma
git clone --filter=blob:limit=1m https://github.com/user/repo.git
# Tree'siz klonlama
git clone --filter=tree:0 https://github.com/user/repo.gitNormal clone: Tüm blob'lar + tüm tree'ler + tüm commit'ler
Blobless clone: Commit'ler + tree'ler (blob'lar lazım oldukça indirilir)
Treeless clone: Sadece commit'ler (tree ve blob lazım oldukça)
CI/CD'de sadece kodu build etmek istiyorsan: --filter=blob:none
Sadece tarihçeye bakmak istiyorsan: --filter=tree:0Sparse Checkout — Seçici Çalışma
Büyük bir monorepo'da sadece belirli dizinlere ihtiyacın varsa:
# Repo'yu sparse modda klonla
git clone --sparse https://github.com/big-company/monorepo.git
cd monorepo
# Sadece istediğin dizinleri seç
git sparse-checkout set src/frontend docs
# Şimdi working directory'de sadece:
# src/frontend/
# docs/
# (Diğer dizinler görünmez ama Git tarihçesinde var)
# Başka bir dizin ekle
git sparse-checkout add src/shared
# Sparse checkout'u kapat (tüm dosyaları getir)
git sparse-checkout disable
# Mevcut ayarları gör
git sparse-checkout listMonorepo yapısı (100+ dizin):
monorepo/
├── src/
│ ├── frontend/ ← İstediğim
│ ├── backend/
│ ├── mobile/
│ ├── shared/ ← İstediğim
│ └── ml/
├── docs/ ← İstediğim
├── tools/
└── scripts/
Sparse checkout sonrası working directory:
monorepo/
├── src/
│ ├── frontend/ ✅
│ └── shared/ ✅
└── docs/ ✅
Disk tasarrufu: %90+git maintenance — Arka Plan Bakımı
Git 2.31+ ile otomatik bakım:
# Otomatik bakımı etkinleştir
git maintenance start
# Bu, arka planda düzenli olarak:
# - gc çalıştırır
# - commit-graph günceller
# - prefetch yapar
# - loose-objects sıkıştırır
# - incremental-repack yapar
# Durumu gör
git maintenance run --task=gc
git maintenance run --task=commit-graph
# Durdur
git maintenance stopCommit Graph — Hızlı Tarihçe Gezinme
# Commit graph oluştur
git commit-graph write --reachable
# .git/objects/info/commit-graph dosyası oluşturulur
# Bu dosya, commit'ler arasındaki parent ilişkisini cache'ler
# git log, git merge-base gibi komutları 10-100x hızlandırır
# Otomatik güncelleme
git config --global fetch.writeCommitGraph trueBüyük Dosya Sorunları
Problem
# ❌ 100 MB'lık bir dosyayı commit'ledin
git add huge-dataset.csv
git commit -m "Add dataset"
# Sonra sildin
git rm huge-dataset.csv
git commit -m "Remove dataset"
# AMA! Dosya hâlâ tarihçede var!
# git clone yaptığında 100 MB indirilir
du -sh .git/
# 105 MB ← Dosyayı sildim ama Git hâlâ saklıyor!Çözüm: git filter-repo
# git filter-repo kur (pip ile)
pip install git-filter-repo
# Büyük dosyaları bul
git rev-list --objects --all | \
git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
sed -n 's/^blob //p' | \
sort -rnk2 | head -10
# Dosyayı tarihçeden tamamen sil
git filter-repo --path huge-dataset.csv --invert-paths
# Veya belirli boyutun üzerindeki tüm dosyaları sil
git filter-repo --strip-blobs-bigger-than 10M⚠️ Dikkat:
git filter-repotüm commit hash'lerini değiştirir (tarihçeyi yeniden yazar). Bu işlem sonrasında tüm ekip üyelerinin repo'yu yeniden klonlaması gerekir.git filter-brancheski yöntemdir,filter-repoçok daha hızlı ve güvenlidir.
Gelecekte Büyük Dosya Sorunu Yaşamamak İçin
# .gitignore'a büyük dosya uzantılarını ekle
echo "*.csv" >> .gitignore
echo "*.zip" >> .gitignore
echo "*.mp4" >> .gitignore
# Veya Git LFS kullan (ileri derslerde)
git lfs track "*.psd"
git lfs track "*.zip"Performans Sorunları ve Çözümleri
┌───────────────────────────────────────────────────────────┐
│ PERFORMANS SORUN GİDERME │
├──────────────────────┬────────────────────────────────────┤
│ Sorun │ Çözüm │
├──────────────────────┼────────────────────────────────────┤
│ git status yavaş │ git config core.untrackedCache true│
│ │ git config core.fsmonitor true │
│ │ (fsmonitor-watchman gerekir) │
├──────────────────────┼────────────────────────────────────┤
│ git log yavaş │ git commit-graph write │
│ │ git config fetch.writeCommitGraph │
├──────────────────────┼────────────────────────────────────┤
│ Clone yavaş │ git clone --depth=1 │
│ │ git clone --filter=blob:none │
│ │ git clone --sparse │
├──────────────────────┼────────────────────────────────────┤
│ Disk alanı çok │ git gc --aggressive │
│ │ git filter-repo (büyük dosyaları │
│ │ sil) │
│ │ git lfs (büyük dosyalar için) │
├──────────────────────┼────────────────────────────────────┤
│ fetch yavaş │ git config fetch.parallel 4 │
│ │ git config protocol.version 2 │
├──────────────────────┼────────────────────────────────────┤
│ Büyük monorepo │ Sparse checkout │
│ │ VFS for Git (Microsoft) │
│ │ Scalar (git maintenance wrapper) │
├──────────────────────┼────────────────────────────────────┤
│ Windows yavaş │ git config core.preloadIndex true │
│ │ git config core.fscache true │
└──────────────────────┴────────────────────────────────────┘Pratik: Depolama Analizi
# Proje oluştur
mkdir storage-demo && cd storage-demo
git init
# Birkaç commit yap
for i in $(seq 1 100); do
echo "Line $i" >> growing-file.txt
git add .
git commit -m "Add line $i"
done
# Depolama analizi
echo "=== gc öncesi ==="
git count-objects -v
# gc çalıştır
git gc
echo "=== gc sonrası ==="
git count-objects -v
# Pack içeriğini incele
git verify-pack -v .git/objects/pack/pack-*.idx | tail -5
# En büyük nesneleri bul
git verify-pack -v .git/objects/pack/pack-*.idx | \
sort -k 3 -n -r | head -5
# Repo boyutu
du -sh .git/Özet
Bu derste Git'in depolama mekanizmasını ve performans optimizasyonlarını öğrendik:
Loose objects her nesneyi ayrı dosya olarak saklar — basit ama çok sayıda nesne olunca verimsiz
Packfiles nesneleri tek dosyada paketler ve delta compression ile benzer nesnelerin sadece farklılıklarını kaydeder — dramatik boyut tasarrufu sağlar
`git gc` loose object'leri pack'ler, erişilemeyen nesneleri temizler ve referansları optimize eder — Git bunu otomatik yapar ama büyük repo'larda manuel çalıştırılabilir
Shallow clone (
--depth) ve partial clone (--filter) ile büyük repo'ları hızlıca klonlayabilirsinSparse checkout monorepo'larda sadece ihtiyacın olan dizinlerle çalışmanı sağlar
Büyük dosyaları tarihçeden temizlemek için `git filter-repo` kullan — ve gelecekte bu sorunu önlemek için
.gitignoreve Git LFS kullan
Bir sonraki derste Git'in otomasyon katmanını göreceğiz: Git Hooks — commit öncesi otomatik lint, mesaj formatı kontrolü ve daha fazlası.
AI Asistan
Sorularını yanıtlamaya hazır