مولّد UUID v1

يولد UUID v1 زمنية مع طابع زمني مضمن

تنسيق

العدد:
Note:يضمّ UUID v1 عنوان MAC للمضيف وطابع التوليد الزمني، مما يطرح مخاوف خصوصية لمعظم التطبيقات الحديثة. للمشاريع الجديدة، يُوصَى بـ UUID v4 ما لم تحتج تحديداً إلى معرّفات مرتّبة زمنياً قابلة للفكّ.

ما هو UUID v1؟

UUID v1 هو إصدار UUID الأصلي، موحَّد في RFC 4122 (2005). يولّد معرّفات فريدة بجمع طابع زمني عالي الدقة مع عنوان MAC للمضيف المولِّد، بالإضافة إلى تسلسل ساعة قصير لمعالجة الدقة دون الطابع الزمني.

لأن الطابع الزمني مضمَّن، تكون قيم UUID v1 من المضيف ذاته رتيبة تصاعدياً بمرور الوقت — مما يجعلها مرتّبة بطبيعتها. صُمِّم هذا للأنظمة الموزّعة حيث يستطيع كل عقدة توليد UUID باستقلالية دون تنسيق.

اليوم، UUID v1 استُبدل إلى حدٍّ بعيد بـ UUID v7 (قابل للترتيب، لا تسريب MAC) وUUID v4 (عشوائي كلياً، خاص). لا يزال مستخدماً في أنظمة مثل Apache Cassandra وقواعد البيانات الموزّعة القديمة.

تشريح UUID v1

سلسلة UUID v1 مثل 550e8400-e29b-11d4-a716-446655440000 ترمّز ستة حقول متمايزة:

الحقلالحجمالوصف
time_low32 bitsالحقل المنخفض 32 بت للطابع الزمني الغريغوري 60 بت (بفترات 100 نانوثانية منذ 15 أكتوبر 1582)
time_mid16 bitsالحقل الأوسط 16 بت للطابع الزمني 60 بت
time_hi_and_version16 bitsأعلى 12 بت للطابع الزمني 60 بت بالإضافة إلى رقم الإصدار 4 بت (دائماً <code>1</code>)
clock_seq_hi_res8 bitsالحقل العلوي 6 بت لتسلسل الساعة مدمجاً مع علامة متغيّر RFC 4122 البالغة 2 بت
clock_seq_low8 bitsالـ 8 بت الدنيا لتسلسل الساعة
node48 bitsمعرّف العقدة 48 بت — عادةً عنوان MAC لواجهة الشبكة المولِّدة، أو قيمة عشوائية 48 بت إذا لم يتوفّر MAC

حقل تسلسل الساعة (clock_seq_hi_res + clock_seq_low) عدّاد 14 بت. يُزاد كلما تحرّكت ساعة النظام للخلف (مثلاً ضبط NTP) أو حين يُعيد النظام التشغيل دون استمرار الطابع الزمني الأخير المعروف. يمنع هذا توليد UUID مكرّرة إذا لم تكن الساعة تتقدّم رتيبياً.

فكّ ترميز طابع UUID v1 الزمني

الطابع الزمني 60 بت مبعثر عبر ثلاثة حقول في UUID. لإعادة تجميع وقت التوليد:

  1. استخرج time_low (البايتات 0-3) وtime_mid (البايتات 4-5) وtime_hi (البايتات 6-7، بطرح نيبل الإصدار)
  2. أعد التجميع: (time_hi &lt;&lt; 48) | (time_mid &lt;&lt; 32) | time_low
  3. الناتج عدد 60 بت من فترات 100 نانوثانية منذ 15 أكتوبر 1582 (حقبة التقويم الغريغوري)
  4. اطرح إزاحة الغريغوري-إلى-Unix: 122,192,928,000,000,000 (فترات 100 نانوثانية بين 15 أكتوبر 1582 و1 يناير 1970)
  5. اقسم على 10,000 لتحويل فترات 100 نانوثانية إلى مللي ثانية
  6. استخدم الناتج كـ طابع زمني Unix بالمللي ثانية لإنشاء كائن Date
  7. اعرضه كـ ISO 8601 للإخراج المقروء بشرياً

دقة الطابع الزمني 100 نانوثانية — أدق بكثير من دقة المللي ثانية في UUID v7. غير أن معظم أنظمة التشغيل عملياً لا تكشف عن دقة أعلى من المللي ثانية، لذا كثيراً ما تكون البتات الدنيا صفراً أو مُركَّبة.

مخاوف الخصوصية

أبرز عيوب UUID v1 أنه يضمّ عنوان MAC للمضيف المولِّد في حقل العقدة. هذا يعني أن كل UUID v1 يحمل بصمة دائمة وفريدة عالمياً للجهاز الذي ولّده.

يستطيع المهاجم الذي يحصل على UUID v1 معرفة: (1) الوقت التقريبي لتوليد المعرّف، (2) عنوان MAC للمضيف المولِّد، و(3) من خلال تحليل عدة UUID، معدّل توليد المعرّفات.

لهذا السبب، لا ينبغي أبداً استخدام UUID v1 كمعرّف مواجه للعموم (مثلاً في عناوين URL أو استجابات API) ما لم تكن مرتاحاً للكشف عن هذه المعلومات. يلاحظ RFC 4122 نفسه أن النظام قد يستخدم قيمة عشوائية 48 بت بدلاً من عنوان MAC، لكن كثيراً من التطبيقات لا تفعل ذلك.

