10 puan yazan GN⁺ 2025-12-16 | 1 yorum | WhatsApp'ta paylaş
  • UUID v4, yüksek rastgelelik nedeniyle indeks verimsizliği ve aşırı I/O üretir; PostgreSQL'de birincil anahtar olarak kullanıldığında performans düşüşüne yol açar
  • Rastgele eklemeler nedeniyle sayfa bölünmesi (page split) ve indeks parçalanması sıklaşır, WAL günlük boyutu artar ve yazma gecikmesi oluşur
  • UUID, 16 baytlık boyutuyla bigint'in iki katı alan kaplar; bu da önbellek isabet oranının düşmesine ve bellek israfına yol açar
  • Güvenlik tanımlayıcısı sanılabilir, ancak RFC 4122'ye göre UUID, tahmini önlemeye yönelik bir güvenlik mekanizması değildir
  • Yeni veritabanlarında tamsayı dizi tabanlı anahtarlar kullanılması, kaçınılmazsa da zaman sıralı UUID v7 tercih edilmesi önerilir

UUID v4'ün performans sorunları

  • PostgreSQL'de UUID v4 birincil anahtarı kullanan veritabanları, son 10 yıldır tutarlı biçimde performans düşüşü ve aşırı I/O göstermiştir
    • UUID v4'te 122 bit rastgele üretildiği için indeks sıralaması mümkün değildir
    • Ekleme sırasında ardışık sayfalara yazılamadığından rastgele erişim oluşur; güncelleme ve silmede de verimsiz arama gerekir
  • B-Tree indeksleri sıralı veriyi varsayar, ancak UUID v4'te sıralılık olmadığından ekleme verimliliği düşüktür
    • Her ekleme rastgele bir sayfaya yazılır ve bu da ara sayfa bölünmelerinin sık yaşanmasına neden olur
    • Bunun sonucunda yazma gecikmesi (latency) ve WAL artışı meydana gelir

UUID'nin yapısı ve alternatifler

  • UUID, 128 bit (16 bayt) boyutunda bir tanımlayıcıdır ve PostgreSQL'de binary uuid türü olarak saklanır
  • UUID v4 rastgele bit tabanlıdır, UUID v7 ise ilk 48 bitte zaman damgası içerir; bu da indeks verimliliğini artırır
  • PostgreSQL 18'de (2025 planı) UUID v7 için yerleşik destek beklenmektedir
  • UUID v7, zaman sıralı düzenlemeye izin verdiği için sayfa yoğunluğu ve önbellek verimliliğini iyileştirir

UUID'nin seçilme nedenleri ve sınırları

  • Çoklu istemci veya mikroservis ortamlarında çakışmasız tanımlayıcı üretimi gerektiğinde UUID kullanılır
    • Örnek: Birden fazla veritabanı örneğinde aynı anda ID üretimi
  • Ancak RFC 4122, “UUID'lerin tahmin edilmesinin zor olduğu varsayılmamalıdır” der; bu yüzden güvenlik tanımlayıcısı olarak uygun değildir
  • Çakışma olasılığı 2.71×10¹⁸ üretimde %50'dir; pratikte çakışma ihtimali düşük olsa da performans maliyeti yüksektir

UUID'nin alan ve I/O verimsizliği

  • UUID, bigint (8 bayt) boyutunun iki katı, int (4 bayt) boyutunun dört katı yer kaplar
    • Büyük tablolar için bu durum depolama alanı artışına ve yedekleme/geri yükleme süresinin uzamasına yol açar
  • İndeks sayfa yoğunluğu deney sonuçları
    • integer indeksi: 97.64%
    • UUID v4 indeksi: 79.06%
    • UUID v7 indeksi: 90.09%
  • Cybertec testinde UUID v4 indeks aramasında 8,5 milyon ek sayfa erişimi ve %31229 I/O artışı gözlemlendi
    • Aynı koşullarda bigint indeksi 27.332 tampon erişimi, UUID v4 ise 8.562.960 tampon erişimi yaptı

