← Kursa Dön
📄 Text · 30 min

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/7db03de997c86a4a028e1ebd3a1ceb225be238

Loose 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ği

Packfiles — 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öre

verify-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  ← Delta

git 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-run

git 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ı temizler
git 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ışınca git gc --aggressive yararlı 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ınmaz

Transfer 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önderilir
Clone 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 al
Normal 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 saniye

Partial 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.git
Normal 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:0

Sparse 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 list
Monorepo 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 stop

Commit 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 true

Bü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-repo tü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-branch eski 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 klonlayabilirsin

  • Sparse 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 .gitignore ve 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ı.