UUID v1, v4 ve v7 Karşılaştırması: Bir Veritabanı Birincil Anahtarı Seçmek
Özet (TL;DR)
Dobra konuşayım: sıcak bir ekleme tablosunun birincil anahtarı olarak UUID v4 kullanmak bir ayak tabancasıdır. Kısa süre önce events.id varsayılanı gen_random_uuid() (bir v4) olan bir PostgreSQL 16 yükünü inceledim ve her INSERT, rastgele bir B-tree yaprağına iniyordu — shared_buffers’ı soğutuyor ve pgstattuple’ın bildirdiği gibi dizin parçalanmasını yüksek tek haneli yüzdelere çekiyordu. Sütun varsayılanını bir v7 üreticisine geçirmek — aynı 16 baytlık uuid türü, aynı dizin — ortalama INSERT gecikmesini kabaca üçte birine düşürdü ve parçalanma sayıları sabitlendi. UUID sürümü seçimi bir veritabanı tasarım kararıdır, bir kriptografi kararı değil. UUID, 8-4-4-4-12 onaltılık haneleri olarak yazılan 128 bitlik bir değerdir; dört bit sürüm, iki veya üç bit varyant olarak sabitlenir. Sürüm 1, zaman ve bir düğüm tanımlayıcısını (tarihsel olarak bir MAC adresi) bu 128 bite damgalar — kabaca sıralanabilir, ama ana bilgisayarı sızdırır. Sürüm 4, ayrılmamış bitleri rastgele verilerle doldurur: güçlü tahmin edilemezlik, ama sıralama yok; bu yüzden eklemeler rastgele dizin konumlarına iner. 2024’te RFC 9562’de resmileşen Sürüm 7, yüksek bitlere bir Unix milisaniye zaman damgası ve sonuna rastgele kuyruk koyar; v4’ün güvenliğini v1’in dizin yerelliğiyle birleştirir. Opak rastgeleliğin gerçekten önemli olduğu kamuya açık bir API için v4 hâlâ varsayılandır. Hemen her yerde v7 daha iyi bir birincil anahtardır. v1 eskidir — veritabanlarınca kabul edilir ama yeni bir seçim olmamalıdır.
Arka plan ve kavramlar
UUID 128 bittir. Geleneksel olarak, tirelerle gruplanmış 32 onaltılık hane olarak yazdırılır: xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx. M hanesi sürümü tutar (1, 4, 7 vb.) ve N’nin üst bitleri varyantı tutar. Geri kalan her şey sürüme özgüdür.
Sürüm 1, makineler ve zaman arasında benzersizlik için tasarlandı. 60 bitlik zaman damgası 1582-10-15’ten bu yana 100-nanosaniyelik aralıkları sayar, bir saat dizisi alanı saat geri alımlarını işler ve 48 bitlik bir düğüm kimliği başlangıçta ağ MAC adresiydi. Gizlilik sonucu doğrudandır: dizüstünüzde üretilen bir v1 UUID, dizüstünüzün MAC’ini kodlar ve tek satırlık uuid -d bunu tersine çevirir. Modern kitaplıklar bazen sızıntıyı önlemek için düğüm kimliğini rastgeleleştirir, ama birçoğu hâlâ orijinal kuralı izler.
Sürüm 4, sürüm ve varyant için sabitlenmiş 6 bit dışında 122 bit rastgeleliktir. Güçlü bir RNG varsayar (tarayıcının crypto.randomUUID()’si veya PostgreSQL’in gen_random_uuid()’si). Çakışmalar gerçekçi hiçbir ölçekte etkili biçimde mümkün değildir — doğum günü sınırı, yaklaşık bir milyar v4 ürettikten sonra kabaca trilyonda bir olasılık verir. Dezavantajı, art arda gelen v4’lerin ilintisiz olmasıdır; dolayısıyla onları bir dizine eklemek rastgele sayfalara dokunur, önbellek yerelliğini ve yazma büyütmesini (Postgres’te WAL artı tam sayfa yazmaları) vurur.
Sürüm 7, 2024’te yayımlanan RFC 9562’nin parçasıdır; RFC 4122’yi geçersiz kılmış ve sürümler 6, 7 ve 8’i eklemiştir. Bir v7 UUID yüksek bitlerinde 48 bitlik Unix milisaniye zaman damgası saklar, ardından sürüm etiketi, küçük bir rand_a alanı, varyant etiketi ve 62 bitlik bir rand_b kuyruğu gelir. Pratik etki şudur: aynı milisaniye içinde üretilen v7 değerleri sıralama düzeninde komşudur ve milisaniyeler arasındaki değerler kronolojik olarak sıralanır. Rastgele kuyruk, bir milisaniye içindeki benzersizlik için hâlâ fazlasıyla entropi sağlar. PostgreSQL 18 yerel bir uuidv7() ile gelir; daha eski sürümlerde pg_uuidv7 uzantısını kullanabilir veya uygulama katmanında üretebilirsiniz (Node uuid 9.x, Python uuid6).
Varyant bitleri önemlidir çünkü RFC 9562 / 4122 ailesini eski Microsoft ve Apollo UUID’lerinden ayırır. Bu rehber için RFC varyantını varsayın (N grubunun ilk onaltılık hanesi 8, 9, a veya b olur).
Depolama biçimi ayrı bir konudur. PostgreSQL 16’nın yerel uuid türü değeri 16 bayt olarak saklar; MySQL tipik olarak BINARY(16) veya CHAR(36) kullanır — dize biçimi depolamayı ikiye katlar ve karşılaştırmaları bayt bazlı yerine karakter bazlı yapar. Sürüm seçimi ile depolama biçimi seçimi birbirini etkiler: v7’yi ikili değer olarak sıralamak ucuz ve doğrudur, hex dize olarak sıralamak doğrudur ama daha yavaştır ve v4’ü sıralamak depolama ne olursa olsun anlamsızdır.
Karşılaştırma ve veriler
| Özellik | v1 | v4 | v7 |
|---|---|---|---|
| Üretim girdisi | Zaman damgası + saat dizisi + düğüm kimliği | 122 rastgele bit | 48 bitlik Unix ms zaman damgası + rastgele kuyruk |
| Gizlilik | Düğüm kimliğini sızdırır (genellikle MAC) | Ana bilgisayar veya zaman bilgisi yok | ms olarak oluşturma zamanını sızdırır, ana bilgisayar yok |
| Zamana göre sıralanabilir | Evet (ama yeniden düzenleme olmadan bayt düzeni ≠ zaman düzeni) | Hayır | Evet — sözlüksel düzen kronolojik düzene uyar |
| Dizin yerelliği | Orta | Zayıf (B-tree boyunca rastgele eklemeler) | İyi (yaklaşık tekdüze artan) |
| Tipik kullanım | Eski sistemler, bazı COM/Windows kimlikleri | Kamuya açık API kimlikleri, oturum token’ları, tuzlar | Olay günlükleri, yüksek hacimli eklemeler, zamana göre sayfalı tablolar |
| Entropi | Düşük (bitlerin çoğu zaman/düğüm) | Yüksek (yaklaşık 122 bit) | Yüksek kuyruk (yaklaşık 74 bit), ms içinde düşük çakışma |
Kabaca bir zihinsel model: v4, dizin davranışı pahasına tahmin edilemezliği en üst düzeye çıkarır; v7, çoğu uygulama için yeterince tahmin edilemezliği korurken veritabanlarının sevdiği sona-ekleme desenini geri getirir; v1 ise tanınmaya değer ama seçilmemesi gereken tarihi bir yapıdır.
Gerçek senaryolar
Senaryo 1 — Ekleme ağırlıklı olay günlüğü. Bu tam olarak açılış anısındaki yüktür. Günde milyonlarca satır alan ve genellikle “son 24 saat, zamana göre sıralı” olarak sorgulanan bir tablo, v7’den doğrudan yararlanır. Yeni satırlar birincil anahtar dizininin sonuna iner, böylece sıcak sayfalar sıcak kalır ve zaman aralıkları üzerindeki aralık taramaları bitişik dizin segmentlerine eşlenir. v4’ten v7’ye geçiş, burada genellikle herhangi bir sorgu kodunu değiştirmeden yazma gecikmesini ve dizin parçalanmasını azaltır — tipik olarak tek satırlık bir sütun varsayılanı değişikliği.
Senaryo 2 — Kamuya açık kullanıcı kimlikleri. /orders/{id} gibi paylaşım bağlantılarının tahmin edilemez olması gerekir, böylece ziyaretçiler diğer kullanıcıların siparişlerini sayamaz. v4 güvenli varsayılandır. v7’nin faydalarını da istiyorsanız, bir v7’nin oluşturma zaman damgasını milisaniyeye kadar açığa çıkardığını unutmayın; bu siparişler için uygun olabilir ama daha hassas bağlamlarda iş sinyallerini (örneğin dakika başı tam ödeme hacmi) sızdırabilir. Ekiplere önerdiğim uzlaşma ikili kimlik desenidir: birincil anahtar için dahili v7 ve dış dünyaya açık ayrı bir v4 veya kısa rastgele slug. Zamanlamayı dışarıya sızdırmadan dizin davranışını korursunuz.
Senaryo 3 — Çok bölgeli veya shard’lı sistemler. v7’nin zaman damgası öneki, aynı milisaniyede UUID üreten iki bölgenin zamana göre temiz biçimde iç içe geçeceği anlamına gelir; ama bir milisaniye içinde bölgeler arası sıralama garantisi yoktur. Daha katı bir bölgeler arası sıralama gerekirse, ULID (Crockford Base32 ile kodlanmış 48 bitlik zaman damgası + 80 bitlik rastgelelik) neredeyse aynı özelliklere ve daha kompakt 26 karakterlik metinsel biçime sahiptir. Snowflake tarzı kimlikler daha ileri giderek düğüm başına sıralama için açık bir makine kimliği dahil eder (orijinal Twitter tasarımı ve Discord’un varyantı her ikisi de 64 bittir), o kimlikleri tahsis etmek için koordinasyon gerektirme bedelini öder.
Yaygın yanlış anlamalar
“UUID’ler veritabanlarında her zaman yavaştır.” Ham olarak 4 baytlık bir int’ten daha yavaştır, ama gerçek maliyet B-tree dizinlerindeki rastgele-ekleme parçalanmasından gelir ve v7 bunu büyük ölçüde ortadan kaldırır. UUID’leri 36 karakterlik bir dize yerine 16 bayt olarak saklamak dizin boyutunu yarıya indirir ve karşılaştırmaları hızlandırır. Birçok “UUID’ler yavaştır” karşılaştırması aslında “MySQL’de CHAR(36) olarak saklanan v4 yavaştır” karşılaştırmasıdır.
“v4 tek güvenli sürümdür.” v7’nin rastgele kuyruğu hâlâ büyük bir entropi havuzudur ve çoğu uygulama için — oturum referansları, API kimlikleri — bir saldırganın onları sayıklayamayacağı kadar tahmin edilemezdir. Tahmin edilebilirliğin önemli olduğu yer zaman damgası önekidir: v7, satırın ne zaman oluşturulduğunu açığa çıkarır. Bu kabul edilebilirse (genellikle öyledir), v7 dış kimlikler için bile makul bir seçimdir.
“UUID’ler dize olarak saklanmalıdır.” Dize biçimi 36 karakterdir (32 hex artı 4 tire), ikili biçim 16 bayttır. İkili daha kompakttır ve bayt olarak doğru sıralanır; bu, tekdüze olmayan bayt düzenine sahip v1 ve bayt düzeninin zamanla hizalanması gereken v7 için önemlidir. PostgreSQL’in uuid türü zaten değeri 16 bayt olarak saklar, bu yüzden orada ekstra düşünecek bir şey yoktur.
“v1’in MAC sızıntısı önemli değil çünkü kimse bakmıyor.” Bir v1 UUID’deki MAC bilinen bir tersine çevirmedir — uuid -d ve herhangi bir adli tıp aracı onu çıkarır. UUID’leriniz URL’lerde, destek biletlerinde veya üçüncü taraflarla paylaşılan günlük dökümlerinde görünüyorsa, bu gerçek bir bilgi ifşasıdır.
Kontrol listesi
- Bu UUID sık eklemeler alan bir veritabanı dizininde görünecek mi? v7’yi tercih edin. Yalnızca oluşturma zamanının tahmin edilemezliği gerçekten gerekirse v4’e düşün.
- UUID son kullanıcılar veya ortaklar tarafından görülebilir mi? Her iki sürüm de çalışır; v7’nin zaman damgası sızıntısının bağlam için kabul edilebilir olduğunu onaylayın.
- Postgres mi kullanıyorsunuz?
uuid(16 bayt) olarak saklayın. MySQL’de, dize uyumluluğu boyut maliyetinden ağır basmıyorsaBINARY(16)kullanın. - Birden çok üretici arasında sıralama garantisi gerekiyor mu? v7 tek başına yeterli değildir; aynı zaman önekli ULID’i veya açık makine kimlikli bir Snowflake tarzı kimliği değerlendirin.
- v1 hâlâ kod tabanında mı? MAC sızıntısını belgeleyin ve şema izin verdiğinde bir geçiş planlayın.
- İstemci tarafında UUID üretiyor musunuz? Kriptografik olarak güçlü bir RNG çağıran bir kitaplık kullanın (v4 için modern tarayıcılarda
crypto.randomUUID(); v7 kitaplıkları tipik olarak aynı RNG’yi sarar).
İlgili araç
Patrache Studio UUID üreticisi v4 ve v7 UUID’leri yerel olarak üretir, böylece üretilen değerler üçüncü taraf bir hizmete asla günlüklenmez. UUID’ler neredeyse her zaman JSON yüklerinin içinde yol alır — JSON Biçimlendirme, Doğrulama ve Şema: Pratikte bu kimliklerin servisler arasında hareket ederken doğru tiple kalmasını sağlayan şema desenlerini kapsar. Kompakt bir metinsel biçime ihtiyacınız olduğunda — örneğin 16 baytlık bir UUID’den türetilmiş 22 karakterlik kısa bir kimlik — Base64 ve URL Kodlaması: Amaç, Tuzaklar, Doğru Kullanım kurallarının standart Base64 yerine neden Base64URL olduğunu açıklar.
Kaynaklar
- IETF RFC 9562, “Universally Unique IDentifiers (UUIDs)” — https://datatracker.ietf.org/doc/html/rfc9562
- IETF RFC 4122, “A Universally Unique IDentifier (UUID) URN Namespace” (RFC 9562 tarafından geçersiz kılındı) — https://datatracker.ietf.org/doc/html/rfc4122
- PostgreSQL belgeleri, “UUID Type” — https://www.postgresql.org/docs/current/datatype-uuid.html
- ULID şartnamesi — https://github.com/ulid/spec