Önbellek ve bellek etkisi

  • UUID, rastgele dağılımı nedeniyle tampon önbellek isabet oranını (cache hit ratio) düşürür
    • Önbelleğe daha fazla sayfa yüklenmesi gerekir ve gerekli sayfalar sık sık çıkarılır (eviction)
  • Önbellek verimliliğinin düşmesi sorgu gecikmesine yol açar ve bellek kullanımını artırır
  • Performansı korumak için düzenli indeks yeniden oluşturma (REINDEX CONCURRENTLY) veya pg_repack kullanılması önerilir

Performansı hafifletme yöntemleri

  • Belleği artırma: Veritabanı boyutunun 4 katı RAM önerilir (ör. DB 25GB → bellek 128GB)
  • work_mem ayarlama: Sıralama işlemlerinde daha fazla bellek ayırarak performans iyileştirilebilir
  • Rails ortamında, implicit_order_column ayarıyla UUID yerine created_at gibi sıralanabilir alanlar kullanılabilir
  • CLUSTER komutu ile tablo, sıralanabilir bir alana göre yeniden düzenlenebilir; ancak özel erişimli kilit gerekir

Tamsayı anahtarlar ve dizi önerisi

  • Yeni veritabanlarında tamsayı dizi tabanlı anahtarlar önerilir
    • integer (4 bayt) yaklaşık 2 milyar, bigint (8 bayt) ise çok daha fazla benzersiz değer sunar
  • Çoğu iş uygulaması için integer yeterlidir; büyük ölçekli servislerde bigint daha uygundur
  • UUID v4 yerine UUID v7 veya sequential_uuids uzantısını kullanmak daha gerçekçi bir alternatiftir

Özet

  • UUID v4, rastgelelik nedeniyle indeks verimsizliği, yüksek I/O ve düşük önbellek verimliliği yaratır
  • Güvenlik tanımlayıcısı olarak kullanılamaz ve alan israfı büyüktür
  • Tamsayı dizi anahtarları, çoğu uygulama için daha uygundur
  • UUID kullanmak kaçınılmazsa zaman sıralı UUID v7 seçilmelidir
  • PostgreSQL'de gen_random_uuid() değerini birincil anahtar olarak kullanmaktan kaçınılmalıdır

