2 poin oleh GN⁺ 2023-06-29 | 1 komentar | Bagikan ke WhatsApp
  • TypeID adalah pengidentifikasi type-safe yang memperluas UUIDv7, format pengidentifikasi unik global yang terinspirasi dari cara Stripe API menggunakan prefix
  • String kanonisnya berupa string huruf kecil, terdiri dari tiga bagian: type prefix ASCII snake_case huruf kecil maksimal 63 karakter, pemisah _, dan suffix UUIDv7 26 karakter yang dienkode dengan base32 yang dimodifikasi
  • Type prefix mencegah kesalahan memakai ID user di tempat yang membutuhkan ID post, dan saat debugging memungkinkan Anda langsung mengetahui tipe entitas yang dirujuk oleh pengidentifikasi tersebut
  • TypeID adalah superset dari UUID; jika informasi tipe dihapus lalu didekode, strukturnya menghasilkan UUIDv7 yang valid
  • Dapat diurutkan-K sehingga bisa digunakan sebagai primary key database, dengan tujuan mengurangi masalah penurunan locality database dibandingkan UUIDv4 yang merupakan ID global sepenuhnya acak
  • Encoding base32 memiliki karakteristik aman untuk URL, tidak peka huruf besar/kecil, menghindari karakter ambigu, dapat dipilih dengan double-click, dan lebih pendek daripada encoding hex tradisional UUID
  • Implementasi resmi tersedia dalam Go, SQL, dan TypeScript, dengan versi spec terbaru v0.3.0
  • Tool command-line dapat digunakan untuk membuat TypeID, mendecode UUID dari TypeID yang sudah ada, dan mengencode UUID menjadi TypeID
    • typeid new prefix untuk membuat TypeID baru
    • typeid decode prefix_01h2xcejqtf2nbrexx3vqjhp41 untuk menampilkan type dan uuid
    • typeid encode prefix 0188bac7-4afa-78aa-bc3b-bd1eef28d881 untuk membuat TypeID
  • Di TypeID Converter dari Jetify, Anda dapat mengubah string TypeID menjadi UUID atau mengubahnya menjadi TypeID dalam format prefix:UUID

