SQLite'ta UUID birincil anahtarlarının tehlikeleri
(andersmurphy.com)- SQLite'ta birincil anahtar uygulaması, normal rowid tabloları ile WITHOUT ROWID tablolarında fiziksel depolama sırasını farklı biçimde oluşturur; rastgele UUID4 kümelenmiş indeks olarak kullanıldığında B-tree yeniden dengelemesi ve ek sayfalama maliyeti ortaya çıkar
- Tamsayı rowid taban çizgisi, 1 milyon satırlık ekleme birimlerinde kabaca saniyede 1 milyon ekleme düzeyindeyken, UUID4 WITHOUT ROWID ekleme süresinde 14-16 kat daha yavaş sonuç verdi
- UUID4'ün sırasız yapısı, satırların B-tree'ye rastgele eklenmesine yol açar ve profil sonuçlarında ağaç dengeleme ile okuma-yazmaya daha fazla zaman harcandığı görülür
- UUID7 WITHOUT ROWID, zaman sıralı UUID olarak UUID4'ün sıralama sorununu azaltarak daha makul ekleme süreleri gösterdi; ancak 16 baytlık BLOB anahtar kullandığı için 8 baytlık tamsayı anahtardan hâlâ daha yavaş
- UUID4 WITH ROWID, gizli rowid'nin sıralı yapısından yararlansa da iki indeksin getirdiği yazma amplifikasyonu ve rastgele indeks ekleme maliyeti nedeniyle UUID7 WITHOUT ROWID'den daha düşük performans gösterir
Kümelenmiş indeks nedir?
- Kümelenmiş indeks, tablo satırlarının fiziksel depolama sırasını belirleyen indekstir
- Satırlar fiziksel olarak yalnızca tek bir şekilde sıralanabildiğinden, her tabloda yalnızca bir kümelenmiş indeks bulunabilir
- Kümelenmiş indeks tablonun kendisidir; kümelenmemiş indeks ise yalnızca indekslenen sütunları ve gerçek satır verisinin bulunduğu yeri gösteren işaretçileri depolar
Rowid
- Tüm normal SQLite tabloları,
rowidadlı örtük bir 64 bit tamsayı birincil anahtara sahiptir - Tablo verisi, her satır için bir giriş içeren B-tree yapısında saklanır ve anahtar olarak
rowiddeğeri kullanılır rowid, fiilen SQLite'ın kümelenmiş indeksidir ve satırların fiziksel depolama sırasırowidsırasıdır
WITHOUT ROWID
- SQLite, örtük
rowidiçermeyenWITHOUT ROWIDtablolarını destekler WITHOUT ROWIDtablolarında, tanımlanan birincil anahtar kümelenmiş indeks görevi görür- SQLite
rowidtabloları, tüm içeriğin yapraklarda saklandığı B*-Tree olarak uygulanırken,WITHOUT ROWIDtabloları hem yaprak hem de ara düğümlerde içerik saklayan normal bir B-Tree kullanır
Taban çizgi: rowid tamsayı birincil anahtarı
- Taban çizgi,
id INTEGER PRIMARY KEY, data BLOByapısına sahip normal bir rowid tablosunda 1 milyon satırlık ekleme sürelerinin ölçülmesiyle oluşturuldu - Sonuç tablosundaki toplam satır sayısı 10 milyon satırdan 100 milyon satıra kadar çıkıyor ve ölçüm süreleri 692ms ile 838ms arasında değişiyor
- Taban çizgi performansı kabaca saniyede 1 milyon ekleme düzeyinde
UUID4 WITHOUT ROWID
- UUID4 testi,
id BLOB PRIMARY KEY, data BLOByapısındaki birWITHOUT ROWIDtablosunda, birincil anahtar olarakrandom-uuid4-bytesdeğerleri eklenerek yapıldı - Sonuç tablosunda ölçüm süresi 10 milyon satırda 2649ms, 100 milyon satırda 12586ms olarak ölçüldü
- Ekleme performansı, tamsayı rowid taban çizgisine göre 14-16 kat daha yavaş seviyede
Profil
- Normalize edilmiş diffgraph, INTEGER ve UUID4 profil anlık görüntülerini karşılaştırır ve farkları flamegraph yapısında gösterir
- Normalize görünüm, iki profilin toplam örnek sayısını eşitleyerek göreli farkları yüzde olarak görmeyi sağlar
- Mavi çerçeveler, ikinci profil olan UUID4'te ilgili fonksiyon süresinin INTEGER'a göre azaldığını; kırmızı çerçeveler ise UUID4'te arttığını gösterir
- Renk yoğunluğu, ilgili çerçevenin kendi örnek sayısındaki değişimi, yani self time delta'daki göreli değişimi ifade eder
- Diffgraph'ta ağaç yeniden dengeleme ile okuma-yazmaya daha fazla zaman harcandığı görülür
- UUID4'ün sırasız yapısı nedeniyle anahtarlar rastgele sırada yerleşir ve SQLite B-tree'yi sürekli yeniden dengeler
UUID7 WITHOUT ROWID
- UUID7, zaman sıralı bir UUID'dir ve UUID4'ün sıralama sorununu ortadan kaldırabilen bir yaklaşımdır
- UUID7 testi de
id BLOB PRIMARY KEY, data BLOByapısındaki birWITHOUT ROWIDtablosunda çalıştırıldı - Sonuç tablosunda ölçüm süresi 10 milyon satırda 1372ms, 100 milyon satırda 1258ms olarak verildi
- UUID7 WITHOUT ROWID, UUID4 WITHOUT ROWID'ye göre daha makul değerlere geri döndü; ancak taban çizgiden hâlâ daha yavaş kaldı
- UUID BLOB birincil anahtarı 16 baytken, tamsayı birincil anahtar 8 bayttır
UUID4 WITH ROWID
- UUID4 WITH ROWID testi,
WITHOUT ROWIDolmadanid BLOB PRIMARY KEY, data BLOBtablosu kullanılarak yapıldı - Bu yapılandırmada kümelenmiş indeks gizli
rowid'dir verowid'nin avantajı sıralı olmasıdır - Dezavantaj ise tabloda iki indeks oluşması ve bunun getirdiği yazma amplifikasyonudur
- Sonuç tablosunda ölçüm süresi 10 milyon satırda 2003ms, 100 milyon satırda 7119ms olarak ölçüldü
- UUID4 WITH ROWID, UUID7 WITHOUT ROWID kadar iyi performans göstermedi; çünkü kümelenmiş indeks olmasa bile indeksin rastgele eklemelerle sürekli kurulması gerekiyor
Sonuç
- SQLite'ta UUID birincil anahtarları, kümelenmiş indeks ve anahtarın sıralanabilirliği nedeniyle ekleme performansını ciddi biçimde etkileyebilen bir tercih olabilir
- Rastgele UUID sorunu yalnızca SQLite'a özgü değil; kümelenmiş indeks kullanan diğer veritabanlarına da uzanan bir sorundur
- Tüm benchmark kodu GitHub deposunda açık olarak yayımlandı
1 yorum
Lobste.rs görüşleri
Güzel. rowid tablolarda tamsayı anahtarın tekdüze artan değil de rastgele olduğu durumdaki sayıları da görmek ilginç olurdu
Yazıda atlanan önemli nokta, rowid tabloların birincil indeksinin B+ ağacı,
without rowidtabloların ise B ağacı olmasıBu yüzden ortalama kayıt boyutu belli bir eşiği geçtiğinde ikincisi genelde ideal olmuyor. Çünkü indeksin iç düğümleri tüm kaydı saklıyor; diye hatırlıyorum, SQLite kılavuzu da sayfa boyutunun 1/20’sini pratik kural olarak veriyor
UUID performansını ölçmek için bu kadar emek harcanmış ama doğal anahtarlar hiç düşünülmemiş
İster tamsayı, ister UUID, ister başka bir biçim olsun, surrogate key karmaşıklık ekler, bilgi eklemez ve normalizasyon hatalarını gizler
Yazar, UUID kullanma nedeni olarak “yinelenmeleri önleme”yi gösteriyor ama bu bir gerekçe değil. Her veritabanındaki her tablodaki her satırın, o satırı benzersiz biçimde tanımlayan sütunlar kümesinden oluşan en az bir anahtarı olmalı
Böyle bir anahtarı olmayan veritabanı yinelenen bilgi barındırır ve mantıksal tutarsızlıklara açıktır. Böyle bir anahtarın zaten bulunduğu veritabanında surrogate key gerekmez. Doğal anahtar zaten varsa ve zorunlu kılınıyorsa, “performans için gerekli” iddiası ek maliyet ve gereksiz karmaşıklık anlamına geldiğinden kanıtlanmalıdır
Benzersiz görünen değerler beklendiği kadar benzersiz çıkmayabiliyor ya da değişmez sandığınız bir değerin sonunda değişmesi gerekebiliyor
Buna karşılık surrogate key kullandığınızda, başkasının kimliği nasıl tanımladığına — ya da çoğu zaman düzgün tanımlamadığına — bağlı kalmadan kendi sisteminiz içinde kimliği tanımlayabiliyorsunuz
İstisnalar var. Tüm modeli ben tanımlıyorsam ve veri sözde gerçek dünyadan gelmiyorsa doğal anahtar daha anlamlı olabilir. Ama muhtemelen hiçbir zaman tam normalize edilmemiş gerçek dünya verilerini içeren bir şema tasarlarken surrogate key çoğu zaman işe yarar
Herhangi bir satıra tamsayıyla başvurabilmek erişimi büyük ölçüde basitleştiriyor; insanların aklında tutması ve sorgularda kullanması da kolay
Tekdüze artıyorsa kendi başına bilgi de taşıyor. Yinelenen bilgi olsa da bu yine de doğru
Arama performansı da optimize oluyor. B ağacı, bitmap vb. ile indekslemek için neredeyse ideal durum bu
İnsanların UUID’yi çoğunlukla kafa karışıklığından kullandığını düşünüyorum. Genelde mantık, anahtarı gizlemek ve tahmin edilemez kılmak oluyor; ama bunun için ayrı bir tanımlayıcı yerine neden bunu birincil anahtara kadar dayatmak gerektiğini anlamaktan vazgeçtim
UUID sürüm 7’de başta 48 bitlik bir zaman damgası var, bu yüzden bu şekilde rastgele dağılmıyor. Dolayısıyla aşırı sayfalama ve yeniden dengeleme de azalacaktır
İnsanlar gerçekten UUID’yi birincil anahtar olarak mı kullanıyor? UUID gerektiğinde bunu ikincil anahtar olarak tutmak yerine böyle yapmanın ne avantajı var merak ediyorum
GUID ve UUID bu sorunu yapıları gereği çözer
v1 ve v6, makine kimliği ile zaman damgasını kodladığı için, makine başına ad alanı olan otomatik artan tamsayılara yakındır
Karışıklığın kaynağı, birçok kişinin UUID’lerin rastgele olduğunu varsayması. Bu yalnızca v4 için geçerli ve ne yazık ki v4 seçiminin bir maliyeti var
Verileri düzenlemek ya da performans ve çakışma garantileri için v3, v5, v7 gibi belli ölçüde deterministiklik gereken durumlar sık görülür