- 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
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
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
“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
Rastgele ve zaman tabanlı UUID seçimi, milisaniye değil saniye düzeyinde performans farkı yaratabilir
Büyük veritabanlarında sharding ve dağıtım zorunlu hale gelir; bu yüzden UUID, auto-increment’ten daha iyi çalışabilir
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
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
Genel olarak shard edilmemiş veritabanlarında rastgele anahtarlar B-tree parçalanmasına yol açar
Ama range query çoksa rastgele anahtarlar dezavantajlıdır
Sonuçta seçim iş yükünün özelliklerine göre yapılmalı
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
Çü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
Bir miktar bilgi sızdırsa da, operasyonel olarak yeterince belirsizdir
Ö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
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
“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
Tamsayı PK’nin indeksleri belleğe daha iyi sığarken, UUIDv4 daha fazla sayfa erişimi gerektirdiği için latency arttı deniyor
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
Ö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
uuidv7()fonksiyonu geliyor ama eklentilerin daha fazla özellik sunup sunmadığı henüz net değil