1 komentar

 
GN⁺ 2023-06-29
Komentar Hacker News
  • Ada beberapa saran: sebaiknya string prefix ditetapkan dan didokumentasikan sebelum terlambat.
    Melihat implementasi Go, karena memakai ASCII huruf kecil tampaknya tidak masalah, tetapi untuk tipe gabungan seperti article-comment ini menjadi ambigu.
    Pada proyek kompleks atau ORM, sulit menghindari pemisah, jadi layak dipertimbangkan untuk mengizinkan satu pemisah tunggal.
    Di implementasi Go belum ada test, padahal kodenya mudah dibuat unit test, jadi ini harus ditambahkan.
    Di Go, seperti implementasi UUID dari Google, pendekatan yang tepat adalah memakai fungsi parsing yang benar dan array byte internal; string sebaiknya hanya dipakai untuk rendering dan prefix.
    Parsing saat ini terlalu permisif, dan jika suffix kosong tampaknya masuk ke mode pembuatan; pengindeksan setelah SplitN sepertinya akan panic jika tidak ada garis bawah.
    Saya mencoba mencari cela pada desainnya sendiri, tetapi pada akhirnya tampaknya desain ini berhasil menemukan sweet spot trade-off yang baik.

    • Bagian paling kompleks, seperti encoding base32, sudah memiliki test: https://github.com/jetpack-io/typeid-go/blob/main/base32/bas...
      Meski begitu, masukannya benar, dan seiring bertambahnya implementasi di berbagai bahasa, perlu dipastikan semuanya kompatibel, jadi kami berencana menambahkan kumpulan test yang lebih ketat.
      Terkait prefix, saya ingin tahu apakah yang dikhawatirkan adalah bahwa himpunan karakter yang diizinkan belum didefinisikan dalam spesifikasi.
    • README meminta implementasi lain, jadi adanya kumpulan test juga akan bagus untuk kode pihak ketiga.
    • Sebagai tindak lanjut, saya sudah mengimplementasikan test yang cukup menyeluruh: https://github.com/jetpack-io/typeid-go/blob/main/typeid_tes...
      Dan prefix juga sudah diperjelas dalam spesifikasi.
    • Saya akan coba menulis test untuk ini.
    • Sepertinya ada salah paham bahwa prefix adalah sesuatu yang sudah tetap.
      Prefix ditentukan sesuai domain penggunaan.
  • Saya sudah memakai pendekatan seperti ini selama beberapa tahun, tetapi ada dua perbedaan.
    Pertama, saya tidak menganggap orang benar-benar mengetik nilainya secara manual, jadi kebingungan antara l dan 1 tidak terlalu saya pedulikan.
    Sebagai gantinya, saya memakai base32 tanpa vokal eiou untuk mengurangi kemungkinan kata, terutama kata-kata kasar, muncul secara kebetulan.
    Kedua, saya menambahkan dua karakter base32 yang diberi salt sebagai checksum.
    Saat nilainya aneh karena kesalahan atau niat jahat, kita tidak perlu sampai melakukan query ke penyimpanan data; saya tidak tahu kenapa implementasi lain tidak melakukan ini.

    • Beberapa tahun lalu, kata sandi yang dibuat otomatis menghasilkan analrita dan kami menerima keluhan.
      Mungkin layak mempertimbangkan untuk memasukkan a juga ke karakter yang dikecualikan.
    • Saya setuju dengan penambahan checksum, tetapi penasaran dengan bagian “kesalahan atau niat jahat”.
      Jika diasumsikan orang tidak mengetik manual, saya bertanya-tanya jalur apa yang bisa membuat nilai salah dimasukkan karena kesalahan.
      Mungkin ada kesalahan salin/tempel atau halaman merendernya dalam huruf besar, tetapi kalau base32, huruf besar/kecil bisa dinormalisasi.
      Saya juga penasaran bagaimana checksum 2 byte yang tidak aman secara kriptografis bisa membantu dalam kasus berniat jahat.
    • Ide checksum ini menarik.
      Saya sedang mempertimbangkan apakah sebaiknya dimasukkan ke spesifikasi TypeID.
    • Beberapa bulan lalu saya mengimplementasikan cara kedua sebagai bagian dari metode encoding.
      Saya tidak tahu seberapa banyak ini mengurangi query database, tetapi bagus karena saat decoding tidak jatuh ke error yang lebih ambigu.
  • Terlepas dari isi artikel, ada tautan “Crockford's alphabet”: https://www.crockford.com/base32.html
    Skema base32 ini mengecualikan I dan L yang bisa tertukar dengan 1, O yang bisa tertukar dengan 0, serta U
    Di halaman itu, alasan menghapus U disebut “ketidaksenonohan yang tidak disengaja”, tapi saya tidak paham maksudnya apa

    • Crockford menulisnya dengan bercanda
      Untuk membuat alfabet base32 yang bagus dari karakter alfanumerik yang tidak membingungkan, cukup hapus O, I, dan L
      Meski begitu masih tersisa 33 karakter, jadi harus menghapus satu lagi, dan karena karakter mana pun tidak masalah, ia seolah memberi alasan arbitrer untuk karakter terakhir yang dihapus
      Kalau ID itu terlihat oleh pengguna, alasannya tidak sepenuhnya mengada-ada, tetapi tentu saja sama sekali bukan pencegahan yang sempurna
    • Jika I dan O sudah dikecualikan lalu U juga dihapus, cukup banyak kombinasi tiga atau empat huruf yang tampak kasar bisa dihilangkan
      Tentu saja karena masih ada A, masih ada kombinasi yang mungkin, tetapi kemungkinannya sangat rendah
    • Bagi penganut Latin sejati, U belum ada pada masa Cicero, jadi itu huruf yang begitu vulgar sampai mendekati cabul
    • Ada juga yang seperti ini: base58 adalah cara Satoshi/Bitcoin https://en.wikipedia.org/wiki/Binary-to-text_encoding#Base58, “base62” adalah saltpack dari Keybase https://github.com/keybase/saltpack, “Adobe 85” yang terkenal https://en.wikipedia.org/wiki/Ascii85, basE91 https://base91.sourceforge.net
      Di perusahaan, kami mendefinisikan beberapa “basis” baru untuk kode QR, dan secara pribadi saya melihat ini sebagai area yang kurang dimanfaatkan dalam ilmu komputer
    • Sekitar 4 tahun sebelum Crockford, saya pernah memikirkan set karakter yang hampir sama dengan seorang rekan
      Kami sedang memecahkan masalah slug URL, dan karena panjangnya cukup besar, kami menilai 5 bit per byte bisa mengurangi kerepotan penyalinan manual
      Pada akhirnya kami masih punya ruang untuk menghapus beberapa huruf lagi, dan setelah semua orang pulang, kami berdua duduk lalu memeringkat tingkat penghinaan kata-kata kasar untuk menentukan huruf mana yang akan dihapus
      Setelah itu saya meyakinkan bahwa ungkapan peyoratif adalah masalah yang lebih besar, jadi kami fokus menghapus n alih-alih u
      tggr hanya terdengar lucu, tetapi n**r akan menjadi masalah yang memaksa percakapan tidak nyaman dengan berbagai tim HR
      Set karakter akhirnya kini agak samar dalam ingatan saya, tetapi biasanya kami menangani [a-z][0-9] dan ada banyak simbol yang tidak bisa dipakai di URL atau sulit dibacakan
      Seingat saya, kami menghapus 0, l, dan 1 semuanya, dan tampaknya kami menganggap penyalinan manual dilakukan seluruhnya dalam huruf besar atau seluruhnya dalam huruf kecil
      0o bukan masalah, begitu juga 1L
  • Saya kurang menyukai encoding Crockford
    Dalam praktiknya, ketika mendukung secara teknis atau menganalisis nilai yang dienkode dengan cara ini, saya merasa itu jelas sebuah kesalahan
    Alfabet ini dirancang untuk sasaran yang sebenarnya jarang, seperti membacakan identifier lewat telepon, dan justru memasukkan ambiguitas
    Dalam situasi ketika Anda melakukan grep pada log atau cross-reference berdasarkan string encoding itu sendiri, ini bencana, dan karena tanda hubung juga diizinkan, itu menjadi sumber utama kesalahan copy/paste dan pemenggalan baris
    Jarang ada orang mengetik identifier objek secara manual, tetapi menyalin/menempel di antara aplikasi, chat, dan forum, meneruskannya lewat email, serta mencarinya di file log adalah hal yang umum
    Dalam kondisi seperti ini, kemudahan pelafalan tidak berarti, case-insensitive justru mengganggu, dan yang dibutuhkan adalah konsistensi serta ketahanan terhadap paste dan pemenggalan baris
    base58 adalah encoding bijektif yang lebih cocok untuk kebutuhan ini, bahkan lebih pendek
    Terinspirasi oleh Stripe, selama beberapa tahun saya memakai UUID berenkode base58 dengan prefix tipe seperti user_1BzGURpnHGn6oNru84B3Ri sebagai identifier objek
    Namun perlu diingat bahwa encoding base32 Douglas Crockford dirancang 20 tahun lalu, ketika lingkungan penggunaannya cukup berbeda

    • Saya juga mempertimbangkan base58 atau base64url, dan saya menyukai encoding yang lebih pendek
      Namun saya tidak ingin bergantung pada case-sensitive, jadi akhirnya condong ke base32
      Misalnya, Anda mungkin ingin memakai ID sebagai nama file, dan bisa saja berada di lingkungan yang terikat pada sistem file case-insensitive
      TypeID selalu memakai alfabet Crockford dalam huruf kecil; bukan berarti memakai seluruh aturan encoding Crockford
      TypeID tidak mengizinkan tanda hubung, dan juga tidak mengizinkan karakter ambigu ditulis berbeda sehingga ID yang sama bisa dienkode dalam beberapa cara
    • Saya setuju bahwa membacakan identifier lewat telepon itu jarang
      Namun kadang kita perlu mengetikkannya langsung dari screenshot atau screen sharing, atau dari perangkat lain yang tidak memudahkan pengambilan identifier
    • base62 yang dipakai base58 atau KSUID punya banyak kelebihan
      Crockford base32 juga berfungsi, tetapi saya tidak terlalu menyukainya
      Secara pribadi, pilihan pertama saya adalah memakai KSUID dengan prefix tipe
      Anda bisa mendapatkan ID 160-bit yang K-sortable dengan encoding base62, dan itu cocok jika tidak harus menggunakan ID 128-bit demi kompatibilitas
    • Saya tidak tahu di mana letak tanda hubung di Crockford Base32: https://en.wikipedia.org/wiki/Base32#Crockford's_Base32
      Encoding base32 favorit saya adalah z-base-32, karena terasa lebih nyaman dilihat: https://philzimmermann.com/docs/human-oriented-base-32-encod...
      Masalah terbesar base58 adalah cocok untuk bilangan bulat, tetapi kurang cocok untuk data biner arbitrer seperti kunci kriptografi, dan string case-sensitive kurang enak dilihat
  • Rapi
    Saya suka prefix “type-safe”
    Di ORM kami, pendekatan serupa—menambahkan tag per entitas secara otomatis ke ID integer di dalam DB—kami sebut tagged ids: https://joist-orm.io/docs/advanced/tagged-ids
    Kami memakai : sebagai pemisah, tetapi untuk urusan salin/tempel dengan klik dua kali, saya sedikit menyesal tidak memakai _
    Secara teori, akan sangat mudah membuat Joist mengubah “kolom UUID di DB” menjadi “TypeID di model domain”, tetapi saat ini sepertinya belum bisa dilakukan hanya lewat konfigurasi di ruang pengguna
    Tetap saja, ini ide yang bagus

    • Reddit juga memakai pendekatan serupa, tetapi dioptimalkan untuk panjang string
      ID elemen berbentuk seperti t3_15bfi0, dan t3_ adalah prefix tipe
      t3 adalah postingan, t1 adalah komentar, t5 adalah subreddit, dan sisanya adalah encoding base36 dari primary key auto-increment
    • Sistem ID internal bertipe di perusahaan kami awalnya memakai titik dua sebagai pemisah, tetapi tak lama kemudian kami menggantinya dengan titik (.)
      Titik dua akan di-encode sebagai % dalam URL encoding, membuat ID jadi lebih panjang, dan URL menjadi jelek serta panjang, yang sangat menjengkelkan
  • UUIDv7 sudah populer di HN selama bertahun-tahun, dan saya penasaran kapan akan menjadi standar yang benar-benar matang serta kapan library, database, dan ekosistem lainnya akan mendukungnya secara native

    • Saya tidak tahu dukungan seperti apa yang diharapkan
      Kebanyakan software tidak peduli pada bit tertentu di dalam UUID, jadi mestinya bisa dipakai hari ini juga
      Jika ada software yang peduli pada bit tertentu, Anda bisa menirunya seperti UUIDv4, dan bit-bit seperti itu pun bisa saja dihasilkan secara acak
      Jika membutuhkan prosedur pembuatan, Anda bisa menulisnya sendiri, dan itu tidak sulit
    • Sepertinya sedang berada di tahap akhir standardisasi di IETF: https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122b...
    • Sudah melewati draf dan perbaikan, kini sangat dekat dengan standardisasi, dan banyak library sudah mendukungnya atau sedang menambahkan fitur baru
      Misalnya, saya mengelola library UUID untuk Dart, dan rilis mayor beta terbaru sudah mencakup v6, v7, dan v8 kustom
      Daftar implementasi juga ada di suatu tempat, dan karena saya tercantum di halaman itu sebagai maintainer library, para penulis memberi tahu saya setiap kali ada draf baru
  • Saya suka bagian “bisa disalin/ditempel dengan klik dua kali”
    Detail itu penting

  • Saya punya beberapa keluhan tentang UUID
    Saya tidak mengerti mengapa kita tidak cukup menggabungkan waktu + angka acak saja, tanpa ritual seperti versioning UUID
    Jika locality tidak penting, gunakan saja angka acak 128-bit apa adanya
    Dari pengalaman, kebanyakan orang tampaknya menganggap UUID harus disimpan dalam representasi heksadesimal yang ramah dibaca manusia, bahkan termasuk tanda hubung
    Itu sangat memboroskan ruang di database, jaringan, dan memori

    • Banyak orang berpikiran sama
      Misalnya ULID https://github.com/ulid/spec lebih pendek dan menyimpan waktu sehingga terurut secara leksikografis
    • Di pihak UUID, ada kecenderungan berbicara seolah-olah satu use case mewakili semuanya, sehingga ada satu format UUID terbaik
      Kenyataannya sama seperti masalah rekayasa lain: tuliskan requirement-nya dan lihat apa yang menyelesaikannya
      Membaca kelebihan berbagai format sangat membantu karena memungkinkan kita memanfaatkan hal-hal yang sudah dipelajari orang lain dengan susah payah
      Kemampuan diurutkan dan locality berbasis waktu mungkin tidak terpikir secara alami, tetapi jika Anda membutuhkannya, jauh lebih baik mengetahuinya sejak awal daripada menyadari 4 tahun kemudian bahwa data itu sudah terbuang
      Beberapa format UUID juga memperkenalkan sendiri masalah keamanan kecil; misalnya kebocoran alamat MAC pada UUID v1, yang sebaiknya dihindari
      Jika solusi yang sudah ada cocok untuk use case Anda, pakai saja
      Jika tidak, Anda tidak perlu terlalu khawatir
      Secara pribadi, di tempat saya paling banyak memakai UUID, saya menetapkan “kirim saja string unik; kalau merasa lebih tenang, pakai library UUID apa pun yang Anda mau”
      Sistem ini tampaknya punya format unik untuk setiap sumber data, tetapi itu tidak masalah
      Volumenya hanya puluhan ribu per hari, jadi tidak ada masalah skala, dan tidak perlu mengurutkan berdasarkan UUID
      Ini lebih seperti token unik yang dibuat pengirim untuk setiap pesan daripada identifier sungguhan, dipakai untuk mendeteksi kedatangan duplikat di hilir dalam sistem heterogen dengan banyak queue
      Keunikan global juga tidak diperlukan; cukup unik di dalam shard kecil, dan secara prinsip tidak masalah jika berulang setelah waktu berlalu
      Namun demi kesederhanaan, kami menjaga sistem agar unik sepanjang seluruh periode
      Jika membuatnya sendiri, kami membaca data dari /dev/urandom lalu meng-encode-nya dengan base64, dan memilih ukuran yang tidak berakhir dengan ==
      Ini pun bukan karena masalah nyata, melainkan alasan estetika
    • Menggabungkan waktu dan angka acak tidak bisa menjamin keunikan global
    • Secara pribadi, saya menganggap satu-satunya standar UUID yang bernilai adalah v4, yaitu angka acak sederhana
      Meski begitu, saya tetap tidak tahu mengapa tanda hubung diperlukan, dan menurut saya versi-versi lain tidak terlalu bermakna
    • Hal terburuk dari tanda hubung adalah keseluruhannya tidak bisa dengan mudah disalin/ditempel dengan klik dua kali
  • Jika tidak benar-benar membutuhkan UUIDv7 atau UUID, https://github.com/segmentio/ksuid menyediakan ruang kunci yang jauh lebih besar
    Jika membutuhkan namespace, cukup tambahkan prefix string, dan kemungkinan tabrakan KSUID jauh lebih kecil daripada UUID versi mana pun
    Di antara generator ID umum yang memiliki timestamp yang dapat diurutkan, ksuid adalah yang terbaik, dan ada library untuk sebagian besar bahasa
    UUID v1~v7 sangat boros

    • Saya sudah merilis paket JavaScript tipis yang menghasilkan KSUID dengan prefix tipe
      Jika Anda sanggup menanggung bit tambahan—dan kebanyakan kasus mungkin bisa—KSUID adalah format yang bagus
      [1] https://github.com/sophiabits/resource-id
    • Alasan saya pindah ke ULID adalah karena selalu huruf kecil, sehingga bisa diperlakukan tanpa membedakan huruf besar/kecil
    • Saya menyukai ksuid karena memelihara beberapa library ksuid populer dan benar-benar menggunakannya
      Namun masalah besar ksuid adalah ukurannya 160 bit, sehingga tidak cocok dengan tipe UUID native di database, misalnya PostgreSQL, dan menimbulkan biaya performa
      1: https://github.com/svix/rust-ksuid
      2: https://github.com/svix/python-ksuid
  • K-sortable adalah konsep yang bagus, dan key yang terurut secara lemah menyelesaikan banyak use case
    Saya juga suka representasi string terkompresi yang bertipe
    Namun saya khawatir akan muncul banyak masalah keamanan sebagai efek samping yang tidak disengaja dari UUID v7
    Orang seharusnya tidak memakai UUID sebagai token, dan juga tidak seharusnya memakai primary key DB seperti itu, tetapi dalam praktiknya mereka melakukannya
    UUID v4 pada dasarnya adalah angka acak kriptografis, jadi menurut saya banyak kerentanan keamanan sejauh ini kebetulan lolos tanpa masalah
    Saya sempat mengira pada UUID v7 data acak sebenarnya turun menjadi 32 bit, dan merasa developer perlu diedukasi dengan baik bahwa UUID bisa ditebak
    Koreksi: sepertinya saya salah membaca soal kemungkinan UUID v7 dapat ditebak
    Drafnya merekomendasikan CSPRNG untuk bit acak, menyatakan entropinya minimal 74 bit, dan dirancang agar “tidak dapat ditebak”
    Untuk penggunaan terkait keamanan memang disarankan memakai “UUID v4”, tetapi mungkin itu terkait makna timestamp

    • Ada masalah usability yang menarik
      Jika namanya UUIDv4 dan UUIDv7, bukankah akan terus muncul kebingungan tanpa akhir karena harus mengingat mana yang bagus untuk database dan mana yang bagus untuk token sekali pakai?
      Saya juga tidak tahu solusi yang kompatibel ke belakang
      Di Elixir, saat membuat UUID v4, fungsi yang dipakai adalah UUID.uuid4()
      Secara teori, kita bisa memindai penggunaannya di kode, tetapi hal-hal seperti ini semuanya meningkatkan kemungkinan error
    • Saya memang melihat beberapa kasus yang layak dipakai, tetapi setiap kali dulu bertemu UUID yang sebagian berurutan seperti v1 atau v5, biasanya itu sedang disalahgunakan
      Yang berbasis hash seperti v3 juga sama
      v4 sederhana dan kecil kemungkinan disalahgunakan