2 คะแนน โดย GN⁺ 2023-06-29 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • TypeID คือรูปแบบตัวระบุเฉพาะส่วนกลางที่ขยายจาก UUIDv7 ให้มีความปลอดภัยด้านชนิดข้อมูล โดยได้แรงบันดาลใจจากวิธีใช้ prefix ของ Stripe API
  • สตริงตามรูปแบบมาตรฐานประกอบด้วย 3 ส่วน ได้แก่ type prefix แบบ ASCII snake_case ตัวพิมพ์เล็กยาวได้สูงสุด 63 ตัวอักษร, ตัวคั่น _, และ suffix UUIDv7 ยาว 26 ตัวอักษรที่เข้ารหัสด้วย base32 แบบดัดแปลง
  • type prefix ช่วยป้องกันการนำ ID ของ user ไปใช้ผิดที่ซึ่งควรเป็น ID ของ post และยังช่วยให้ระบุได้ทันทีระหว่างดีบักว่าตัวระบุนั้นชี้ไปยังเอนทิตีประเภทใด
  • TypeID เป็น superset ของ UUID โดยเมื่อลบข้อมูลประเภทออกและถอดรหัส จะได้ UUIDv7 ที่ถูกต้อง
  • สามารถจัดเรียงแบบ K-sortable ได้ จึงใช้เป็นคีย์หลักของฐานข้อมูลได้ และมีเป้าหมายเพื่อลดปัญหา database locality ที่แย่ลงเมื่อเทียบกับ UUIDv4 ซึ่งเป็น global ID แบบสุ่มทั้งหมด
  • การเข้ารหัสแบบ base32 มีข้อดีคือปลอดภัยต่อ URL, ไม่แยกความต่างตัวพิมพ์เล็กใหญ่, หลีกเลี่ยงอักขระที่กำกวม, ดับเบิลคลิกเพื่อเลือกได้, และมีความยาวสั้นกว่าการเข้ารหัสแบบ hex ตามธรรมเนียมของ UUID
  • มี implementation อย่างเป็นทางการสำหรับ Go, SQL, TypeScript และสเปกล่าสุดคือ v0.3.0
  • สามารถใช้เครื่องมือบรรทัดคำสั่งเพื่อสร้าง TypeID, ถอดรหัส UUID จาก TypeID ที่มีอยู่, และเข้ารหัส UUID เป็น TypeID ได้
    • typeid new prefix ใช้สร้าง TypeID ใหม่
    • typeid decode prefix_01h2xcejqtf2nbrexx3vqjhp41 จะแสดง type และ uuid
    • typeid encode prefix 0188bac7-4afa-78aa-bc3b-bd1eef28d881 ใช้สร้าง TypeID
  • ใน TypeID Converter ของ Jetify สามารถแปลงสตริง TypeID เป็น UUID หรือแปลงเป็น TypeID จากรูปแบบ prefix:UUID ได้