1 yorum

 
GN⁺ 2025-12-16
Hacker News yorumları
  • Bu, tipik bir erken optimizasyon örneği
    Kalıcı tanımlayıcılara veri koymak, veri yönetiminde kaçınılması gereken bir şeydir
    Norveç vatandaşlık numarasında doğum tarihinin kimliğe eklenmesi gibi durumlarda, sonradan doğum tarihi yanlış bilinen göçmenler ortaya çıkabilir ya da 1 Ocak doğumluların çokluğu nedeniyle numara yetmeyebilir
    Kart kataloglarının kullanıldığı eski dönemde arama maliyetini azaltmak için veriyle tanımlayıcıyı karıştırmak anlaşılabilir bir şeydi, ama bugün güçlü veritabanları varken buna pek gerek yok

    • Bence bu örnek aslında yanlış bir varsayılan değer seçimi sorunu
      Bilinmeyen doğum gününü 1 Ocak olarak atamak problemdi; tarihin anahtara konması başlı başına temel sorun değildi
      Eğer 00 ya da 99 gibi tarih olmayan değerler kullanılsaydı çakışma olmazdı
      UUID içine timestamp koymanın amacı anlam yüklemek değil, performans optimizasyonu yapmaktır
      Zamana göre artan anahtarlar, B-tree yeniden yazım maliyetini azaltarak DB ekleme performansını artırır
    • İtalya’daki vatandaşlık numarası da cinsiyeti içeriyor; bu da cinsiyet geçişi ameliyatından sonra sorun çıkarıyor
      “Kalıcı tanımlayıcılara veri koymayın” sözü genel bir ilkedir; ama duruma göre bu trade-off kabul edilebilir
      Örneğin md5 hash’ini UUID olarak kullanıp indeks kurarsanız parçalanma olur ama yönetilebilir düzeyde kalabilir
    • UUIDv7, gerçekte bilgi taşımaktan ziyade yalnızca zaman yanlılığı (random bias) olan bir üretim biçimi
      Rastgele ve zaman tabanlı UUID seçimi, milisaniye değil saniye düzeyinde performans farkı yaratabilir
    • Küçük veritabanlarında bu erken optimizasyondur; ama ölçek büyüdüğünde tam tersi yaklaşım gerekebilir
      Büyük veritabanlarında sharding ve dağıtım zorunlu hale gelir; bu yüzden UUID, auto-increment’ten daha iyi çalışabilir
    • Norveç vatandaşlık numarası örneği için, gerçekten 1 Ocak doğumluların o kadar fazla olup olamayacağı konusunda şüpheliyim
      Format DDMMYYXXXXX ise 100 bin kişiye kadar kapsayabilir; gerçekten bu kadar yığılma olur mu merak ediyorum
      Muhtemelen belirli bir yılda büyük bir mülteci akını gibi istisnai bir durumdur
  • UUID’ler güvenlik token’ı gibi kullanılmamalı
    Sırf tahmin edilmesi zor diye onları bir güvenlik özelliği olarak kullanmak risklidir
    Rastgeleliğin amacı yalnızca tahmini zorlaştırmak değil, ardışık ID’ler arasındaki ilişkiyi gizlemektir

  • DB türüne göre PK stratejisi tamamen değişir
    Postgres’te rastgele PK verimsiz olabilir; ama Cockroach ya da Spanner gibi dağıtık veritabanlarında tekdüze artan anahtarlar tam tersine hot shard sorununa yol açar

    • Dağıtık veritabanlarında da tamamen rastgele yerine artan eğilimli anahtarlar daha iyidir
      UUIDv7’de üst bitler sıralanabilir, alt bitler rastgeledir; böylece düğümler arası dağılım ile yerel depolama verimliliği aynı anda elde edilir
    • Buna DB türünden çok DB yapısı farkı olarak bakmak gerekir
      Genel olarak shard edilmemiş veritabanlarında rastgele anahtarlar B-tree parçalanmasına yol açar
    • Google Cloud Bigtable’da otomatik dağılım sağlamak için sıralı anahtarlar ters çevrilmiş (reverse) biçimde kullanılır
    • Shard edilmiş bir Postgres’te rastgele PK avantajlı olabilir
      Ama range query çoksa rastgele anahtarlar dezavantajlıdır
      Sonuçta seçim iş yükünün özelliklerine göre yapılmalı
    • Yazma ağırlıklı ve zaman yanlılığı yüksek iş yüklerinde, Postgres’te bile rastgele PK daha iyi olabilir
  • Yazı, UUIDv4’ü PK olarak kullanmanın dezavantajlarını iyi anlatıyor; ama önerilen tamsayı obfuscation yöntemi gerçek servisler için uygun görünmüyor
    Küçük ölçekli veritabanlarında UUIDv7 makul bir uzlaşma

    • Ben UUIDv7 yerine UUIDv4’ü tercih ederim
      Çünkü üretim zamanının açığa çıkmasını istemem
      Veri miktarı UUIDv4’ün rastgeleliği nedeniyle performans sorunu yaratacak kadar büyük değilse, v4 daha güvenli bir tercihtir
    • Postgres’te tek bir sequence kullanmayı seviyorum
      Bir miktar bilgi sızdırsa da, operasyonel olarak yeterince belirsizdir
    • Amaç sadece kullanıcı sayısını gizlemekse, auto-increment anahtara şifreleme/permutasyon uygulanabilir
      Örneğin AES-128 ile dönüştürüp ardından base64 ile encode ederek YouTube video ID’si gibi görünmesi sağlanabilir
  • Teknik durum tespiti sırasında birçok şirket görüyorum; hızlı sharding imkânı şirket büyümesinin anahtarlarından biri
    Tüm tablolarda UUID bulunursa, sharding sırasında yapıyı değiştirmeden ölçeklenmek mümkün olur
    Az miktardaki alan ve zaman kaybına kıyasla çok daha büyük bir ölçeklenebilirlik avantajı sağlar

    • UUIDv7, tekdüze artan yapısı sayesinde Postgres’te de performans avantajı sağlar
    • Biz de sharding sürecinde UUID olmadığı için zorlandık
      Sonunda asıl zorluk, UUID olup olmamasından çok karmaşık veri modelinin kendisiydi ve migration zaten zordu
  • Uygulamamızda tamsayı PK’leri şifreleyip UUID gibi gösteriyoruz
    Çünkü sıralı ID’ler görünür olursa müşteri sayısı tahmin edilebilir ve dictionary attack yapılabilir
    Şifrelenmiş ID’lerde çözme başarısız olduğunda tarama girişimleri hemen tespit edilebilir

    • Ama anahtar kaybı ya da anahtar değişimi durumunda çözülememe sorunu doğabilir
    • Anahtarların nasıl yönetildiği merak konusu — ortam değişkeniyle mi veriliyor, koda mı gömülüyor, AES-GCM gibi bir AEAD şeması mı kullanılıyor; güvenlik yönetimi burada önemli
  • “2 milyar yeterlidir” demek tehlikeli
    Her DBA’nın, böyle bir kararla başlayan en az bir kâbus hikâyesi vardır

  • Yazıda “rastgele değerler sıralama için verimsiz” denmiş ama aslında bayt sırasına göre sıralama mümkündür
    Yine de rastgele anahtarlar ardışık ekleme üretmediği için B-tree daha sık yeniden dengelenir ve performans düşer

    • UUIDv4 dağıtık ortamlarda faydalıdır; ama bunun bedeli olarak 128 bit alanı ve sırasızlığı kabul etmek gerekir
    • Yazarın daha sonra B-tree indeks karşılaştırma deneyi eklediği söyleniyor
      Tamsayı PK’nin indeksleri belleğe daha iyi sığarken, UUIDv4 daha fazla sayfa erişimi gerektirdiği için latency arttı deniyor
    • Bunun teknik açıdan yeterince güçlü bir temele dayanmadığını söyleyenler de vardı
    • B-tree’de anahtar ne kadar artan yapıdaysa ekleme o kadar verimlidir; rastgele anahtarlar ise cache friendliness açısından zayıftır
    • Veriye erişim, oluşturulma anıyla ne kadar yakından ilişkiliyse zaman sıralı düzen performans açısından o kadar avantajlı olur
  • Bu yazı, sorundan önce çözümün geldiği bir erken optimizasyon gibi görünüyor
    UUIDv4 çoğu durumda gayet yeterlidir
    Performans sorunları gerçekten ortaya çıktığında düşünülmelidir

    • Ama bir kez UUIDv4 ile başlarsanız, sonradan int64’e rekey yapmak neredeyse imkânsızdır
    • Gerçekten performans sorunu çıktığında ise sistem zaten büyüme aşamasında olur ve PK değiştirecek alan kalmaz
  • Özetle, Postgres’te UUIDv7, v4’e göre biraz daha iyi performans gösterir
    Yeni sürümlerde eklenti olmadan da UUIDv7 desteği mümkün

    • Ama yazının temel mesajı, mümkünse sequence ve tamsayı PK kullanılması yönünde
    • Postgres 18 ile birlikte yerleşik uuidv7() fonksiyonu geliyor ama eklentilerin daha fazla özellik sunup sunmadığı henüz net değil
    • Çoğu kullanıcı için artık ayrı bir eklenti gerekmeyecek