เครื่องสร้าง UUID v7
สร้าง UUID v7 เรียงตามเวลาสำหรับ primary key ของฐานข้อมูล
…
จัดรูปแบบ
UUID v7 คืออะไร?
UUID v7 คือรูปแบบ UUID รุ่นถัดไปที่กำหนดมาตรฐานใน RFC 9562 (พฤษภาคม 2024) มันเข้ารหัส timestamp Unix millisecond 48 บิตในบิตที่มีนัยสำคัญสูงสุด ตามด้วย version และ variant marker และเติมบิตที่เหลือด้วยข้อมูลสุ่มที่ cryptographically secure
เนื่องจาก timestamp อยู่ใน high bits ค่า UUID v7 จึงเรียงตามลำดับเวลา ทั้งแบบ lexicographic และตัวเลข ทำให้เป็นตัวเลือกที่ยอดเยี่ยมสำหรับ database primary key ที่ UUID แบบสุ่ม (v4) ทำให้ B-tree index แตกกระจาย
ทำไม UUID แบบสุ่มถึงทำให้ Database Index แตกกระจาย
B-tree index ที่ใช้โดย PostgreSQL, MySQL, SQLite และฐานข้อมูลส่วนใหญ่ เก็บ row เรียงตามค่า key เมื่อ insert row ใหม่ ฐานข้อมูลต้องวางมันในตำแหน่งที่ถูกต้องในต้นไม้ index
กับ UUID v4 (สุ่มทั้งหมด) การ insert ใหม่แต่ละครั้งจะลงที่ตำแหน่งสุ่มในต้นไม้ index ทำให้ฐานข้อมูลต้องอ่านและเขียน internal index page ตลอดเวลา แตก page ที่เต็มและทิ้ง page อื่นไว้ครึ่งว่าง ผลคือ index ที่แตกกระจายและบวมซึ่งทำให้ทั้งการเขียนและการอ่านช้าลงเมื่อตารางโต
UUID v7 Bit Layout
UUID v7 กว้าง 128 บิต แบ่งเป็นหกฟิลด์:
| Bits | Field | วัตถุประสงค์ |
|---|---|---|
| 48 | unix_ts_ms | Unix timestamp 48 บิตในหน่วย millisecond — ครองครึ่งบนทั้งหมด |
| 4 | ver | Version number — เสมอ 0111 (decimal 7) |
| 12 | rand_a | ข้อมูลสุ่มที่ cryptographically secure 12 บิต |
| 2 | var | Variant marker — เสมอ 10 (RFC 4122 variant) |
| 62 | rand_b | ข้อมูลสุ่มที่ cryptographically secure 62 บิต |
ความแม่นยำของ timestamp คือ 1 millisecond ภายใน millisecond เดียวกัน ค่า UUID v7 เรียงตาม random suffix ไม่รับประกันว่าจะเพิ่มขึ้นเรื่อยๆ ใน sub-millisecond แต่เป็น k-sortable: ID ที่สร้างใกล้เคียงกันในเวลาจะเรียงใกล้เคียงกันใน index
UUID v7 vs UUID v1
ทั้ง UUID v1 และ UUID v7 ฝัง timestamp แต่ออกแบบต่างกันอย่างมีนัยสำคัญ:
| คุณสมบัติ | UUID v7 | UUID v1 |
|---|---|---|
| Epoch / Time Base | Unix epoch (1 ม.ค. 1970) | Gregorian epoch (15 ต.ค. 1582) |
| ความแม่นยำของเวลา | 1 millisecond | 100 nanoseconds |
| เรียงลำดับได้ | ใช่ — k-sortable ตามการออกแบบ | ไม่ — time field ถูกสลับใน UUID layout |
| ความเป็นส่วนตัว | ไม่รั่วข้อมูล host | ฝัง MAC address ของ host ที่สร้าง |
| ประสิทธิภาพ DB Index | ยอดเยี่ยม — insert แบบ sequential, fragmentation น้อยมาก | แย่ — ไม่ sequential แม้มี timestamp |
| มาตรฐาน | RFC 9562 (2024) | RFC 4122 (2005) |
| Native Browser Support | ยังไม่มี (ไม่มี crypto.randomUUID v7) | ไม่มีแบบ native |
สำหรับโปรเจกต์ใหม่ที่ต้องการ UUID ที่เรียงตามเวลา ให้เลือก UUID v7 แทน UUID v1 UUID v1 เป็น legacy และรั่วข้อมูล host
UUID v7 vs ULID
ULID (Universally Unique Lexicographically Sortable Identifier) แก้ปัญหาคล้ายกับ UUID v7 ต่อไปนี้คือการเปรียบเทียบ:
- ตาม RFC 9562 UUID standard — เข้ากันได้กับเครื่องมือ UUID ทั้งหมด
- รูปแบบ hex พร้อมขีดกลาง — ได้รับการยอมรับทั่วไป
- รองรับ database UUID column แบบ native
- 128 บิตรวม
- Crockford Base32 encoding — 26 ตัวอักษร กระชับกว่าเล็กน้อย
- Case-insensitive และหลีกเลี่ยงตัวอักษรที่อาจสับสน (I, L, O, U)
- อ่านได้ง่ายกว่าสำหรับมนุษย์
- ต้องการ library — ไม่มี native platform support
หากอยู่ในระบบนิเวศ UUID แล้ว (PostgreSQL uuid column, REST API ที่ส่ง UUID) ให้ใช้ UUID v7 หากเริ่มใหม่และต้องการ encoding ที่เป็นมิตรกับมนุษย์มากกว่า ULID เป็นทางเลือกที่สมเหตุสมผล
การใช้ UUID v7 ในฐานข้อมูล
UUID v7 ยังไม่ถูกสร้างแบบ native โดยฐานข้อมูลส่วนใหญ่ แต่สามารถเก็บใน UUID column มาตรฐานและสร้างใน application code หรือผ่าน extension:
uuid Extension pg-uuidv7 เพิ่มฟังก์ชัน uuid_generate_v7() ฝั่ง server หากต้องการ DB-generated IDBINARY(16) หรือ CHAR(36) สร้างใน application code MySQL 8.0+ รองรับ ordered UUID ผ่าน UUID_TO_BIN(UUID(), 1) สำหรับ v1 แต่ v7 ต้องสร้างระดับ appTEXT (36 ตัวอักษร) หรือ BLOB (16 bytes) สร้างใน application code การเรียง lexicographic บน TEXT ทำงานถูกต้องเพราะ UUID v7 ใช้ timestamp prefix ขนาดคงที่ตัวอย่างโค้ด
UUID v7 ยังไม่มีใน crypto.randomUUID() ใช้ library เช่น uuidv7 (npm) จนกว่าจะมี native support:
function generateUuidV7() {
const buf = new Uint8Array(16)
crypto.getRandomValues(buf)
const ms = BigInt(Date.now())
// Embed 48-bit Unix ms timestamp
buf[0] = Number((ms >> 40n) & 0xFFn)
buf[1] = Number((ms >> 32n) & 0xFFn)
buf[2] = Number((ms >> 24n) & 0xFFn)
buf[3] = Number((ms >> 16n) & 0xFFn)
buf[4] = Number((ms >> 8n) & 0xFFn)
buf[5] = Number(ms & 0xFFn)
// Set version 7 (0111xxxx)
buf[6] = (buf[6] & 0x0F) | 0x70
// Set variant (10xxxxxx)
buf[8] = (buf[8] & 0x3F) | 0x80
const hex = [...buf].map(b => b.toString(16).padStart(2, '0')).join('')
return `${hex.slice(0,8)}-${hex.slice(8,12)}-${hex.slice(12,16)}-${hex.slice(16,20)}-${hex.slice(20)}`
}
// Node.js 20+ built-in
// import { randomUUID } from 'node:crypto' // v4 only — no v7 yet in stdlib# pip install uuid7 import uuid_extensions uid = uuid_extensions.uuid7() print(uid) # e.g. 018e2b3d-1a2b-7000-8000-abc123456789 print(uid.time) # Unix ms timestamp embedded in the UUID # Or as a plain string from uuid_extensions import uuid7str print(uuid7str())
-- PostgreSQL 13+ extension-free implementation
CREATE OR REPLACE FUNCTION uuid_generate_v7()
RETURNS uuid
LANGUAGE sql
AS $$
SELECT encode(
set_bit(
set_bit(
overlay(
uuid_send(gen_random_uuid())
PLACING substring(int8send(floor(extract(epoch FROM clock_timestamp()) * 1000)::bigint) FROM 3)
FROM 1 FOR 6
),
52, 1
),
53, 1
),
'hex'
)::uuid;
$$;
-- Usage as a default primary key
CREATE TABLE events (
id uuid PRIMARY KEY DEFAULT uuid_generate_v7(),
payload jsonb,
created_at timestamptz DEFAULT now()
);function extractTimestamp(uuid: string): Date {
const hex = uuid.replace(/-/g, '')
const ms = parseInt(hex.slice(0, 12), 16) // first 48 bits = ms timestamp
return new Date(ms)
}
const uid = '018e2b3d-1a2b-7000-8000-abc123456789'
console.log(extractTimestamp(uid).toISOString())
// → "2024-03-15T10:22:05.259Z"