อีกวันทีม HB คุยกัน และ Ben dev-ops master บอกว่าอยากใช้ ULIDs แทน UUIDs สำหระบบเฉพาะ
อย่างวิศวกรมืออาชีพ reaction ผมคือ mumble something non-committal แล้วขโมยไป Google หาว่า ULID คืออะไร
สองชั่วโมงต่อมา ผมออกมาพร้อม thousand-yard stare และการตระหนักว่าโลก unique identifiers ใหญ่และ wonderful กว่าที่ผมจินตนาการ
ก่อนเข้า ULIDs มากลับพื้นฐาน UUIDs ก่อน:
ปัญหาของ "regular" ids คืออะไร?
Web apps ใช้ databases ส่วนใหญ่ default numeric ids auto-increment ใน Rails อาจเห็น behavior แบบนี้:
p1 = Person.create!
p1.id # => 1
p2 = Person.create!
p2.id # => 2
Database generate sequential ids เพราะเก็บ counter increment on record creation
Pattern นี้เห็นนอก databases ด้วย บางครั้ง assign ids เอง อาจเก็บ custom counter ใน Redis instance
Sequential ids ง่ายสำหรับ low-volume แต่ problematic เมื่อ volume เพิ่ม:
- สร้าง records concurrently ไม่ได้ เพราะแต่ละ insert ต้องรอคิวรับ id
- Request sequential id อาจต้อง network round trip slower performance
- ยาก scale out data stores ที่ให้ sequential ids ต้องกังวล counters บน servers ต่างๆ out of sync
- Node ที่มี counter กลายเป็น single point of failure ได้ง่าย
Sequential ids ยัง leak data อาจเป็นปัญหาบางกรณี:
- เดา ids ของ resources ที่อาจไม่ใช่ของคุณได้ง่าย
- สร้าง user id 20 = รู้ว่า service มี 20 users
UUIDs are web-scale
UUIDs ดูต่าง sequential ids เป็น 128-bit numbers ปกติ expressed as 32 hexadecimal digits:
123e4567-e89b-12d3-a456-426655440000
UUIDs สร้างด้วย algorithms ใน RFC 4122 พยายามแก้ปัญหา sequential ids:
- Generate UUIDs บน nodes หลายตัวได้โดยไม่ shared state หรือ coordination
- Guess ยากกว่า sequential ids (รายละเอียดหลัง)
- ไม่ divulge dataset size
Catch คือ มีโอกาสน้อยที่ nodes สองตัว generate same id independently เรียกว่า "collision"
หลายรสของ UUID
มี 5 types UUID algorithms ใน RFC 4122 แบ่ง 2 categories:
- Time and randomness-based algorithms เราพูดถึง สร้าง UUID ใหม่ทุก run
- Type 4: randomly-generated id น่าจะ best bet สำหรับโค้ดใหม่
- Type 1: ID มี host's MAC address และ current timestamp Deprecated เพราะเดาง่ายเกิน
- Type 2: ดู uncommon purpose-built สำหรับ RPC แบบเก่า
- Name based algorithms ต่างหน่อย ให้ UUID เดียวกันสำหรับ input set เดียวกัน
- Type 5: ใช้ SHA-1 hash generate UUID แนะนำ
- Type 3: ใช้ MD5 hash deprecated เพราะ MD5 insecure
ใน Ruby generate UUIDs ผ่าน uuidtools gem รองรับทุก type ยกเว้น mysterious type 2:
require "uuidtools"
# Type 1
UUIDTools::UUID.timestamp_create
# => #<UUID:0x2adfdc UUID:64a5189c-25b3-11da-a97b-00c04fd430c8>
# Type 4
UUIDTools::UUID.random_create
# => #<UUID:0x19013a UUID:984265dc-4200-4f02-ae70-fe4f48964159>
# Type 3
UUIDTools::UUID.md5_create(UUIDTools::UUID_DNS_NAMESPACE, "www.widgets.com")
# => #<UUID:0x287576 UUID:3d813cbb-47fb-32ba-91df-831e1593ac29>
# Type 5
UUIDTools::UUID.sha1_create(UUIDTools::UUID_DNS_NAMESPACE, "www.widgets.com")
# => #<UUID:0x2a0116 UUID:21f7f8de-8051-5b89-8680-0195ef798b6a>
ต่อไป ULIDs
Note: ต้นฉบับผมลืม link ULID spec นี่คือมัน มี links ไป implementations ใน Ruby และภาษาอื่นๆ
ULIDs เป็น useful new take on unique identifiers ความแตกต่างชัดเจนคือดูต่างกัน:
01ARZ3NDEKTSV4RRFFQ69G5FAV
ประกอบด้วย 2 base32-encoded numbers: UNIX timestamp ตามด้วย random number โครงสร้างใน spec:
01AN4Z07BY 79KA1307SR9X4MV3
|----------| |----------------|
Timestamp Randomness
48bits 80bits
โครงสร้างนี้น่าสนใจ! UUIDs ใช้ timestamp หรือ randomness แต ULIDs ใช้ ทั้ง timestamp และ randomness
ผลคือ ULIDs มี properties น่าสนใจ:
- Lexicographically (alphabetically) sortable
- Timestamp accurate ถึง millisecond
- สวยกว่า UUIDs :)
เปิดโอกาส cool possibilities:
- Partition database by date ใช้ timestamp ใน ULID select partition ที่ถูกต้อง
- Sort by ULID แทน created_at column ถ้า millisecond precision พอ
Downside อาจมี:
- ถ้า expose timestamp ไม่ดีสำหรับแอป ULIDs อาจไม่ใช่ตัวเลือกดี
sort by ulidอาจไม่ work ถ้าต้อง sub-millisecond accuracy- ตามอินเทอร์เน็ต บาง ULID implementations ไม่ bulletproof
สรุป
UUIDs เป็นและจะยังคงเป็น standard อยู่มานาน libraries มีทุกภาษา แต new approaches คุ้มค่าพิจารณา โดยเฉพาะเข้าโลกที่ run by distributed systems มากขึ้น New unique-id approaches อาจช่วยแก้ปัญหาที่ไม่ prevalent ตอน RFC4122