2 puan yazan GN⁺ 2023-06-29 | 1 yorum | WhatsApp'ta paylaş
  • TypeID, UUIDv7’yi genişleten tip güvenli bir tanımlayıcıdır; Stripe API’nin prefix kullanım biçiminden ilham alan, küresel olarak benzersiz bir tanımlayıcı formatı
  • Resmî dize küçük harfli bir string’dir ve en fazla 63 karakterlik küçük harfli snake_case ASCII type prefix, _ ayırıcı ve değiştirilmiş base32 ile kodlanmış 26 karakterlik UUIDv7 suffix olmak üzere üç bölümden oluşur
  • Type prefix, user ID’sinin post ID’si gereken yerde yanlışlıkla kullanılmasını önler ve hata ayıklama sırasında tanımlayıcının hangi varlık tipini işaret ettiğini hemen anlamayı sağlar
  • TypeID, UUID’nin bir üst kümesidir; tip bilgisi kaldırılıp decode edildiğinde geçerli bir UUIDv7 elde edilecek şekilde tasarlanmıştır
  • K-sıralanabilir olduğu için veritabanı birincil anahtarı olarak kullanılabilir; tamamen rastgele küresel ID olan UUIDv4 ile karşılaştırıldığında veritabanı locality’sindeki bozulma sorununu azaltmayı amaçlar
  • base32 kodlaması URL güvenliği, büyük/küçük harfe duyarsızlık, belirsiz karakterlerden kaçınma, çift tıklamayla seçilebilme ve UUID’nin geleneksel hex kodlamasına göre daha kısa uzunluk özelliklerine sahiptir
  • Resmî implementasyonlar Go, SQL, TypeScript için sunulur; en güncel spec sürümü v0.3.0’dır
  • Komut satırı aracıyla TypeID oluşturma, mevcut TypeID’nin UUID’sini decode etme ve UUID’yi TypeID’ye encode etme işlemleri yapılabilir
    • typeid new prefix ile yeni TypeID oluşturma
    • typeid decode prefix_01h2xcejqtf2nbrexx3vqjhp41 ile type ve uuid çıktılama
    • typeid encode prefix 0188bac7-4afa-78aa-bc3b-bd1eef28d881 ile TypeID oluşturma
  • Jetify’nin TypeID Converter aracında TypeID dizeleri UUID’ye dönüştürülebilir veya prefix:UUID formatında TypeID dönüşümü yapılabilir

