تولیدکننده UUID v7
تولید UUID v7 مرتبشده بر اساس زمان برای کلید اصلی پایگاه داده
…
فرمتبندی
UUID v7 چیست؟
UUID v7 یک فرمت UUID نسل بعدی است که در RFC 9562 (مه ۲۰۲۴) استانداردسازی شده است. این فرمت یک تایماستمپ یونیکس ۴۸ بیتی با دقت میلیثانیه را در بیتهای با اهمیت بیشتر رمزگذاری میکند، سپس نشانگرهای نسخه و واریانت میآیند، و بیتهای باقیمانده با دادههای تصادفی رمزنگاریشده پر میشوند.
چون تایماستمپ در بیتهای بالا قرار دارد، مقادیر UUID v7 بهصورت زمانی مرتب میشوند — هم لغتنگارانه و هم عددی. این ویژگی آنها را برای کلیدهای اصلی پایگاه داده بسیار مناسب میکند، جایی که UUIDهای تصادفی (v4) باعث تکهتکهشدن ایندکس B-tree میشوند.
چرا UUIDهای تصادفی ایندکسهای پایگاه داده را تکهتکه میکنند
ایندکسهای B-tree — که توسط PostgreSQL، MySQL، SQLite و اکثر پایگاههای داده دیگر استفاده میشوند — ردیفها را بر اساس مقدار کلید مرتب نگه میدارند. وقتی ردیف جدیدی درج میکنید، پایگاه داده باید آن را در موقعیت مرتبشده صحیح در ایندکس قرار دهد.
با UUID v4 (کاملاً تصادفی)، هر درج جدید در موقعیتی اساساً تصادفی در درخت ایندکس قرار میگیرد. این موضوع پایگاه داده را مجبور میکند صفحات ایندکس داخلی را مدام بخواند و بازنویسی کند، صفحات پر را تقسیم کند و برخی دیگر را نیمهخالی بگذارد. نتیجه یک ایندکس تکهتکه و متورم است که با رشد جدول، هم نوشتن و هم خواندن را کند میکند.
چیدمان بیتی UUID v7
UUID v7 دارای ۱۲۸ بیت است که به شش فیلد تقسیم میشود:
| بیتها | فیلد | هدف |
|---|---|---|
| 48 | unix_ts_ms | تایماستمپ یونیکس ۴۸ بیتی بر حسب میلیثانیه — کل نیمه بالایی را اشغال میکند |
| 4 | ver | شماره نسخه — همیشه 0111 (عدد صحیح ۷) |
| 12 | rand_a | ۱۲ بیت داده تصادفی رمزنگاریشده |
| 2 | var | نشانگر واریانت — همیشه 10 (واریانت RFC 4122) |
| 62 | rand_b | ۶۲ بیت داده تصادفی رمزنگاریشده |
دقت تایماستمپ ۱ میلیثانیه است. در یک میلیثانیه یکسان، مقادیر UUID v7 بر اساس پسوند تصادفیشان مرتب میشوند — افزایش یکنواخت زیر میلیثانیه تضمین نمیشود، اما k-sortable هستند: شناسههایی که در زمان نزدیک به هم تولید میشوند در ایندکس نیز نزدیک به هم مرتب خواهند شد.
UUID v7 در مقابل UUID v1
هر دوی UUID v1 و UUID v7 یک تایماستمپ جاسازی میکنند، اما از نظر طراحی تفاوتهای قابل توجهی دارند:
| ویژگی | UUID v7 | UUID v1 |
|---|---|---|
| Epoch / پایه زمانی | Unix epoch (۱ ژانویه ۱۹۷۰) | Gregorian epoch (۱۵ اکتبر ۱۵۸۲) |
| دقت زمانی | ۱ میلیثانیه | ۱۰۰ نانوثانیه |
| قابل مرتبسازی | بله — k-sortable by design | خیر — فیلدهای زمانی در چیدمان UUID درهم میشوند |
| حریم خصوصی | هیچ اطلاعات میزبانی فاش نمیشود | آدرس MAC میزبان تولیدکننده را جاسازی میکند |
| عملکرد ایندکس پایگاه داده | عالی — درجهای ترتیبی، تکهتکهشدن حداقل | ضعیف — غیرترتیبی علیرغم وجود تایماستمپ |
| استاندارد | RFC 9562 (2024) | RFC 4122 (2005) |
| پشتیبانی بومی مرورگر | هنوز نه (بدون crypto.randomUUID v7) | بهصورت بومی در دسترس نیست |
برای هر پروژه جدیدی که به UUIDهای مرتبشده بر اساس زمان نیاز دارد، UUID v7 را به UUID v1 ترجیح دهید. UUID v1 قدیمی است و اطلاعات میزبان را فاش میکند.
UUID v7 در مقابل ULID
ULID (شناسه منحصربهفرد جهانی قابل مرتبسازی لغتنگارانه) مشکلی مشابه UUID v7 را حل میکند. در اینجا مقایسه آنها آمده است:
- از استاندارد UUID مطابق RFC 9562 پیروی میکند — با تمام ابزارهای UUID سازگار است
- فرمت هگز با خطتیره — بهطور جهانی شناختهشده
- پشتیبانی بومی از ستون UUID در پایگاه داده
- مجموعاً ۱۲۸ بیت
- رمزگذاری Crockford Base32 — ۲۶ کاراکتر، کمی فشردهتر
- حروفکوچک و بزرگ یکسان است و از کاراکترهای مبهم (I، L، O، U) اجتناب میکند
- در نگاه اول خواناتر برای انسان
- به کتابخانه نیاز دارد — پشتیبانی بومی پلتفرم وجود ندارد
اگر در اکوسیستم UUID هستید (ستون uuid در PostgreSQL، REST APIهایی که UUID برمیگردانند)، از UUID v7 استفاده کنید. اگر از صفر شروع میکنید و رمزگذاری دوستانهتری برای انسان ترجیح میدهید، ULID گزینه معقولی است.
استفاده از UUID v7 در پایگاههای داده
UUID v7 هنوز توسط اکثر پایگاههای داده بهصورت بومی تولید نمیشود، اما میتواند در ستونهای UUID استاندارد ذخیره شده و در کد برنامه یا از طریق افزونهها تولید شود:
uuid ذخیره کنید. افزونه pg-uuidv7 یک تابع سمت سرور uuid_generate_v7() اضافه میکند اگر به تولید شناسه توسط پایگاه داده نیاز دارید.BINARY(16) یا CHAR(36) ذخیره کنید. در کد برنامه تولید کنید. MySQL 8.0+ از طریق UUID_TO_BIN(UUID(), 1) پشتیبانی از UUID مرتبشده برای v1 دارد، اما v7 نیاز به تولید در سطح برنامه دارد.TEXT (36 کاراکتر) یا BLOB (16 بایت) ذخیره کنید. در کد برنامه تولید کنید. مرتبسازی لغتنگارانه روی TEXT به درستی کار میکند زیرا UUID v7 از پیشوند تایماستمپ با عرض ثابت استفاده میکند.نمونه کد
UUID v7 هنوز از طریق crypto.randomUUID() در دسترس نیست. از کتابخانهای مانند uuidv7 (npm) تا رسیدن پشتیبانی بومی استفاده کنید:
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"