1 ความคิดเห็น

 
GN⁺ 2023-06-29
ความคิดเห็นจาก Hacker News
  • มีข้อเสนอแนะอยู่หลายอย่าง: ควรกำหนดและทำเอกสารของ สตริง prefix ให้ชัดเจนตั้งแต่เนิ่น ๆ ก่อนที่จะสายเกินไป
    ดูจาก implementation ของ Go แล้วใช้ ASCII ตัวพิมพ์เล็กจึงน่าจะโอเค แต่สำหรับชนิดแบบผสมอย่าง article-comment ยังดูคลุมเครือ
    ในโปรเจกต์ที่ซับซ้อนหรือ ORM มักเลี่ยงตัวคั่นได้ยาก จึงน่าพิจารณาให้รองรับตัวคั่นแบบเดี่ยว
    implementation ของ Go ยังไม่มีการทดสอบ ทั้งที่เป็นโค้ดที่เขียน unit test ได้ง่าย จึงควรเพิ่มอย่างยิ่ง
    ใน Go น่าจะเหมาะกว่าที่จะใช้ฟังก์ชัน parse ที่ถูกต้องและใช้อาร์เรย์ไบต์ภายในแบบเดียวกับ implementation UUID ของ Google ส่วนสตริงก็ควรใช้เฉพาะตอนเรนเดอร์และกับ prefix
    ตอนนี้การ parse ผ่อนปรนเกินไป และถ้า suffix ว่างก็ดูเหมือนจะเข้าสู่โหมดสร้างใหม่ อีกทั้งการ index หลัง SplitN ก็น่าจะ panic ได้ถ้าไม่มีขีดล่าง
    แต่พอดูจากภาพรวมแล้ว แม้จะพยายามหาจุดตำหนิในดีไซน์ สุดท้ายก็รู้สึกว่ามันหาจุดลงตัวของ sweet spot ของ trade-off ได้ดี

    • มีการทดสอบในส่วนที่ซับซ้อนที่สุดอย่างการเข้ารหัส base32 อยู่แล้ว: https://github.com/jetpack-io/typeid-go/blob/main/base32/bas...
      ถึงอย่างนั้น ข้อท้วงติงก็ถูกต้อง และยิ่งมี implementation หลายภาษาเพิ่มขึ้น ก็ยิ่งต้องตรวจสอบความเข้ากันได้ระหว่างกัน จึงมีแผนจะเพิ่มชุดทดสอบที่เข้มงวดยิ่งขึ้น
      ส่วนเรื่อง prefix อยากรู้ว่าความกังวลคือการที่ยังไม่ได้กำหนดชุดอักขระที่อนุญาตไว้ในสเปกหรือไม่
    • ใน README ก็เปิดรับ implementation อื่น ๆ อยู่แล้ว ดังนั้นถ้ามี ชุดทดสอบ ก็จะเป็นประโยชน์กับโค้ดของ third party ด้วย
    • ภายหลังได้ทำชุดทดสอบที่ค่อนข้างละเอียดแล้ว: https://github.com/jetpack-io/typeid-go/blob/main/typeid_tes...
      และได้ทำให้ prefix ในสเปกชัดเจนขึ้นแล้วด้วย
    • จะลองเขียนการทดสอบสำหรับเรื่องนี้ดู
    • น่าจะเข้าใจผิดว่า prefix เป็นสิ่งที่ถูกกำหนดตายตัวแล้ว
      จริง ๆ แล้ว prefix เป็นสิ่งที่กำหนดตามโดเมนการใช้งาน
  • ใช้วิธีแบบนี้มาหลายปีแล้ว แต่มีสองจุดที่ต่างกัน
    อย่างแรกคือไม่ได้มองว่าคนจะพิมพ์ค่านี้ด้วยมือจริง ๆ จึงไม่ได้กังวลมากเรื่องความสับสนระหว่าง l กับ 1
    แต่แทนที่จะเป็นแบบนั้น จะใช้ base32 ที่ตัดสระ eiou ออก เพื่อลดโอกาสที่บังเอิญกลายเป็นคำ โดยเฉพาะคำหยาบ
    อย่างที่สองคือจะเพิ่มอักขระ base32 ที่ใส่ salt แล้วสองตัวเป็น checksum
    เวลาค่าผิดปกติไม่ว่าจะเกิดจากความผิดพลาดหรือเจตนาร้าย ก็ไม่จำเป็นต้องไป query ถึง data store และไม่เข้าใจว่าทำไม implementation อื่น ๆ ถึงไม่ทำแบบนี้

    • เมื่อหลายปีก่อนเคยมีรหัสผ่านที่สร้างอัตโนมัติออกมาเป็น analrita แล้วมีคนร้องเรียน
      อาจควรพิจารณาใส่ a เข้าไปในชุดอักขระที่ตัดทิ้งด้วย
    • เห็นด้วยกับการเพิ่ม checksum แต่สงสัยในส่วนที่ว่า “ความผิดพลาดหรือเจตนาร้าย”
      ถ้ามองว่าไม่ได้มีคนพิมพ์ด้วยมือจริง ๆ ก็สงสัยว่าเส้นทางไหนที่จะทำให้ใส่ค่าผิดโดยไม่ตั้งใจได้
      อาจมีกรณี copy/paste ผิด หรือหน้าเว็บเรนเดอร์เป็นตัวพิมพ์ใหญ่ แต่ถ้าเป็น base32 ก็จัดรูปแบบตัวพิมพ์เล็กใหญ่ให้เป็นมาตรฐานได้
      อีกอย่างคือ checksum 2 ไบต์ที่ไม่ปลอดภัยเชิงคริปโตจะช่วยอะไรได้อย่างไรในกรณีที่มีเจตนาร้าย
    • ไอเดียเรื่อง checksum น่าสนใจ
      กำลังพิจารณาอยู่ว่าควรรวมไว้ในสเปกของ TypeID หรือไม่
    • เมื่อไม่กี่เดือนก่อนเคยทำวิธีที่สองนี้ไว้เป็นส่วนหนึ่งของรูปแบบการเข้ารหัส
      ไม่แน่ใจว่าช่วยลดการ query ฐานข้อมูลไปได้มากแค่ไหน แต่ก็ดีตรงที่ระหว่างถอดรหัสจะไม่หลุดไปเป็นข้อผิดพลาดที่กำกวมกว่าเดิม
  • นอกเหนือจากเนื้อหาหลัก ยังมีลิงก์ “Crockford's alphabet”: https://www.crockford.com/base32.html
    ระบบ base32 นี้ตัด I กับ L ที่สับสนกับ 1, O ที่สับสนกับ 0 และ U ออกไปจากตัวอักษรและตัวเลข
    ในหน้านั้นบอกว่าเหตุผลที่ตัด U ออกคือ “คำหยาบที่เกิดขึ้นโดยไม่ตั้งใจ” แต่ไม่เข้าใจว่าหมายถึงอะไร

    • เป็นมุกที่ Crockford เขียนแบบขำ ๆ
      ถ้าจะสร้างอักษร base32 ที่ใช้ตัวอักษรและตัวเลขซึ่งไม่ชวนสับสน แค่ตัด O, I, L ออกก็พอ
      แต่ก็ยังเหลือ 33 ตัวอยู่ จึงต้องตัดเพิ่มอีก 1 ตัว และจะเป็นตัวไหนก็ได้ เลยใส่เหตุผลแบบตามอำเภอใจให้กับตัวสุดท้ายที่ถูกตัดออก
      ถ้าเป็น ID ที่ผู้ใช้มองเห็นได้ เหตุผลนี้ก็ไม่ได้เหลวไหลเสียทีเดียว แต่แน่นอนว่าไม่ได้ป้องกันได้สมบูรณ์เลย
    • เมื่อตัด I และ O ออกไปแล้ว ถ้าตัด U เพิ่มด้วย ก็จะลดชุดตัวอักษร 3 ตัวหรือ 4 ตัวที่ดูหยาบคายได้ค่อนข้างมาก
      แน่นอนว่ายังมีบางชุดที่เป็นไปได้เพราะยังมี A อยู่ แต่โอกาสก็น้อยมาก
    • สำหรับผู้เคร่งครัดภาษาละตินตัวจริง U เป็นตัวอักษรที่หยาบคายจนเกือบเข้าข่ายลามก เพราะในสมัยของ Cicero มันยังไม่มีอยู่
    • ยังมีอย่างอื่นอีก: base58 แบบของ Satoshi/Bitcoin https://en.wikipedia.org/wiki/Binary-to-text_encoding#Base58, “base62” ของ saltpack จาก Keybase https://github.com/keybase/saltpack, “Adobe 85” ที่เป็นที่รู้จัก https://en.wikipedia.org/wiki/Ascii85, basE91 https://base91.sourceforge.net
      ที่บริษัทก็เคยนิยาม “เลขฐาน” ใหม่หลายแบบสำหรับ QR code และส่วนตัวมองว่านี่เป็นพื้นที่ที่วิทยาการคอมพิวเตอร์ยังใช้ประโยชน์กันน้อย
    • ราว 4 ปีก่อนหน้า Crockford ผมกับเพื่อนร่วมงานก็เคยคิดชุดอักขระที่แทบจะเหมือนกันขึ้นมา
      ตอนนั้นกำลังแก้ปัญหา URL slug และเพราะมันค่อนข้างยาว จึงมองว่า 5 บิตต่อไบต์จะช่วยลดความลำบากในการคัดลอกเขียนตามได้
      สุดท้ายเราพอมีพื้นที่ให้ตัดตัวอักษรออกเพิ่มอีกไม่กี่ตัว และหลังเลิกงานตอนที่ทุกคนกลับกันหมดแล้ว เราสองคนนั่งจัดอันดับระดับความดูหมิ่นของคำหยาบเพื่อเลือกว่าจะตัดตัวไหนออก
      หลังจากนั้นผมก็โน้มน้าวว่าคำเหยียดน่าจะเป็นปัญหาใหญ่กว่า เลยหันไปเน้นการตัด n แทน u
      tggr ก็แค่น่ารักดี แต่ n**r นั้นหมายถึงต้องไปคุยอึดอัดกับทีม HR หลายทีม
      ชุดอักขระสุดท้ายตอนนี้จำได้ลาง ๆ แต่โดยทั่วไปคือ [a-z][0-9] และมีสัญลักษณ์จำนวนมากที่ใช้ใน URL ไม่ได้หรืออ่านบอกต่อกันได้ยาก
      เท่าที่จำได้ เราตัด 0, l, 1 ออกทั้งหมด และน่าจะถือว่าการคัดลอกเขียนตามจะทำกันเป็นตัวพิมพ์ใหญ่ทั้งหมดหรือพิมพ์เล็กทั้งหมด
      0o ไม่ใช่ปัญหา และ 1L ก็เหมือนกัน
  • การเข้ารหัสแบบ Crockford ไม่ค่อยถูกใจผม
    เวลาไปซัพพอร์ตหรือวิเคราะห์ค่าที่เข้ารหัสด้วยวิธีนี้จริง ๆ รู้สึกว่ามันเป็นความผิดพลาดที่เห็นได้ชัด
    ตัวอักษรชุดนี้ถูกออกแบบมาสำหรับเป้าหมายที่ในความเป็นจริงพบไม่บ่อย เช่น การอ่านตัวระบุทางโทรศัพท์ และมันนำความกำกวมเข้ามา
    ถ้าต้อง grep ในล็อกหรือเทียบข้ามระบบโดยอาศัยสตริงที่เข้ารหัสเอง มันคือหายนะ และการอนุญาตให้มีขีดกลางก็ยิ่งเป็นต้นเหตุหลักของปัญหาการคัดลอก/วางและการตัดบรรทัด
    การพิมพ์ object identifier ด้วยมือตรง ๆ นั้นไม่ค่อยเกิดขึ้น แต่การคัดลอก/วางข้ามแอป แชต และฟอรัม การส่งต่อทางอีเมล และการค้นหาในไฟล์ล็อกเป็นเรื่องปกติ
    ในเงื่อนไขแบบนี้ ความอ่านออกเสียงได้แทบไม่มีความหมาย ความไม่แยกแยะตัวพิมพ์ใหญ่เล็กกลับเป็นอุปสรรค และสิ่งที่ต้องการคือความสม่ำเสมอกับความทนทานต่อการวางและการตัดบรรทัด
    base58 เป็นการเข้ารหัสแบบหนึ่งต่อหนึ่งที่ตอบโจทย์นี้ได้ดีกว่าและยังสั้นกว่าอีกด้วย
    โดยได้แรงบันดาลใจจาก Stripe ผมใช้ UUID ที่เข้ารหัสแบบ base58 พร้อม type prefix เป็น object identifier มาหลายปีแล้ว เช่น user_1BzGURpnHGn6oNru84B3Ri
    แต่ก็ต้องยอมรับว่า base32 ของ Douglas Crockford ถูกออกแบบไว้เมื่อ 20 ปีก่อน ในยุคที่สภาพแวดล้อมการใช้งานแตกต่างจากตอนนี้มาก

    • ผมก็เคยชั่งใจระหว่าง base58 กับ base64url และชอบการเข้ารหัสที่สั้นกว่าด้วย
      แต่เพราะไม่อยากตั้งอยู่บนสมมติฐานว่าต้องแยกตัวพิมพ์ใหญ่เล็ก สุดท้ายเลยเอนเอียงมาทาง base32
      ตัวอย่างเช่น คุณอาจอยากใช้ ID เป็นชื่อไฟล์ และอาจอยู่ในสภาพแวดล้อมที่ติดข้อจำกัดของระบบไฟล์แบบไม่แยกตัวพิมพ์ใหญ่เล็ก
      TypeID ใช้อักษร Crockford แบบตัวพิมพ์เล็กเสมอ ไม่ได้ใช้กฎการเข้ารหัสของ Crockford ทั้งหมด
      TypeID ไม่อนุญาตให้มีขีดกลาง และไม่ยอมให้เข้ารหัส ID เดียวกันได้หลายแบบด้วยการใช้ตัวอักษรที่กำกวมสลับกัน
    • เห็นด้วยว่าการอ่านตัวระบุทางโทรศัพท์เป็นเรื่องไม่บ่อย
      แต่บางครั้งก็มีกรณีที่ต้องพิมพ์เองจากตัวระบุในภาพหน้าจอหรือการแชร์หน้าจอ หรือจากอุปกรณ์อีกเครื่องที่ดึงตัวระบุออกมาได้ไม่ง่าย
    • base62 ที่ base58 หรือ KSUID ใช้นั้นมีข้อดีมาก
      Crockford base32 ก็ใช้งานได้ แต่ไม่ถึงกับชอบมากนัก
      ส่วนตัวแล้วตัวเลือกอันดับหนึ่งคือใช้ KSUID ที่มี type prefix
      จะได้ ID แบบ 160 บิตที่จัดเรียงแบบ K-sortable ได้ในรูปแบบ base62 และถ้าไม่ได้จำเป็นต้องใช้ ID 128 บิตเพราะเหตุผลด้านความเข้ากันได้ ก็ถือว่าเหมาะดี
    • ไม่แน่ใจว่าใน Crockford Base32 มีขีดกลางตรงไหน: https://en.wikipedia.org/wiki/Base32#Crockford's_Base32
      base32 ที่ผมชอบคือ z-base-32 เพราะรู้สึกว่าสบายตากว่า: https://philzimmermann.com/docs/human-oriented-base-32-encod...
      ปัญหาใหญ่ที่สุดของ base58 คือมันเหมาะกับจำนวนเต็ม แต่เหมาะน้อยกว่าสำหรับข้อมูลไบนารีแบบสุ่มอย่างกุญแจเข้ารหัส และสตริงที่แยกตัวพิมพ์ใหญ่เล็กก็ดูไม่ค่อยสวย
  • เรียบดี
    ชอบ prefix แบบ “type-safe”
    ใน ORM ของเราเคยใช้วิธีคล้ายกัน โดยติดแท็กกับ integer ID ใน DB ตามประเภทเอนทิตีโดยอัตโนมัติ และเรียกมันว่า tagged ids: https://joist-orm.io/docs/advanced/tagged-ids
    เราใช้ : เป็นตัวคั่น แต่ก็แอบเสียดายอยู่นิดหน่อยที่ไม่ได้ใช้ _ ในแง่ของการคัดลอก/วางด้วยการดับเบิลคลิก
    ในทางทฤษฎี การให้ Joist แปลง “UUID column ใน DB” ให้เป็น “TypeID ในโดเมนโมเดล” ก็น่าจะทำได้ง่ายมาก แต่ตอนนี้คงยังทำไม่ได้ด้วยการตั้งค่าในฝั่งผู้ใช้อย่างเดียว
    ถึงอย่างนั้นก็ยังเป็นไอเดียที่ดี

    • Reddit ก็ใช้แนวคล้ายกัน แต่ปรับให้เหมาะกับความยาวของสตริง
      ID ขององค์ประกอบจะมีรูปแบบอย่าง t3_15bfi0 โดย t3_ คือ type prefix
      t3 คือโพสต์, t1 คือคอมเมนต์, t5 คือซับเรดดิต และส่วนที่เหลือคือการเข้ารหัส base36 ของ primary key แบบเพิ่มอัตโนมัติ
    • ระบบ internal ID แบบมี type ของบริษัทเรา ตอนแรกใช้ colon เป็นตัวคั่น แต่ไม่นานก็เปลี่ยนเป็นจุด(.)
      colon จะถูก %-encode ใน URL ทำให้ ID ยาวขึ้น และ URL ทั้งดูน่าเกลียดทั้งยาวจนน่ารำคาญมาก
  • UUIDv7 เป็นของฮิตบน HN มาหลายปีแล้ว เลยสงสัยว่าเมื่อไรจะกลายเป็นมาตรฐานจริงจังเสียที และเมื่อไรไลบรารี, ฐานข้อมูล และ ecosystem ที่เหลือจะรองรับแบบ native

    • ไม่แน่ใจว่าคาดหวังการรองรับแบบไหน
      ซอฟต์แวร์ส่วนใหญ่ไม่ได้สนใจ specific bit ภายใน UUID ดังนั้นจะใช้ตั้งแต่วันนี้เลยก็ได้
      ถ้ามีซอฟต์แวร์ที่สนใจ bit เหล่านั้น ก็แค่ทำตัวให้เหมือน UUIDv4 ได้ และ bit แบบนั้นก็สุ่มออกมาได้อยู่แล้ว
      ถ้าต้องมีขั้นตอนการสร้างเฉพาะ ก็เขียนเองได้และไม่ได้ยากอะไร
    • ดูเหมือนจะอยู่ในช่วงท้ายของการทำมาตรฐานโดย IETF: https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122b...
    • มันผ่านทั้งร่างและการปรับปรุงมาแล้ว ใกล้เป็นมาตรฐานมาก และตอนนี้ก็มีหลายไลบรารีที่รองรับหรือกำลังเพิ่มฟีเจอร์ใหม่
      ยกตัวอย่าง ผมดูแล Dart UUID library อยู่ ซึ่งใน beta major release ล่าสุดมี v6, v7 และ custom v8 รวมอยู่ด้วย
      น่าจะมีรายการ implementation อยู่ที่ไหนสักแห่ง และผมก็อยู่ในหน้านั้นในรายชื่อผู้ดูแลไลบรารี เลยได้รับการแจ้งจากผู้เขียนทุกครั้งที่มีร่างใหม่ออกมา
  • ชอบตรงที่บอกว่า “สามารถคัดลอก/วางด้วยการดับเบิลคลิกได้”
    รายละเอียดเล็กๆ สำคัญ

  • ผมมีปัญหากับ UUID อยู่นิดหน่อย
    ไม่เข้าใจว่าทำไมต้องทำพิธีรีตองเรื่อง UUID versioning แทนที่จะเอา เวลา + ค่าสุ่ม มารวมกันเฉยๆ
    ถ้า locality ไม่สำคัญ ก็ใช้เลขสุ่ม 128 บิตตรงๆ ไปเลยก็ได้
    จากประสบการณ์ ดูเหมือนคนส่วนใหญ่จะคิดว่าต้องเก็บ UUID เป็นเลขฐาน 16 แบบอ่านได้โดยมนุษย์ แถมยังรวม hyphen เข้าไปด้วย
    เปลืองพื้นที่ทั้งในฐานข้อมูล, เครือข่าย และหน่วยความจำมากเกินไป

    • หลายคนก็คิดแบบเดียวกัน
      อย่าง ULID https://github.com/ulid/spec ก็สั้นกว่าและเก็บเวลาไว้ด้วย จึงเรียงตามลำดับพจนานุกรมได้
    • ฝั่ง UUID มักมีแนวโน้มจะพูดเหมือนมี use case เดียวที่ใช้แทนทั้งหมดได้ และเพราะงั้นก็มี UUID format ที่ดีที่สุดอยู่แบบเดียว
      แต่ความจริงก็เหมือนปัญหาทางวิศวกรรมอื่นๆ คือ เขียน requirement ออกมาแล้วดูว่าอะไรตอบโจทย์
      การอ่านข้อดีของหลายๆ format มีประโยชน์ เพราะทำให้เราได้ใช้บทเรียนที่คนอื่นเรียนมาอย่างยากลำบากแล้ว
      ความสามารถในการ sort ได้และ locality ตามเวลาอาจไม่ใช่สิ่งที่นึกถึงเองตามธรรมชาติ แต่ถ้าจำเป็น การรู้ไว้ก่อนย่อมดีกว่าเพิ่งมารู้ตอนอีก 4 ปีให้หลังว่าคุณทิ้งข้อมูลนั้นไปแล้ว
      UUID บาง format ยังใส่ปัญหาด้านความปลอดภัยเล็กๆ เข้ามาเองด้วย เช่น การรั่วของ MAC address ใน UUID v1 ซึ่งควรหลีกเลี่ยง
      ถ้า use case ของคุณเหมาะกับวิธีเดิมที่มีอยู่แล้ว ก็ใช้มันไปได้เลย
      ถ้าไม่ใช่ ก็ไม่ต้องกังวลมาก
      โดยส่วนตัว ในที่ที่ผมใช้ UUID มากที่สุด เราระบุไว้แค่ว่า “ส่ง unique string มาเถอะ จะใช้ UUID library อะไรก็ได้ถ้าคุณสบายใจ”
      ระบบนี้ดูเหมือนจะมี format เฉพาะแตกต่างกันไปตามแหล่งข้อมูล แต่ก็ไม่เป็นไร
      ปริมาณงานอยู่ระดับหลายหมื่นรายการต่อวัน เลยไม่ได้มีปัญหาเรื่องสเกล และก็ไม่จำเป็นต้อง sort ตาม UUID
      มันไม่ใช่ identifier จริงๆ เท่าไร แต่เป็น token เฉพาะที่ผู้ส่งสร้างขึ้นมาสำหรับแต่ละข้อความ เพื่อใช้ตรวจจับการมาถึงซ้ำในปลายทางล่างของระบบ heterogeneous ที่มีหลายคิว
      ไม่จำเป็นต้อง unique แบบ global ด้วยซ้ำ แค่ unique ภายใน shard เล็กๆ ก็พอ และตามหลักแล้วจะวนกลับมาซ้ำเมื่อเวลาผ่านไปก็ยังได้
      เพียงแต่เพื่อความเรียบง่าย เราเลยคงระบบให้ unique ตลอดทั้งช่วงเวลาไปเลย
      ถ้าสร้างเอง เราจะอ่านข้อมูลจาก /dev/urandom แล้วเข้ารหัสเป็น base64 และเลือกขนาดที่ไม่ลงท้ายด้วย ==
      อันนี้ก็ไม่ใช่เพราะปัญหาจริงอะไรหรอก แต่เพราะเหตุผลด้านความสวยงามล้วนๆ
    • การเอาเวลาและค่าสุ่มมารวมกัน ไม่ได้การันตีความเป็นเอกลักษณ์แบบ global
    • โดยส่วนตัว ผมมองว่ามาตรฐาน UUID ที่มีคุณค่าจริงมีแค่ v4 ซึ่งเป็นค่าสุ่มล้วน
      ถึงอย่างนั้นก็ยังไม่เข้าใจอยู่ดีว่าทำไมต้องมี hyphen และมองว่าเวอร์ชันอื่นๆ ก็ไม่ได้มีความหมายมากนัก
    • จุดแย่ที่สุดของ hyphen คือมันทำให้ดับเบิลคลิกแล้วคัดลอก/วางทั้งก้อนได้ไม่ง่าย
  • หากไม่ได้จำเป็นต้องใช้ UUIDv7 หรือ UUID จริง ๆ https://github.com/segmentio/ksuid มี key space ที่ใหญ่กว่ามาก
    ถ้าต้องการ namespace ก็เพียงเติม string prefix เข้าไป และความเป็นไปได้ที่จะเกิดการชนกันของ KSUIDก็น้อยกว่า UUID ทุกเวอร์ชันมาก
    ในบรรดาตัวสร้าง ID แบบทั่วไปที่มี timestamp ซึ่งจัดเรียงได้ ksuid ดีที่สุด และมีไลบรารีสำหรับภาษาส่วนใหญ่
    UUID v1~v7 สิ้นเปลืองเกินไป

    • ได้ปล่อยแพ็กเกจ JavaScript ขนาดบางสำหรับสร้าง KSUID ที่มี type prefix ไว้แล้ว
      ถ้ารับบิตที่เพิ่มขึ้นได้ ซึ่งส่วนใหญ่ก็น่าจะได้ KSUID เป็นรูปแบบที่ยอดเยี่ยม
      [1] https://github.com/sophiabits/resource-id
    • เหตุผลที่ย้ายไปใช้ ULID คือมันเป็นตัวพิมพ์เล็กทั้งหมดเสมอ จึงจัดการแบบไม่แยกตัวพิมพ์เล็กใหญ่ได้
    • ผมดูแลไลบรารี ksuid ยอดนิยมอยู่หลายตัวและใช้งานจริงด้วย จึงชอบ ksuid มาก
      แต่ปัญหาใหญ่ของ ksuid คือมันมี 160 บิต จึงไม่เข้ากับ native UUID type ของฐานข้อมูล เช่น PostgreSQL ทำให้มีต้นทุนด้านประสิทธิภาพ
      1: https://github.com/svix/rust-ksuid
      2: https://github.com/svix/python-ksuid
  • K-sortable เป็นแนวคิดที่ยอดเยี่ยม และคีย์ที่จัดเรียงได้แบบหลวม ๆ ช่วยแก้ use case ได้หลายแบบ
    ชอบ representation แบบสตริงที่บีบอัดและมี type ด้วย
    แต่ก็เป็นห่วงว่าผลข้างเคียงที่ไม่ได้ตั้งใจของ UUID v7 อาจทำให้เกิดปัญหาด้านความปลอดภัยมากขึ้น
    คนเราไม่ควรใช้ UUID เป็น token และไม่ควรใช้เป็น primary key ของ DB แบบนั้นเช่นกัน แต่ในทางปฏิบัติก็ทำกัน
    UUID v4 แท้จริงแล้วแทบจะเป็นเลขสุ่มเชิงเข้ารหัส ดังนั้นช่องโหว่ด้านความปลอดภัยจำนวนมากจนถึงตอนนี้จึงเหมือนรอดมาได้โดยบังเอิญ
    เคยคิดว่าใน UUID v7 ข้อมูลสุ่มจริงถูกลดเหลือ 32 บิต และคิดว่าต้องให้ความรู้แก่นักพัฒนาให้ดีว่า UUID สามารถเดาได้
    แก้ไข: ดูเหมือนว่าผมจะเข้าใจผิดเรื่องความสามารถในการคาดเดาของ UUID v7
    ในร่างระบุว่าแนะนำให้ใช้ CSPRNG สำหรับบิตสุ่ม มีเอนโทรปีอย่างน้อย 74 บิต และออกแบบมาให้ “คาดเดาไม่ได้”
    แม้จะบอกว่าในงานด้านความปลอดภัยให้ใช้ “UUID v4” แต่ก็น่าจะหมายถึงนัยที่เกี่ยวกับ timestamp

    • มีปัญหาด้าน usabilityที่น่าสนใจ
      ถ้าชื่อเป็น UUIDv4 กับ UUIDv7 ดูเหมือนว่าจะเกิดความสับสนไม่รู้จบว่าตัวไหนเหมาะกับฐานข้อมูลและตัวไหนเหมาะกับ one-time token เพราะต้องคอยจำอยู่ตลอดหรือไม่
      และก็นึกไม่ออกว่ามีวิธีแก้ที่ยังคง backward compatibility ได้ดีไหม
      ใน Elixir เวลาสร้าง v4 UUID จะใช้ฟังก์ชัน UUID.uuid4()
      ในทางทฤษฎีก็อาจสแกนโค้ดเพื่อดูการใช้งานได้ แต่เรื่องพวกนี้ล้วนเพิ่มโอกาสเกิดข้อผิดพลาด
    • พอมองเห็นบางกรณีที่น่าใช้ได้อยู่ แต่ที่ผ่านมาเวลาพบ UUID แบบกึ่งลำดับอย่าง v1 หรือ v5 ก็มักถูกใช้งานผิดเสมอ
      แบบอิงแฮชอย่าง v3 ก็เช่นกัน
      v4 นั้นเรียบง่ายและมีโอกาสถูกใช้ผิดน้อย