3 คะแนน โดย GN⁺ 2024-08-27 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • UUID มีทั้งหมด 8 เวอร์ชัน ตั้งแต่ v1 ถึง v8 และการที่ตัวเลขสูงกว่าไม่ได้หมายความว่าใหม่กว่าหรือดีกว่า แต่เป็นวิธีการสร้างที่แตกต่างกันซึ่งกำหนดไว้ใน RFC 9562
  • ตัวเลือกที่ใช้กันทั่วไปจะเหลือหลัก ๆ คือ v4 และ v7 โดย v4 เป็นค่าเริ่มต้นสำหรับ ID แบบสุ่ม ส่วน v7 เหมาะเมื่อจำเป็นต้องเรียงลำดับตามเวลาที่สร้าง
  • v1 และ v6 ใช้วัตถุดิบเดียวกัน แต่ v6 เป็นรูปแบบที่สลับลำดับฟิลด์เพื่อให้เมื่อจัดเรียงแล้วจะเป็น ลำดับตามเวลาที่สร้าง
  • v3 และ v5 สร้าง UUID โดยแฮชข้อมูลนำเข้า โดย v3 ใช้ MD5 และ v5 ใช้ SHA-1 และสามารถใช้ DNS กับ URL เป็นค่าป้อนเข้าได้
  • หากเป็นไปได้ แนวทางที่ใช้ได้จริงคือใช้ v7 แทน v1·v6, ใช้ v5 เมื่อต้องการ UUID ที่อิงจากข้อมูลนำเข้า และพิจารณา v8 เมื่อต้องการ UUID แบบกำหนดเองทั้งหมด

วิธีสร้าง UUID ตามแต่ละเวอร์ชัน

  • UUID มีเวอร์ชันตั้งแต่ v1 ถึง v8 และทั้งหมดถูกกำหนดไว้ใน RFC 9562
  • UUID แบบอิงเวลา

    • UUID Version 1 สร้างจาก timestamp, ตัวนับแบบ monotonic และ MAC address
    • UUID Version 6 ใช้ข้อมูลเดียวกับ v1 แต่สลับลำดับเพื่อให้เมื่อจัดเรียงแล้วเป็นลำดับตามเวลาที่สร้าง
    • UUID Version 7 สร้างจาก timestamp และข้อมูลแบบสุ่ม
  • UUID แบบสุ่ม·กำหนดเอง

    • UUID Version 4 สร้างจาก ข้อมูลแบบสุ่ม ทั้งหมด และใกล้เคียงกับรูปแบบที่หลายคนนึกถึงเมื่อพูดถึง UUID
    • UUID Version 8 เป็นรูปแบบที่ผู้ใช้กำหนดเองได้ทั้งหมด ยกเว้นฟิลด์ version/variant ที่ทุกเวอร์ชันต้องมี
  • UUID แบบอิงแฮชของข้อมูลนำเข้า

    • UUID Version 3 สร้างจาก MD5 hash ของข้อมูลที่ผู้ใช้ให้มา
      • ใน RFC มี DNS และ URL เป็นค่าป้อนเข้าที่เป็นไปได้
    • UUID Version 5 สร้างจาก SHA-1 hash ของข้อมูลที่ผู้ใช้ให้มา
      • เช่นเดียวกับ v3 สามารถใช้ DNS และ URL เป็นค่าป้อนเข้าได้
  • UUID ที่สงวนไว้

    • UUID Version 2 ถูกสงวนไว้สำหรับ security ID และไม่มีรายละเอียดที่เป็นที่รู้จัก