متى يظل UUID v1 مناسباً

المفاتيح الأساسية في Apache Cassandra
تستخدم Cassandra UUID v1 (عبر نوع TimeUUID) كنمط تصميم أساسي. يتوافق ترتيب الطابع الزمني بشكل طبيعي مع نموذج تخزين Cassandra، مما يتيح استعلامات نطاق زمني فعّالة.
الأنظمة الموزّعة القديمة
الأنظمة المبنية قبل وجود UUID v7 (ما قبل 2024) التي تعتمد على UUID مرتّبة زمنياً ولا تستطيع الترحيل بسهولة إلى تنسيق جديد.
سجلات التدقيق والأحداث
حين تكون هوية المضيف المولِّد معروفة وموثوقة، يمكن لتضمين عنوان MAC توفير قابلية تتبّع إضافية لأحداث التدقيق.
المعرّفات الداخلية
معرّفات لا تُعرَض خارج نظام داخلي متحكَّم به، حيث الكشف عن عنوان MAC ليس مصدر قلق.
استعلامات النطاق الزمني دون عمود طابع زمني منفصل
يمكن فكّ ترميز الطابع الزمني المضمَّن لتصفية الصفوف حسب وقت التوليد، بما يجمع المعرّف والطابع الزمني في آنٍ واحد.
التوافق مع مولّدات UUID v1 الأقدم
حين تستقبل أو تعالج قيم UUID v1 من أنظمة خارجية تولّدها، وتفكّ ترميز الطابع الزمني المضمَّن للعرض أو التحليل.

UUID v1 مقابل UUID v7

UUID v7 هو الخلف الحديث لـ UUID v1 في المعرّفات المرتّبة زمنياً. إليك مقارنة مباشرة:

الجانبUUID v1UUID v7
الأصل / القاعدة الزمنيةالحقبة الغريغورية (15 أكتوبر 1582)حقبة Unix (1 يناير 1970)
الدقة100 نانوثانيةملّي ثانية واحدة
معرّف العقدةعنوان MAC (يكشف هوية المضيف)عشوائي (خاص)
الخصوصيةيكشف عنوان MAC والطابع الزمني للتوليدلا معلومات مضيف مضمَّنة
أداء فهرس قاعدة البياناتجيد — تسلسلي لكل مضيفممتاز — k-sortable عبر جميع المولِّدات
المعيارRFC 4122 (2005)RFC 9562 (2024)

للمشاريع الجديدة، UUID v7 هو البديل الموصَى به لـ UUID v1. يوفّر ضمانات ترتيب زمني مماثلة دون تضمين عنوان MAC للمضيف.

أمثلة الكود

توليد UUID v1 غير متاح أصيلياً في المتصفحات أو Node.js. استخدم حزمة uuid npm:

JavaScript (browser)
// 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.789Z
Python
import 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"
Go
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))
}

الأسئلة الشائعة

هل يمكنني فكّ ترميز الطابع الزمني من UUID v1؟
نعم. طابع التوليد الزمني قابل للاسترداد كلياً من سلسلة UUID v1. هذه الأداة تفعل ذلك تماماً — الصق أي UUID v1 وستعرض الطابع الزمني UTC المفكَّك. راجع خطوات فكّ الترميز أعلاه للاطلاع على الخوارزمية.
هل عنوان MAC موجود دائماً في UUID v1؟
ليس بالضرورة. يسمح RFC 4122 للتطبيقات باستبدال قيمة عشوائية 48 بت بعنوان MAC حين لا تتوفّر واجهة شبكة أو حين تُرغَب الخصوصية. عملياً، كثير من التطبيقات من جانب الخادم تضمّ عنوان MAC الحقيقي. قيم UUID v1 المولَّدة في المتصفح تستخدم دائماً قيمة عقدة عشوائية إذ لا تكشف المتصفحات عن عناوين MAC.
لماذا يستخدم طابع UUID v1 الزمني 1582 كحقبة؟
دخل إصلاح التقويم الغريغوري حيّز التنفيذ في 15 أكتوبر 1582. عُرِّف طابع UUID v1 الزمني نسبةً لهذا التاريخ لتوفير نقطة مرجعية ثابتة وعالمية تسبق حقبة Unix (1970). يمنح هذا حقل الطابع الزمني 60 بت نطاقاً كافياً ليظل فريداً حتى عام 3400 تقريباً.
UUID v1 مقابل UUID v7 — متى لا أزال أستخدم v1؟
السبب الرئيسي لاستخدام UUID v1 اليوم هو التوافق مع الأنظمة القائمة — لا سيما Apache Cassandra التي تستخدم v1 كنوع TimeUUID. لجميع الأنظمة الجديدة، UUID v7 أفضل بشكل صارم: يستخدم حقبة Unix المألوفة، لا تسريب لعنوان MAC، ويوفّر أداء أفضل لفهارس B-tree.
هل يمكن أن تتصادم قيم UUID v1؟
نظرياً، يمكن أن تتصادم قيمتا UUID v1 إذا ولَّد عنوان MAC ذاته UUID اثنين ضمن فترة 100 نانوثانية ذاتها وكان تسلسل الساعة مطابقاً. تسلسل الساعة موجود تحديداً لمنع هذا — يُزاد عند الاستدعاءات المتتالية السريعة. عملياً، تصادمات UUID v1 نادرة للغاية في الأنظمة المطبَّقة بشكل صحيح.