เครื่องสร้าง UUID v1
สร้าง UUID v1 ตามเวลาพร้อม timestamp ที่ฝังอยู่
…
จัดรูปแบบ
UUID v1 คืออะไร?
UUID v1 คือ UUID version ดั้งเดิม กำหนดมาตรฐานใน RFC 4122 (2005) สร้าง identifier ที่ไม่ซ้ำโดยรวม timestamp ความแม่นยำสูงกับ MAC address ของ host ที่สร้าง บวกกับ clock sequence สั้นๆ เพื่อจัดการ sub-timestamp resolution
เนื่องจาก timestamp ฝังอยู่ ค่า UUID v1 จาก host เดิมจะเพิ่มขึ้นเรื่อยๆ ตามเวลา ทำให้เรียงลำดับได้ตามธรรมชาติ ออกแบบมาสำหรับระบบ distributed ที่แต่ละ node สร้าง UUID ได้อิสระโดยไม่ต้องประสานงาน
ปัจจุบัน UUID v1 ถูกแทนที่โดย UUID v7 (เรียงลำดับได้, ไม่รั่ว MAC) และ UUID v4 (สุ่มทั้งหมด, เป็นส่วนตัว) เป็นส่วนใหญ่ ยังคงใช้งานในระบบอย่าง Apache Cassandra และฐานข้อมูล distributed แบบ legacy
โครงสร้างของ UUID v1
UUID v1 string เช่น 550e8400-e29b-11d4-a716-446655440000 เข้ารหัสฟิลด์หกฟิลด์ที่แตกต่างกัน:
| Field | ขนาด | คำอธิบาย |
|---|---|---|
| time_low | 32 bits | low field 32 บิตของ Gregorian timestamp 60 บิต (ช่วง 100 นาโนวินาทีนับจาก 15 ต.ค. 1582) |
| time_mid | 16 bits | middle field 16 บิตของ timestamp 60 บิต |
| time_hi_and_version | 16 bits | 12 บิตบนของ timestamp 60 บิต บวกกับ version number 4 บิต (เสมอ <code>1</code>) |
| clock_seq_hi_res | 8 bits | high field 6 บิตของ clock sequence รวมกับ variant marker 2 บิตตาม RFC 4122 |
| clock_seq_low | 8 bits | 8 บิตล่างของ clock sequence |
| node | 48 bits | node identifier 48 บิต — โดยทั่วไปคือ MAC address ของ network interface ที่สร้าง หรือค่าสุ่ม 48 บิตหากไม่มี MAC |
ฟิลด์ clock sequence (clock_seq_hi_res + clock_seq_low) คือ counter 14 บิต จะเพิ่มขึ้นทุกครั้งที่ system clock เดินถอยหลัง (เช่น NTP adjustment) หรือเมื่อระบบ restart โดยไม่ได้บันทึก timestamp ล่าสุด เพื่อป้องกันการสร้าง UUID ซ้ำหาก clock ไม่เดินไปข้างหน้าอย่างต่อเนื่อง
การถอดรหัส UUID v1 Timestamp
timestamp 60 บิตกระจายอยู่ใน UUID สามฟิลด์ ในการประกอบเวลาที่สร้างใหม่:
- ดึง
time_low(bytes 0–3),time_mid(bytes 4–5), และtime_hi(bytes 6–7 ลบ version nibble) - ประกอบใหม่:
(time_hi << 48) | (time_mid << 32) | time_low - ผลลัพธ์คือ count 60 บิตของ
ช่วง 100 นาโนวินาทีนับจาก 15 ตุลาคม 1582 (Gregorian calendar epoch) - ลบ Gregorian-to-Unix offset: 122,192,928,000,000,000 (ช่วง 100-ns ระหว่าง 15 ต.ค. 1582 และ 1 ม.ค. 1970)
- หารด้วย
10,000เพื่อแปลงช่วง 100 นาโนวินาทีเป็น millisecond - ใช้ผลลัพธ์เป็น
Unix millisecond timestampเพื่อสร้าง Date object - Format เป็น
ISO 8601สำหรับ output ที่มนุษย์อ่านได้
ความแม่นยำของ timestamp คือ 100 นาโนวินาที ละเอียดกว่าความแม่นยำ millisecond ของ UUID v7 มาก อย่างไรก็ตาม ในทางปฏิบัติระบบปฏิบัติการส่วนใหญ่ไม่เปิดเผย clock resolution ระดับ sub-millisecond ดังนั้นบิตล่างมักเป็นศูนย์หรือสังเคราะห์ขึ้น
ข้อกังวลด้านความเป็นส่วนตัว
ข้อเสียที่สำคัญที่สุดของ UUID v1 คือมันฝัง MAC address ของ host ที่สร้างใน node field ซึ่งหมายความว่า UUID v1 ทุกตัวมี fingerprint ถาวรและไม่ซ้ำกันทั่วโลกของเครื่องที่สร้างมัน
ผู้โจมตีที่ได้ UUID v1 สามารถระบุ: (1) เวลาโดยประมาณที่สร้าง ID, (2) MAC address ของ host ที่สร้าง, และ (3) โดยการวิเคราะห์ UUID หลายตัว อัตราการสร้าง ID
ด้วยเหตุนี้ UUID v1 ไม่ควรใช้เป็น identifier ที่เปิดสาธารณะ (เช่น ใน URL หรือ API response) เว้นแต่คุณยินดีเปิดเผยข้อมูลนี้ RFC 4122 เองระบุว่าระบบอาจใช้ค่าสุ่ม 48 บิตแทน MAC address แต่การ implementation จำนวนมากไม่ทำเช่นนั้น
เมื่อไร UUID v1 ยังเหมาะสม
UUID v1 vs UUID v7
UUID v7 คือผู้สืบทอดสมัยใหม่ของ UUID v1 สำหรับ identifier ที่เรียงตามเวลา ต่อไปนี้คือการเปรียบเทียบตรงๆ:
| ด้าน | UUID v1 | UUID v7 |
|---|---|---|
| Epoch / Time Base | Gregorian epoch (15 ต.ค. 1582) | Unix epoch (1 ม.ค. 1970) |
| ความแม่นยำ | 100 nanoseconds | 1 millisecond |
| Node Identifier | MAC address (รั่ว host identity) | สุ่ม (เป็นส่วนตัว) |
| ความเป็นส่วนตัว | รั่ว MAC address และ generation timestamp | ไม่มีข้อมูล host ฝังอยู่ |
| ประสิทธิภาพ DB index | ดี — sequential ต่อ host | ยอดเยี่ยม — k-sortable ทั่วทุก generator |
| มาตรฐาน | RFC 4122 (2005) | RFC 9562 (2024) |
สำหรับโปรเจกต์ใหม่ UUID v7 คือทางเลือกที่แนะนำแทน UUID v1 ให้การรับประกัน time-ordering ที่คล้ายกันโดยไม่มีผลกระทบด้านความเป็นส่วนตัวจากการฝัง host MAC address
ตัวอย่างโค้ด
การสร้าง UUID v1 ไม่มีแบบ native ในเบราว์เซอร์หรือ Node.js ใช้ uuid npm package:
// Generate a UUID v1 using the Web Crypto API
function generateUuidV1() {
const buf = new Uint8Array(16)
crypto.getRandomValues(buf)
const ms = BigInt(Date.now())
const gregorianOffset = 122192928000000000n
const t = ms * 10000n + gregorianOffset
const tLow = Number(t & 0xFFFFFFFFn)
const tMid = Number((t >> 32n) & 0xFFFFn)
const tHiVer = Number((t >> 48n) & 0x0FFFn) | 0x1000 // version 1
const clockSeq = (buf[8] & 0x3F) | 0x80 // variant 10xxxxxx
const clockSeqLow = buf[9]
const hex = (n, pad) => n.toString(16).padStart(pad, '0')
const node = [...buf.slice(10)].map(b => b.toString(16).padStart(2, '0')).join('')
return `${hex(tLow,8)}-${hex(tMid,4)}-${hex(tHiVer,4)}-${hex(clockSeq,2)}${hex(clockSeqLow,2)}-${node}`
}
// Extract the embedded timestamp from a UUID v1
function extractTimestamp(uuid) {
const parts = uuid.split('-')
const tHex = parts[2].slice(1) + parts[1] + parts[0]
const t = BigInt('0x' + tHex)
const ms = (t - 122192928000000000n) / 10000n
return new Date(Number(ms))
}
const id = generateUuidV1()
console.log(id) // e.g. "1eb5e8b0-6b4d-11ee-9c45-a1f2b3c4d5e6"
console.log(extractTimestamp(id)) // e.g. 2023-10-15T12:34:56.789Zimport uuid from datetime import datetime, timezone # Generate UUID v1 (uses MAC address by default) uid = uuid.uuid1() print(uid) # Extract embedded timestamp # uuid.time is 100-ns intervals since Oct 15, 1582 GREGORIAN_OFFSET = 122192928000000000 # 100-ns intervals ts_100ns = uid.time ts_ms = (ts_100ns - GREGORIAN_OFFSET) // 10000 dt = datetime.fromtimestamp(ts_ms / 1000, tz=timezone.utc) print(dt.isoformat()) # e.g. "2023-10-15T12:34:56.789000+00:00"
package main
import (
"fmt"
"time"
"github.com/google/uuid" // go get github.com/google/uuid
)
func main() {
id, _ := uuid.NewUUID() // UUID v1
fmt.Println(id)
// Extract timestamp from UUID v1
// uuid.Time is 100-ns ticks since Oct 15, 1582
t := id.Time()
sec := int64(t)/1e7 - 12219292800 // convert to Unix seconds
nsec := (int64(t) % 1e7) * 100
ts := time.Unix(sec, nsec).UTC()
fmt.Println(ts.Format(time.RFC3339Nano))
}