เกณฑ์การเลือกใช้งานจริง

  • ในกรณีส่วนใหญ่ ตัวเลือกคือ v4 หรือ v7
  • ถ้าต้องการเพียง ID แบบสุ่ม v4 เหมาะเป็นตัวเลือกเริ่มต้น
  • หากต้องสามารถจัดเรียง ID ได้ อาจพิจารณา v7
    • ตัวอย่างเช่น เมื่อใช้ UUID เป็น database key v7 อาจเป็นตัวเลือกหนึ่ง
  • v5 หรือ v8 ค่อนข้างเหมาะกับกรณีที่ต้องการใส่ข้อมูลของตัวเองไว้ใน UUID
    • สำหรับกรณีประเภทนี้ โดยมากผู้ใช้มักรู้อยู่แล้วว่าตัวเองต้องการสถานการณ์แบบใด
  • ตาม RFC นั้น v7 ปรับปรุงจาก v1 และ v6 ดังนั้นหากเป็นไปได้ควรใช้ v7 แทน v1·v6
    • หากจำเป็นต้องใช้ v1 หรือ v6 จริง ๆ ก็สามารถใช้ v6 ได้
  • v2 ถูกสงวนไว้สำหรับงานด้านความปลอดภัยที่ไม่ได้ระบุรายละเอียด
  • v3 ถูกแทนที่ด้วย v5 ซึ่งใช้แฮชที่แข็งแรงกว่า และหากจำเป็นต้องใช้ v3 ก็มีแนวโน้มว่าผู้ใช้จะรู้อยู่แล้วว่าทำไมจึงต้องใช้

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

 
GN⁺ 2024-08-27
ความคิดเห็นจาก Hacker News
  • การบอกว่าไม่มีรายละเอียดของ UUID v2 นั้นเป็นเรื่องเมื่ออ่านแค่ RFC ที่ขึ้นชื่อว่าคลุมเครือมากเท่านั้น: https://pubs.opengroup.org/onlinepubs/9696989899/chap5.htm#t...
    แม้แทบจะไม่เคยเจอ แต่ก็มี version 0 UUID อยู่ด้วย ควรพูดถึงเพราะมันเป็นที่มาของบิตที่สงวนไว้ ซึ่งทำให้ “เวอร์ชัน” อื่น ๆ ในภายหลังสามารถนิยามแบบเข้ากันได้ ผมรวบรวมการสำรวจที่เกี่ยวข้องไว้ในไลบรารี UUID ของผม: https://github.com/okeeblow/DistorteD/blob/NEW%E2%80%85SENSA...
    เพราะมันเท่ดีเลยตัดสินใจรองรับ แต่ยังต้องตัดสินใจเรื่อง date rollover และวิธีจัดการ Apollo UID ที่เก่ากว่านั้น

    • ถ้าพูดอย่างเป็นธรรม RFC9562 อ้างถึงเอกสาร สเปก UUID Version 2 ไว้สองฉบับ เพียงแต่ RFC4122 ดูเหมือนข้อความเข้ารหัสเกินไปสำหรับรสนิยมของผม
      ตอนนี้กำลังเริ่มทำ RFC เชิงให้ข้อมูลเกี่ยวกับประเภท UUID ในอดีตที่นิยามอยู่ในพื้นที่ Variant 0-7 เพื่อช่วยให้ผู้คนเข้าใจ หากอยากร่วมอภิปรายหรือตรวจถ้อยคำ ดูได้ที่ https://github.com/yocto/draft-yocto-uuid
  • รายละเอียดหาได้ภายใน 2 นาที กดลิงก์ในบทความไปยังส่วนที่นิยาม DCE ใน RFC 9562 แล้วตามลิงก์แรกในย่อหน้านั้นเข้าไปยังสเปก จากนั้นค้นหา “UUID” แล้วไปที่ภาคผนวก A ก็มีทุกอย่าง
    ชื่ออาจชวนสับสนว่า “Universal Unique Identifier” แต่เนื้อหาที่ต้องการมีครบ ควรกดดูอย่างน้อยลิงก์ที่ตัวเองใส่ไว้ในบทความ

    • สิ่งที่เรียนรู้ได้จากตรงนี้มีสองอย่าง อย่างหนึ่งคือการอ่าน สเปก v2 UUID และอีกอย่างคือการดึงข้อมูลบริบทขณะสร้างออกมาจาก UUID
      จากบริบทที่อธิบายว่าเวอร์ชันอื่น ๆ ถูกสร้างจากองค์ประกอบเฉพาะบางอย่าง ประโยคนั้นชัดเจนว่าหมายถึงอย่างหลัง แม้ประโยคจะคลุมเครืออยู่บ้าง แต่คงไม่ถึงขั้นชวนให้เข้าใจผิด
    • ผมก็ทำตามกระบวนการเดียวกันเป๊ะ และรู้สึกว่าผู้เขียนไม่ได้ใส่ใจรายละเอียดมากนัก เลยเลิกอ่านต่อทันที
      แต่ภาคผนวกก็อ่านสนุกเหมือนภาพ snapshot ของยุคนั้น
    • บางทีบทความนี้อาจเขียนโดย language model ก็ได้
  • ถ้ามี มาตรฐาน UUID แบบสั้น อย่าง 73WakrfVbNJBaAmhQtEeDv หรือ bK7nP9xM ก็คงดี
    พูดให้เคร่งครัดก็คงไม่ใช่ UUID เพราะอาจซ้ำกันที่ไหนสักแห่งได้ แต่ผมอยากได้ชุดมาตรฐาน ID ที่สุ่มและสั้นพอจะจำได้

    • ที่ใกล้เคียงที่สุดนึกถึง ULID เป็น base32 ยาว 26 ตัวอักษร จึงสั้น มี 128 บิต และเรียงตามลำดับพจนานุกรมได้
      ผมคิดว่าเหตุผลที่ไม่มีมาตรฐานที่ได้รับความนิยมกว่านี้คือคุณต้องยอมเสียอะไรบางอย่าง 128 บิตมีความเสี่ยงชนกันต่ำสำหรับแทบทุกการใช้งาน แต่ยิ่งเล็กลงก็ยิ่งต้องพิจารณาสถานการณ์เฉพาะและผลกระทบจากการชนกัน ทำให้ทำเป็นมาตรฐานได้ยาก หากใช้อินโค้ดแบบอื่นอย่าง base64 หรือ base85 ก็จะสั้นลง แต่ต้องแลกกับการแยกตัวพิมพ์ใหญ่/เล็ก ความปลอดภัยสำหรับ URL และอื่น ๆ: https://github.com/ulid/spec
    • ถ้าต้องการ UUID ที่กระชับกว่าเดิม สามารถแปลง UUID เดิมเป็น URL-safe base64 เพื่อแสดงผลในรูปแบบ 22 ตัวอักษรได้
      มันเป็นเพียงการแสดงผลอีกแบบของ UUID เดียวกันและแปลงกลับได้ สุดท้าย UUID ก็คือค่า 128 บิต จึงเป็นการเขียนแทนมากกว่าการแปลงจริง ๆ
    • Sqids อาจเป็นคำตอบก็ได้ มันสร้าง ID ที่สั้นกว่า UUID มาก แต่ไม่ได้ unique แบบสากล และสร้างจากลำดับจำนวนเต็ม: https://sqids.org/
    • โดยปกติจะสร้างเลขสุ่ม N บิตแล้วเข้ารหัสเป็น base58 เลือก N ได้ตามต้องการ
      แม้จะเสียข้อดีด้านการเรียงลำดับแบบ monotonic ที่มีใน UUID บางเวอร์ชันไป แต่ base58 ปลอดภัยสำหรับ URL และไม่มีอักขระพิเศษ ค่ายังคงเก็บเป็นไบนารีได้ เช่น ใน Postgres สามารถใช้ bytea แทนคอลัมน์ข้อความได้
    • กำลังมีงานมาตรฐานสำหรับ เทคนิคการเข้ารหัสทางเลือก ที่ใช้แสดง UUID 128 บิตให้อยู่ในรูปข้อความที่สั้นลง
      ดูการอภิปรายได้ที่นี่: https://github.com/uuid6/new-uuid-encoding-techniques-ietf-d...
  • timestamp ของ UUID v7 เป็นการเปลี่ยนแปลงครั้งใหญ่สำหรับ Databend เราใช้มันค้นหาไฟล์ metadata ใน AWS S3 อย่างรวดเร็วด้วย timestamp ทำให้งานอย่าง vacuum เร็วขึ้นมาก
    PR: https://github.com/datafuselabs/databend/pull/16049

    • น่าสนใจดี แต่ผมไม่ค่อยนึกถึงการใช้ UUID เพื่อค้นหาตามเวลา ถ้าเป็นผมคงมี ฟิลด์ timestamp แยกต่างหาก
      ข้อดีใหญ่ของ UUID ที่เรียงตามเวลาคือ locality ที่ดีขึ้น เวลาใส่รายการใหม่เข้า index โดยมากจะกลายเป็นการต่อท้าย ซึ่งอาจถูกกว่าการแทรกแบบสุ่ม อย่างไรก็ดี อาจทำให้เกิด contention มากขึ้น จึงน่าพิจารณาไฮบริดที่ใส่บิตสุ่มบางส่วนไว้หน้า timestamp เพื่อสร้าง “shard” ที่เรียงลำดับได้ การอ่านก็มักกระจุกอยู่ที่ข้อมูลล่าสุดด้วย ดังนั้นการที่ข้อมูลล่าสุดอยู่ในที่เดียวและขึ้น cache ได้ดีจึงมีประโยชน์
    • เหมาะมากกับ key-value store อย่าง DynamoDB ด้วย ดูเรียบร้อยกว่าคีย์ผสมที่เติม timestamp หรือวันที่ ISO เป็น prefix มาก และดีกว่าการเปลือง secondary index สำหรับ timestamp
  • เข้าใจวัตถุประสงค์ของ uuid2 ได้ยาก ไม่รู้ด้วยซ้ำว่ายังมีประเภทแบบนี้อยู่อีก และเห็น uuid2 ครั้งแรกตอนขอให้ Xandr ลบข้อมูลส่วนบุคคลของฉัน: https://news.ycombinator.com/item?id=40913915
    อ่าน Wikipedia แล้วก็ยังไม่ค่อยเข้าใจว่าทำไมถึงสร้างสิ่งที่เรียกว่า “ตัวระบุเอกลักษณ์แบบสากล” แล้วยังต้องมีหลายประเภท และทำไมบางประเภทถึงถูกทำมาให้ตามรอยไปถึง PC ต้นทางได้ สงสัยว่าการผสมส่วนหนึ่งของรหัส MAC เข้าไปทำให้ uuid2 ใกล้เคียงการสุ่มมากขึ้นหรือมีเหตุผลอื่นกันแน่ จากมุมมองความเป็นส่วนตัว ก็สงสัยด้วยว่าทำไมไม่ใช้แค่ตัวระบุยาว ๆ ที่มีอักขระให้เลือกจำนวนมากเพื่อทำให้โอกาสซ้ำแทบเป็นศูนย์ไปเลย

    • วัตถุประสงค์ดั้งเดิมคือการระบุข้อความใน สถาปัตยกรรมการประมวลผลแบบกระจายของ Apollo UID และต่อมา UUID เป็นวิธีที่ย้อนกลับได้สำหรับทำเครื่องหมายจุดตัดของสองมิติ
      เครื่องสองเครื่องใด ๆ จะสร้าง UID/UUID เดียวกันจากอินพุตสองค่าเดียวกัน และฝ่ายที่ได้รับข้อความที่ถูกระบุแล้วสามารถย้อนตัวระบุกลับไปเป็นองค์ประกอบเดิมได้ เพราะถูกออกแบบเป็นป้ายกำกับของข้อความชั่วคราว สองมิติจึงเป็นเวลาและ ID ฮาร์ดแวร์ โดยแรกเริ่มใช้หมายเลขซีเรียลของ Apollo และต่อมาจึงใช้ที่อยู่ฮาร์ดแวร์ Ethernet เป็นต้น
      ผมคิดว่าความสับสนส่วนใหญ่มาจากการที่ในการใช้งาน AEGIS ช่วงแรก วิศวกรของ Apollo เริ่มใช้ UID แบบ “canned” หรือก็คือแบบคงที่และเป็นที่รู้จักกันดีสำหรับการระบุในระบบไฟล์ เมื่อเวลาผ่านไป การใช้งาน UUID โดยทั่วไปได้ย้ายจากตัวระบุชั่วคราวที่ตั้งใจให้ซ้ำกันได้ ไปเป็นตัวระบุแบบ canned ที่ต้องหลีกเลี่ยงการซ้ำกันโดยสิ้นเชิง และสองมิติก็กลายเป็นการสุ่มกับการสุ่มอีกอย่าง
      ประวัติซับซ้อนกว่านั้น Microsoft จ้างหนึ่งในคนสำคัญของ Apollo ไปทำ MSRPC สำหรับ Windows NT จึงเกิด GUID ขึ้นด้วย GUID มีการจัดวางฟิลด์ต่างจาก UUID และตรงข้ามกับที่เอกสารจำนวนมากบอก มันไม่ใช่ mixed-endian Microsoft ชอบใช้ canned GUID ไม่ใช่แค่สำหรับ GUID ระบุข้อความ RPC ชั่วคราว แต่รวมถึงแทบทุกอย่างที่ต้องมีตัวระบุที่เป็นที่รู้จักกันดี เช่น คลาส COM, media codec เป็นต้น ตัวอย่าง: https://gix.github.io/media-types/
      ขอโทษที่ลิงก์คลังของผมสองครั้งในช่องคอมเมนต์เดียวกัน แต่ผมเริ่มสรุปประวัตินี้ไว้ใน README ของไลบรารี UUID ของผมแล้ว และควรกลับไปเขียนต่อ Apollo เริ่มในปี 1980 ส่วนร่าง RFC ของ Leach/Salz UUID เพิ่งออกมาในปี 1998 ดังนั้นยังมีเรื่องราวมหาศาลที่ไม่อยู่ในมาตรฐานสมัยใหม่: https://github.com/okeeblow/DistorteD/blob/NEW%E2%80%85SENSA...
  • UUID v4 เป็นแค่ตัวสร้างไบต์สุ่มที่ใส่ขีดกลางในตำแหน่งที่กำหนดเท่านั้น ไม่จำเป็นต้องใช้มันก็ได้ ถ้าสร้างไบต์สุ่มเองก็ประหยัดพื้นที่ได้
    ขีดกลางที่ไม่จำเป็นและข้อมูลเวอร์ชันอะไรพวกนี้ก็จะลดลงด้วย

    • UUID คือ ตัวเลข 128 บิต และรูปแบบสตริงที่มีขีดกลางก็เป็นเพียงหนึ่งในหลายวิธีในการแทนตัวเลขนั้น
      คล้ายกับที่ที่อยู่ IPv4 เป็นตัวเลข 32 บิต และ “สี่ชุดคั่นด้วยจุด” เป็นหนึ่งในรูปแบบการแทนค่า ถ้าคิดว่า UUID เป็นรูปแบบสตริง แปลว่าจับแนวคิดพื้นฐานที่สุดเกี่ยวกับ UUID ผิดแล้ว แม้แค่อยากได้ตัวระบุแบบสุ่ม ผมก็คิดว่าการมีบิตแฟล็กเล็ก ๆ ใน UUID แบบสุ่มที่บอกว่า “อันนี้ตั้งใจให้เป็นแบบสุ่ม” เป็นเรื่องดี มันมีประโยชน์เมื่อเจอตัวระบุเดี่ยว ๆ ที่ไม่มีบริบท
    • UUID v4 ยังตั้งค่า 4 บิต เป็นค่าคงที่เพื่อระบุว่าเป็นเวอร์ชัน 4
      จะถกเถียงได้ว่าการสร้าง namespace แยกกันระหว่างวิธีสร้างหลายแบบมีประโยชน์หรือไม่ แต่ตัวสร้างเลขสุ่มทั่วไปมีโอกาสสร้าง UUIDv4 ที่ถูกต้องเพียง 1/16 เท่านั้น แน่นอนว่าถ้าอยากสร้างตัวสร้าง UUID เอง การตั้งบิตให้ถูกต้องเป็นเรื่องเล็กน้อย
    • ถูกต้อง แต่สิ่งที่ดึงดูดนักพัฒนาส่วนใหญ่คือมันใช้งานง่าย แทบทุกภาษามี ไลบรารี UUID ที่ทำงานได้ในบรรทัดเดียว
      ถ้าเป็น Go ก็แค่ uuid.New().String() แต่ถ้าจะอ่านข้อมูลสุ่มด้วย crypto/rand แล้วแปลงเป็น base64 หรือ hex ต้องใช้บรรทัดและความพยายามมากกว่า
  • แนะนำว่าอย่าใช้ เวอร์ชันที่อิง MAC ในทางทฤษฎีอาจรวมทุกแบบยกเว้น v4 และ v7 แต่ v1 แย่ที่สุด
    v3 ก็มีปัญหาว่า MD5 ถูกเจาะอย่างรุนแรงแล้ว

    • MD5 นั้น “ถูกเจาะ” ในฐานะฟังก์ชันแฮชเชิงเข้ารหัส แต่ในฐานะฟังก์ชันแฮชที่ไม่ใช่เชิงเข้ารหัสก็ยังใช้ได้ดีโดยสมบูรณ์
  • ไม่รู้รายละเอียดนอกจากเวอร์ชัน 4 แต่สิ่งที่ดูเหมือนจะขาดไปและน่าจะมีประโยชน์จริง ๆ คือวิธีที่ใช้ ข้อมูล SHA256 กับ counter คล้ายกับ PBKDF2
    มันอาจเป็นตัวระบุที่สืบทอดมาโดยรักษาความเป็นส่วนตัว และยังอาจพิสูจน์ได้แบบหลวม ๆ ว่า UUID บางตัวสืบทอดมาจาก seed ใด

    • ถ้าจำเป็นจริง ๆ ก็ให้มอง UUIDv4 เป็นรูปแบบการเข้ารหัส แล้วสร้างเอาต์พุต 122 บิตด้วยอัลกอริทึมเข้ารหัส จากนั้นเข้ารหัสเป็น UUID
      นอกเหนือจากนั้น คุณก็คงอยากได้เอาต์พุตที่ยาวกว่านี้
    • คล้ายกับ v3 แต่เป็นรูปแบบที่ทำให้ ระบุอัลกอริทึมแฮชได้
  • ใช้ v7 ไปเลยก็พอ
    ทีนี้ถึงตาผู้เชี่ยวชาญด้านความปลอดภัยบอกว่าไม่ใช่แล้ว

    • ถ้าวันที่สร้างอาจเป็นข้อมูลอ่อนไหว หรือ UUID ต้องคาดเดาไม่ได้โดยสมบูรณ์ ก็ใช้ v4 นอกเหนือจากนั้นก็ใช้ v7
    • สิ่งที่ผมรู้สึกเสียดายเสมอเกี่ยวกับ UUID และ ULID คือ เท่าที่ผมรู้ ไม่มีวิธีที่ดีในการ สร้างแบบ deterministic
      สำหรับการใช้งานจำนวนมาก ถ้าประมวลผลข้อมูลซ้ำแล้วสร้าง ID เดิมได้จะมีประโยชน์มาก แต่ผมไม่รู้วิธีมาตรฐานในการทำสิ่งนี้
    • ไม่เข้าใจว่าทำไมถึงมองข้ามประเด็นด้านความปลอดภัยกันง่ายขนาดนั้น