مولّد UUID v7
يولد UUID v7 مرتبة زمنياً لمفاتيح قواعد البيانات الأولية
…
تنسيق
ما هو UUID v7؟
UUID v7 هو تنسيق UUID من الجيل التالي موحَّد في RFC 9562 (مايو 2024). يرمّز طابعاً زمنياً Unix بمللي ثانية 48 بت في البتات الأكثر أهمية، تليها علامات الإصدار والمتغيّر، ويملأ البتات المتبقية ببيانات عشوائية آمنة تشفيرياً.
لأن الطابع الزمني يشغل البتات العليا، تُرتَّب قيم UUID v7 زمنياً — لغوياً وعددياً على حد سواء. هذا يجعلها مناسبة تماماً للمفاتيح الأساسية في قواعد البيانات، حيث تتسبّب UUID العشوائية (v4) في تشرذم فهارس B-tree.
لماذا تشرذم UUID العشوائية فهارس قواعد البيانات
فهارس B-tree — المستخدمة في PostgreSQL وMySQL وSQLite ومعظم قواعد البيانات الأخرى — تبقي الصفوف مرتّبة حسب قيمة المفتاح. حين تدرج صفاً جديداً، يجب على قاعدة البيانات وضعه في الموضع المرتّب الصحيح داخل شجرة الفهرس.
مع UUID v4 (عشوائي كلياً)، كل إدخال جديد يهبط في موضع عشوائي بالأساس داخل شجرة الفهرس. هذا يجبر قاعدة البيانات على قراءة وإعادة كتابة صفحات الفهرس الداخلية باستمرار، مقسِّمةً الصفحات الممتلئة وتاركةً غيرها نصف فارغة. النتيجة فهرس متشرذم منتفخ يبطّئ القراءة والكتابة معاً مع نمو الجدول.
تخطيط بتات UUID v7
UUID v7 عرضه 128 بت، مقسَّمة على ستة حقول:
| البتات | الحقل | الغرض |
|---|---|---|
| 48 | unix_ts_ms | طابع زمني Unix بالمللي ثانية 48 بت — يشغل النصف الأعلى بالكامل |
| 4 | ver | رقم الإصدار — دائماً 0111 (عشري 7) |
| 12 | rand_a | 12 بت من بيانات عشوائية آمنة تشفيرياً |
| 2 | var | علامة المتغيّر — دائماً 10 (متغيّر RFC 4122) |
| 62 | rand_b | 62 بت من بيانات عشوائية آمنة تشفيرياً |
دقة الطابع الزمني ملّي ثانية واحدة. داخل المللي ثانية ذاتها، تُرتَّب قيم UUID v7 بلاحقتها العشوائية — لا يُضمَن أن تكون رتيبة تصاعدياً دون المللي ثانية، لكنها k-sortable: المعرّفات المولَّدة بالقرب من بعضها زمنياً ستُرتَّب بالقرب من بعضها في الفهرس.
UUID v7 مقابل UUID v1
يضمّ كلٌّ من UUID v1 وUUID v7 طابعاً زمنياً، لكنهما يختلفان اختلافاً كبيراً في التصميم:
| الميزة | UUID v7 | UUID v1 |
|---|---|---|
| الأصل / القاعدة الزمنية | حقبة Unix (1 يناير 1970) | الحقبة الغريغورية (15 أكتوبر 1582) |
| الدقة الزمنية | ملّي ثانية واحدة | 100 نانوثانية |
| قابل للترتيب | نعم — k-sortable بالتصميم | لا — حقول الوقت مبعثرة في تخطيط 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 في قواعد البيانات
- 128 بت إجمالاً
- ترميز Crockford Base32 — 26 حرفاً، أكثر إيجازاً قليلاً
- غير حساس لحالة الأحرف ويتجنّب الأحرف الملتبسة (I وL وO وU)
- أكثر قابلية للقراءة البشرية للوهلة الأولى
- يتطلب مكتبة — لا دعم أصيلي للمنصة
إذا كنت بالفعل في بيئة UUID (عمود uuid في PostgreSQL وواجهات برمجية تُعيد UUID)، استخدم UUID v7. إذا كنت تبدأ من الصفر وتفضّل ترميزاً أكثر ملاءمة للإنسان، فـ ULID بديل معقول.
استخدام UUID v7 في قواعد البيانات
لا تولّد معظم قواعد البيانات UUID v7 أصيلياً بعد، لكن يمكن تخزينه في أعمدة UUID القياسية وتوليده في كود التطبيق أو عبر الإضافات:
uuid. تضيف إضافة pg-uuidv7 وظيفة uuid_generate_v7() على مستوى الخادم إذا احتجت معرّفات يولّدها DB.BINARY(16) أو CHAR(36). ولِّد في كود التطبيق. MySQL 8.0+ لديه دعم UUID مرتَّب عبر UUID_TO_BIN(UUID(), 1) لـ 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"