1 yorum

 
GN⁺ 2023-06-29
Hacker News yorumları
  • Birkaç öneri var: prefix dizgesi çok geç olmadan sabitlenip belgelenirse iyi olur
    Go uygulamasına bakınca küçük harf ASCII olduğu için sorun yok gibi görünüyor ama article-comment gibi bileşik tiplerde belirsizlik var
    Karmaşık projeler veya ORM'ler için ayırıcıdan kaçınmak zor olacağından, tek bir ayırıcıya izin vermeyi değerlendirmeye değer
    Go uygulamasında test yok ama birim testi yazması kolay bir kod olduğu için mutlaka eklenmeli
    Go'da, Google'ın UUID uygulamasında olduğu gibi düzgün bir ayrıştırma fonksiyonu ve dahili byte dizisi kullanmak daha doğru; dizgeyi ise yalnızca render etme ve prefix için kullanmak daha iyi
    Şu anki ayrıştırma fazla esnek ve suffix boşsa oluşturma moduna giriyor gibi görünüyor; ayrıca SplitN sonrası indeksleme, alt çizgi yoksa panic'e yol açacak gibi
    Tasarımın kendisinde kusur bulmaya çalıştım ama sonuçta trade-off'un sweet spot'unu iyi yakalamış gibi görünüyor

    • base32 kodlaması gibi en karmaşık kısımlarda test var: https://github.com/jetpack-io/typeid-go/blob/main/base32/bas...
      Yine de eleştiri yerinde; dil uygulamaları arttıkça birbirleriyle uyumlu olduklarını doğrulamak gerekeceği için daha sıkı bir test paketi eklemeyi planlıyoruz
      Prefix konusunda ise, izin verilen karakter kümesini spesifikasyonda tanımlamamış olmamızın mı endişe yarattığını merak ediyorum
    • README başka uygulamalar istiyor; bu yüzden test paketi olursa üçüncü taraf kodlar için de iyi olur
    • Sonrasında oldukça kapsamlı testler uyguladım: https://github.com/jetpack-io/typeid-go/blob/main/typeid_tes...
      Ayrıca spesifikasyonda prefix'i de netleştirdim
    • Bunun için test yazmayı deneyeceğim
    • Prefix'in sabit bir şey olduğu yanlış anlaşılmış gibi görünüyor
      Prefix, kullanım alanına göre belirlenir
  • Bu yaklaşımı birkaç yıldır kullanıyoruz ama iki fark var
    Birincisi, insanların gerçekten bu değerleri elle girdiğini varsaymadığımız için l ile 1 karışıklığını çok önemsemiyoruz
    Bunun yerine, kelimelerin, özellikle küfürlerin tesadüfen oluşma olasılığını azaltmak için eiou sesli harflerini çıkaran bir base32 kullanıyoruz
    İkincisi, salt eklenmiş iki base32 karakterini checksum olarak ekliyoruz
    Böylece değer hatalı ya da kötü niyetli olduğunda veri deposuna sorgu atmadan bunu anlayabiliyoruz; diğer uygulamaların bunu neden yapmadığını bilmiyorum

    • Birkaç yıl önce otomatik oluşturulmuş bir parola olarak analrita çıkmış ve bu yüzden şikayet almıştık
      Hariç tutulan karakterlere a harfini eklemeyi de düşünebilirsiniz
    • Checksum eklenmesine katılıyorum ama “hatalı ya da kötü niyetli” kısmını merak ediyorum
      İnsanların bunu elle girmediğini varsayıyorsak, yanlış bir değerin kazara girilmesine yol açan kanalın ne olduğu soru işareti
      Kopyala/yapıştır hataları ya da sayfanın bunu büyük harfle render etmesi olabilir ama base32 kullanılıyorsa büyük/küçük harf normalize edilebilir
      Ayrıca 2 byte'lık, kriptografik olarak güvenli olmayan bir checksum'ın kötü niyetli durumlarda nasıl yardımcı olacağını da merak ediyorum
    • Checksum fikri ilginç
      Bunu TypeID spesifikasyonuna dahil etmenin doğru olup olmayacağını değerlendiriyorum
    • Birkaç ay önce kodlama yönteminin bir parçası olarak ikinci yaklaşımı uyguladım
      Veritabanı sorgularını ne kadar azalttığını bilmiyorum ama decode sırasında daha muğlak hatalara düşmemesi hoşuma gidiyor
  • Metinden ayrı olarak “Crockford's alphabet” bağlantısı da var: https://www.crockford.com/base32.html
    Bu base32 sistemi, alfanümerik karakterler arasında 1 ile karıştırılan I, L, 0 ile karıştırılan O ve ayrıca U harfini hariç tutuyor.
    Sayfada U harfinin çıkarılma nedeni olarak “kazara müstehcenlik” deniyor ama bunun tam olarak ne anlama geldiğini bilmiyorum.

    • Crockford bunu şaka yollu yazmış.
      Karıştırılmayacak alfanümerik karakterlerle düzgün bir base32 alfabesi yapmak için aslında sadece O, I, L harflerini çıkarmak yeterli.
      Yine de 33 karakter kalıyor, yani bir tanesini daha çıkarmak gerekiyor; hangisini çıkarırsanız çıkarın fark etmediği için, son çıkarılan karaktere rastgele bir gerekçe uydurmuş denebilir.
      Kullanıcının göreceği bir ID söz konusuysa tamamen saçma bir gerekçe de sayılmaz ama elbette kusursuz bir önleme sağladığı da söylenemez.
    • I ve O zaten çıkarılmışken U da çıkarılırsa, kaba görünen üç harfli ya da dört harfli kombinasyonların epey çoğu ortadan kalkabiliyor.
      Tabii A hâlâ olduğu için mümkün kalan kombinasyonlar var ama olasılık oldukça düşük.
    • Gerçek bir Latin dili safçısı için U, Cicero döneminde henüz var olmadığı için müstehcenliğe yakın derecede bayağı bir harftir.
    • Bunun başka örnekleri de var: base58, Satoshi/Bitcoin yaklaşımı https://en.wikipedia.org/wiki/Binary-to-text_encoding#Base58, “base62”, Keybase’in saltpack’i https://github.com/keybase/saltpack, meşhur “Adobe 85” https://en.wikipedia.org/wiki/Ascii85, basE91 https://base91.sourceforge.net
      Şirkette QR kodlar için birkaç yeni “taban” tanımlamıştık; bence bu, bilgisayar bilimlerinde nispeten az değerlendirilen bir alan.
    • Crockford’dan yaklaşık 4 yıl önce bir iş arkadaşımla neredeyse aynı karakter kümesini düşünmüştük.
      URL slug sorununu çözmeye çalışıyorduk ve uzunluklar oldukça fazla olduğu için bayt başına 5 bitin elle yazma zahmetini azaltacağını düşünmüştük.
      Sonuçta birkaç harfi daha çıkarma payımız vardı ve herkes işten çıktıktan sonra oturup küfürlerin ne kadar saldırgan olduğuna göre sıralama yaparak hangi harfleri eleyeceğimize karar vermiştik.
      Daha sonra aşağılayıcı ifadelerin daha büyük sorun olduğuna ikna edip u yerine n harfini çıkarmaya odaklanmıştım.
      tggr sadece sevimli duruyor ama n**r, çeşitli HR ekipleriyle rahatsız edici görüşmeler yapma sorununa yol açıyordu.
      Son karakter kümesi artık biraz bulanık hatırlanıyor ama genelde [a-z][0-9] ile uğraşıyorduk ve URL’de kullanılamayan ya da sözlü olarak aktarması zor pek çok sembol vardı.
      Hatırladığım kadarıyla 0, l, 1 karakterlerini tamamen çıkarmıştık ve elle yazmanın ya tamamen büyük harfle ya da tamamen küçük harfle yapılacağını varsaymıştık.
      0o sorun değildi, 1L de öyle.
  • Crockford kodlamasını pek sevmiyorum.
    Gerçekten de bu şekilde kodlanmış değerlerle teknik destek ya da analiz yaparken bunun bariz bir hata olduğunu hissettim.
    Bu alfabe, telefonda tanımlayıcı okuma gibi pratikte nadir olan bir hedef için tasarlanmış ve belirsizlik ekliyor.
    Kodlanmış dizenin kendisiyle loglarda grep yapmak ya da çapraz referans vermek gerektiğinde tam bir felaket; ayrıca tirelere de izin verdiği için kopyala-yapıştır ve satır kayması hatalarının başlıca sebeplerinden biri oluyor.
    Nesne tanımlayıcılarını doğrudan elle yazmak nadirdir ama uygulamalar, sohbetler ve forumlar arasında kopyala-yapıştır yapmak, e-postayla iletmek ve log dosyalarında aramak çok yaygındır.
    Bu koşullarda telaffuz edilebilirlik anlamsızdır, büyük/küçük harf duyarsızlığı ise engel çıkarır; gerekli olan şey tutarlılık ile yapıştırma ve satır kaymasına dayanıklılıktır.
    base58, bu gereksinimlere daha iyi uyan birebir bir kodlamadır ve hatta daha kısadır.
    Stripe’tan ilham alarak birkaç yıldır user_1BzGURpnHGn6oNru84B3Ri gibi tip prefix’i eklenmiş base58 kodlu UUID’leri nesne tanımlayıcısı olarak kullanıyorum.
    Yine de Douglas Crockford’un base32 kodlamasını 20 yıl önce, kullanım ortamının oldukça farklı olduğu bir dönemde tasarladığını hesaba katmak gerekir.

    • base58 ve base64url’ü de düşündüm; daha kısa kodlamalar hoşuma gitmişti.
      Ama büyük/küçük harf ayrımını varsaymak istemediğim için sonunda base32 tarafına yöneldim.
      Örneğin bir ID’yi dosya adı olarak kullanmak isteyebilirsiniz ve büyük/küçük harf duyarsız bir dosya sistemiyle sınırlı bir ortamda olabilirsiniz.
      TypeID, Crockford alfabesini her zaman küçük harfle kullanıyor; Crockford kodlama kurallarının tamamını kullanmıyor.
      TypeID’de tireye izin verilmiyor ve belirsiz karakterleri farklı yazarak aynı ID’nin birden fazla biçimde kodlanmasına da izin verilmiyor.
    • Tanımlayıcıların telefonda okunmasının nadir olduğu konusunda katılıyorum.
      Ama bazen ekran görüntüsünde ya da ekran paylaşımında görünen bir tanımlayıcıyı, ya da tanımlayıcıyı kolayca kopyalayamadığınız başka bir cihazdaki bir tanımlayıcıyı doğrudan girmek gerekebiliyor.
    • base58’in ya da KSUID’in kullandığı base62’nin birçok avantajı var.
      Crockford base32 de çalışıyor ama beni pek memnun etmiyor.
      Kişisel olarak birinci tercihim, tip prefix’i eklenmiş KSUID kullanmak olurdu.
      Böylece base62 kodlamalı, 160 bitlik, K-sıralanabilir bir ID elde edersiniz; uyumluluk nedeniyle mutlaka 128 bitlik ID gerekmedikçe oldukça uygun.
    • Crockford Base32’de tirelerin tam olarak nerede olduğunu bilmiyorum: https://en.wikipedia.org/wiki/Base32#Crockford's_Base32
      En sevdiğim base32 kodlaması z-base-32; göze daha rahat geliyor: https://philzimmermann.com/docs/human-oriented-base-32-encod...
      base58’in en büyük sorunu, tam sayılar için çok uygun olsa da kripto anahtarları gibi rastgele ikili veriler için daha az uygun olması ve büyük/küçük harf duyarlı dizelerin hoş görünmemesidir.
  • Temiz görünüyor
    “Type-safe” prefix hoşuma gitti
    Bizim ORM’de benzer şekilde DB içindeki tamsayı ID’lere varlık bazında etiketleri otomatik ekleyen yönteme tagged ids diyorduk: https://joist-orm.io/docs/advanced/tagged-ids
    Ayırıcı olarak : kullanıyorduk ama çift tıklamayla kopyala/yapıştır açısından _ kullanmamış olmaktan biraz pişmanım
    Teoride Joist’in “DB’deki UUID sütununu” “alan modelindeki TypeID”ye dönüştürmesini sağlamak da oldukça kolay olurdu, ama şu an yalnızca kullanıcı tarafı yapılandırmayla olacak gibi görünmüyor
    Yine de iyi fikir

    • Reddit de benzer ama string uzunluğu için optimize edilmiş bir yaklaşım kullanıyor
      Öğe ID’leri t3_15bfi0 gibi oluyor ve t3_ tip prefix’i
      t3 gönderi, t1 yorum, t5 subreddit; geri kalanı ise otomatik artan birincil anahtarın base36 kodlaması
    • Şirketimizdeki tipli iç ID sistemi başlangıçta ayırıcı olarak iki nokta üst üste kullanıyordu ama kısa süre sonra noktaya (.) geçtik
      İki nokta üst üste URL encoding’de % ile encode edildiği için ID uzunluğu artıyor, URL’ler çirkin ve uzun oluyor, bu da çok sinir bozucuydu
  • UUIDv7 birkaç yıldır HN’de popüler; ne zaman düzgün bir standart olacağını ve kütüphaneler, veritabanları ve ekosistemin geri kalanının bunu ne zaman native destekleyeceğini merak ediyorum

    • Nasıl bir destek beklendiğini bilmiyorum
      Çoğu yazılım UUID içindeki belirli bitlerle ilgilenmediği için bugün de kullanılabilir
      Belirli bitleri önemseyen bir yazılım varsa UUIDv4 gibi taklit edilebilir ve o bitler de rastgele üretilebilir
      Bir üretim prosedürü gerekiyorsa kendiniz yazabilirsiniz, zor değil
    • IETF’de standardizasyonun son aşamasında gibi görünüyor: https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122b...
    • Taslaklar ve iyileştirmelerden geçti, standardizasyona çok yaklaştı ve birçok kütüphane zaten destekliyor ya da yeni özellik ekliyor
      Örneğin Dart UUID kütüphanesini ben yönetiyorum; en son beta major sürümde v6, v7 ve özel v8 var
      Bir yerde implementasyonların listesi de var ve ben de o sayfada kütüphane yöneticisi olarak yer aldığım için her yeni taslak çıktığında yazarlardan bildirim alıyorum
  • “Çift tıklamayla kopyala/yapıştır yapılabiliyor” kısmı hoşuma gitti
    Detaylar önemlidir

  • UUID’den biraz şikayetçiyim
    UUID versioning gibi ritüellerle uğraşmak yerine neden sadece zaman + rastgelelik birleştirilmiyor anlamıyorum
    Yerellik önemli değilse doğrudan 128 bit rastgele sayı kullanılabilir
    Deneyimime göre çoğu insan UUID’lerin insan tarafından okunabilir hex gösterimiyle, hatta tireleriyle birlikte saklanması gerektiğini düşünüyor
    Veritabanında, ağda ve bellekte çok fazla yer israf ediyor

    • Birçok kişi aynı şeyi düşündü
      Örneğin ULID https://github.com/ulid/spec daha kısa ve zamanı sakladığı için sözlüksel olarak sıralanabiliyor
    • UUID tarafında bazen tek bir kullanım senaryosu her şeyi temsil ediyormuş ve dolayısıyla en iyi tek bir UUID biçimi varmış gibi konuşuluyor
      Gerçekte bu da diğer mühendislik problemleri gibi; gereksinimleri yazarsınız ve neyin çözdüğüne bakarsınız
      Farklı biçimlerin avantajlarını okumak faydalı, çünkü başkalarının zor yoldan öğrendiklerinden yararlanmanızı sağlıyor
      Sıralanabilirlik ve zaman tabanlı yerellik akla doğal olarak gelmeyebilir ama gerekiyorsa, bunu proje başladıktan 4 yıl sonra verileri çöpe attığınızı fark etmektense önceden bilmek çok daha iyi
      Bazı UUID biçimleri kendi küçük güvenlik sorunlarını da beraberinde getirdi; örneğin UUID v1’deki MAC adresi sızıntısından kaçınmak iyi olur
      Mevcut çözüm kullanım durumunuza uyuyorsa doğrudan kullanın
      Uymuyorsa da çok dert etmeye gerek yok
      Ben şahsen UUID’yi en çok kullandığım yerde “bana sadece benzersiz bir string gönderin, içiniz rahat edecekse istediğiniz UUID kütüphanesini kullanın” diye belirttim
      Bu sistemde her veri kaynağının kendine özgü bir biçimi var gibi görünüyor ama sorun değil
      Ölçek de günde on binler seviyesinde olduğu için bir problem yok ve UUID’ye göre sıralama gerekmiyor
      Bu, gerçek bir tanımlayıcıdan çok göndericinin her mesaj için ürettiği benzersiz bir token; birden fazla kuyruğu olan heterojen bir sistemde aşağı akışta yinelenen teslimatları tespit etmek için kullanılıyor
      Küresel benzersizlik de gerekmiyor; yalnızca küçük bir shard içinde benzersiz olması yeterli, hatta prensipte zaman sonra tekrar etmesi de sorun değil
      Ama sadelik için sistemin tüm süre boyunca benzersiz kalmasını sağlıyoruz
      Kendim üretmem gerektiğinde /dev/urandomdan veri okuyup base64 olarak encode ediyorum ve == ile bitmeyen bir boyut seçtim
      Bu da gerçek bir problem yüzünden değil, sadece görünüş açısından
    • Zamanla rastgeleliği birleştirmek küresel benzersizliği garanti etmez
    • Bana göre gerçekten değerli olan tek UUID standardı, basit rastgelelik kullanan v4
      Yine de tirelerin neden gerekli olduğunu hâlâ anlamıyorum ve diğer sürümlerin de pek anlamlı olmadığını düşünüyorum
    • Tirelerin en kötü yanı, tamamını çift tıklamayla kolayca kopyala/yapıştır yapmayı engellemesi
  • UUIDv7 ya da UUID şart değilse, https://github.com/segmentio/ksuid çok daha geniş bir anahtar alanı sunuyor
    Namespace gerekiyorsa başına bir string prefix ekleyebilirsiniz ve KSUID çakışma olasılığı herhangi bir UUID sürümüne göre çok daha düşüktür
    Sıralanabilir timestamp’e sahip genel amaçlı ID üreticileri arasında ksuid en iyisiydi ve çoğu dil için kütüphanesi var
    UUID v1~v7 israfa kaçıyor

    • Tip prefix’li KSUID üreten ince bir JavaScript paketi yayımladım
      Ek bitleri kaldırabiliyorsanız, ki çoğu durumda kaldırabilirsiniz, KSUID harika bir formattır
      [1] https://github.com/sophiabits/resource-id
    • ULID’ye geçme nedenim her zaman küçük harfli olması; bu yüzden büyük/küçük harf duyarsız ele alınabiliyor olması
    • Birkaç popüler ksuid kütüphanesinin bakımını yapıyorum ve gerçekten kullanıyorum, bu yüzden ksuid’yi seviyorum
      Ama ksuid’nin büyük sorunu 160 bit olması; bu da PostgreSQL gibi veritabanlarının yerel UUID tipiyle uyumlu olmadığı için bir performans maliyeti doğuruyor
      1: https://github.com/svix/rust-ksuid
      2: https://github.com/svix/python-ksuid
  • K-sıralanabilirlik harika bir kavram ve gevşek sıralı anahtarlar birçok kullanım senaryosunu çözüyor
    Tip içeren sıkıştırılmış string gösterimini de beğeniyorum
    Ancak UUID v7’nin istenmeyen bir yan etkisi olarak çok sayıda güvenlik sorunu doğurmasından endişeliyim
    İnsanlar UUID’yi token olarak kullanmamalı ve veritabanının birincil anahtarı olarak da böyle kullanmamalı, ama pratikte bunu yapıyorlar
    UUID v4 fiilen kriptografik rastgele sayı olduğundan, şimdiye kadarki birçok güvenlik açığının tesadüfen atlatıldığını düşünüyorum
    UUID v7’de gerçek rastgele veri miktarının 32 bite düştüğünü sanmıştım ve geliştiricilere UUID’lerin tahmin edilebilir olabileceğini iyi anlatmak gerektiğini düşünmüştüm
    Düzeltme: v7 UUID’lerinin tahmin edilebilirliği konusunda yanılmış gibi görünüyorum
    Taslak, rastgele bitler için CSPRNG öneriyor, entropinin en az 74 bit olduğunu ve “tahmin edilemez” olacak şekilde tasarlandığını söylüyor
    Güvenlikle ilgili kullanım alanları için “UUID v4” öneriliyor, ama bu muhtemelen timestamp’le ilgili anlamından dolayı olabilir

    • İlginç bir kullanılabilirlik sorunu var
      İsimler UUIDv4 ve UUIDv7 olunca, hangisinin veritabanı için daha iyi, hangisinin tek kullanımlık token için daha iyi olduğunu sürekli hatırlamayı gerektiren bitmeyen bir kafa karışıklığı olmaz mı diye düşünüyorum
      Geriye dönük uyumlu bir çözüm de pek göremiyorum
      Elixir’de v4 UUID oluştururken UUID.uuid4() fonksiyonu kullanılıyor
      Teorik olarak koddaki kullanım yerleri taranabilir, ama bunların hepsi hata olasılığını artırıyor
    • Bazı makul kullanım durumları görüyorum, ama geçmişte v1 ya da v5 gibi kısmen sıralı UUID’lerle her karşılaştığımda bunların yanlış kullanıldığını gördüm
      v3 gibi hash tabanlı olanlarda da aynı durum vardı
      v4 basit ve yanlış kullanım ihtimali daha düşük