التخطي إلى المحتوى
Toova
جميع الأدوات

UUID v4 مقابل v7 — الفروق ومتى تستخدم كلًا منهما

Toova

ظل UUID v4 الخيار الافتراضي للمفاتيح الأساسية الموزعة لأكثر من عقد. أنشئ قيمة عشوائية من 128 بت، صغها على شكل ثمانية مجموعات سداسية عشرية، والأمر مكتمل. لا تنسيق مطلوب، احتمال التصادم شبه معدوم، تعمل في كل مكان. إذن لماذا ينتشر UUID v7 — المُقنَّن في RFC 9562 عام 2024 — بسرعة عبر الأنظمة الإنتاجية في 2026؟

الجواب المختصر: أداء قاعدة البيانات. يُدمّر UUID v4 محلية فهرس B-tree. يُصلح UUID v7 ذلك مع الحفاظ على كل ما يحبه المطورون في معرّفات UUID. توضح هذه المقالة ما يمثله كل إصدار فعلًا، ولماذا يهم الفرق على نطاق واسع، ومتى تختار كل منهما، وكيف تُرحّل دون توقف.

هل تحتاج إلى توليد معرّفات UUID الآن؟ جرّب مولد UUID في Toova الذي يدعم v4 وv7 بكميات كبيرة. لمعرّفات عشوائية أخرى، يغطي مولد السلاسل العشوائية ومولد كلمات المرور الرموز الأقصر.

UUID v4 — العشوائية الخالصة

يستخدم UUID الإصدار 4 مولد أعداد شبه عشوائية آمن تشفيريًا (CSPRNG) لملء 122 من أصل 128 بت. البتات الست المتبقية ثابتة: أربع بتات ترمّز الإصدار (0100) وبتتان ترمّزان المتغير (10). النتيجة تبدو كالتالي:

f47ac10b-58cc-4372-a567-0e02b2c3d479
              ^^^^
              version = 4 (random)

العشوائية هي الميزة. يمكن لنظامين مستقلين توليد معرّفات UUID دون تنسيق بينهما دون أن يتصادما أبدًا — احتمال التصادم في مجموعة من 2.71 كوادريليون معرّف v4 هو 50٪ تقريبًا، مما يعني أن الخطر لأي تطبيق عملي لا يُذكر. لا تحتاج إلى خادم معرّفات مركزي، أو تسلسل قاعدة بيانات، أو قفل موزع.

كيفية توليد v4

import { v4 as uuidv4 } from 'uuid';
const id = uuidv4();
// => 'f47ac10b-58cc-4372-a567-0e02b2c3d479'

تفوّض مكتبة uuid (JavaScript) وdالة uuid.uuid4() في Python وgoogle/uuid في Go وكل بيئة تشغيل لغة رئيسية إلى CSPRNG الخاص بنظام التشغيل — /dev/urandom في Linux وCryptGenRandom في Windows. تكلفة التوليد لا شيء تقريبًا.

المشكلة: الإدراج العشوائي يُدمّر فهارس B-tree

يحتفظ فهرس B-tree بترتيب البيانات. عند إدراج صف جديد، تجد قاعدة البيانات مكان المفتاح الجديد في الترتيب المُرتَّب وتضعه هناك. إذا كان كل مفتاح جديد عشوائيًا، فإنه يقع في موضع عشوائي من الفهرس — مما يعني أن كل إدراج يحتاج إلى تحميل صفحة مختلفة من القرص في تجمع المخزن المؤقت. عند حجم صغير هذا غير محسوس. عند حجم كبير (ملايين الصفوف، معدل INSERT مرتفع)، يُنشئ نمطًا يُسمى تشتت الفهرس: يمتلئ الفهرس بصفحات نصف فارغة لأن كل إدراج يصل إلى موضع مختلف، ويتضرر تجمع المخزن المؤقت باستمرار لأن مجموعة العمل النشطة تمتد على الفهرس بأكمله بدلًا من شريحة حديثة يمكن التنبؤ بها.

الأعراض في الإنتاج: تزداد زمن الانتظار لـ INSERT، ويرتفع عبء autovacuum (PostgreSQL)، ويتصاعد ضغط نقطة التحقق، ويتراجع أداء القراءة للسجلات الحديثة لأن "الحديثة" لم تعد تُعيَّن لأي محلية في الفهرس. هذا ليس افتراضيًا — إنه نقطة ألم موثقة جيدًا على جداول PostgreSQL وMySQL الكبيرة ذات المفاتيح الأساسية UUID v4.

UUID v7 — العشوائية المرتبة زمنيًا

صُمِّم UUID v7 تحديدًا لحل مشكلة تشتت B-tree. يُشفّر طابعًا زمنيًا Unix بالميلي ثانية مدته 48 بت في البتات الأكثر أهمية، يتبعه بتات الإصدار و12 بت عشوائي وبتات المتغير و62 بت عشوائي آخر. إجمالي العشوائية: 74 بت — لا يزال أبعد بكثير مما يلزم لمنع التصادم.

018f4e6b-a23c-7d45-9abc-0e02b2c3d479
 ^^^^^^^^^^^^^^
 48-bit Unix timestamp (ms precision)
                   ^
                   version = 7

لأن الطابع الزمني يحتل البتات العالية، تأتي المعرّفات المولّدة لاحقًا بعد المعرّفات المولّدة سابقًا في الترتيب. الإدراجات دائمًا تُلحَق بالحافة اليمنى للفهرس. تحتاج قاعدة البيانات فقط إلى الحفاظ على الحرارة في أحدث صفحة الفهرس في تجمع المخزن المؤقت، لا في الفهرس بأكمله. عند معدلات INSERT العالية، هذا وحده يمكن أن يُخفّض زمن انتظار الكتابة بنسبة 30–60٪ ويُقلّل الإدخال/الإخراج بمقدار رتبة كاملة على الجداول التي تحتوي على عشرات الملايين من الصفوف.

كيفية توليد v7

import { v7 as uuidv7 } from 'uuid';
const id = uuidv7();
// => '018f4e6b-a23c-7d45-9abc-0e02b2c3d479'

أضافت مكتبة uuid دعم v7 في الإصدار 10. أضافت المكتبة القياسية لـ Python دعمه في الإصدار 3.14. يتضمن PostgreSQL 17 الدالة uuidv7() كدالة مدمجة. إذا كنت على مكدس أقدم، توفر عدة مكتبات صغيرة توليد v7 بدون تبعيات.

الرتابة تحت مستوى الميلي ثانية

ماذا يحدث عندما يُولَّد اثنان من معرّفات v7 في نفس الميلي ثانية؟ يسمح RFC 9562 للتنفيذات بزيادة عداد رتيب في البتات العشوائية لضمان الترتيب ضمن نفس الميلي ثانية. تفعل مكتبة uuid ذلك افتراضيًا. النتيجة: حتى لو وُلِّدت 10,000 معرّف في ميلي ثانية واحدة، فإنها لا تزال تُرتَّب بشكل صحيح.

مشكلة تشتت فهرس B-tree بالتفصيل

لفهم سبب أهمية ذلك، تأمّل ما يحدث عند 10,000 إدراج في الثانية بمفتاح أساسي UUID v4 على جدول يحتوي على 100 مليون صف:

  • كل إدراج يولّد مفتاحًا عشوائيًا من 128 بت يقع في موضع عشوائي بين 100 مليون إدخال موجود.
  • يجب على قاعدة البيانات تحميل صفحة B-tree المحددة التي تحتوي على ذلك الموضع في تجمع المخزن المؤقت.
  • مع 100 مليون صف وصفحات بحجم 8 كيلوبايت، يمتد الفهرس على نحو 100,000 صفحة. في كل ثانية، تُحتاج 10,000 صفحة مختلفة — أكثر بكثير مما يمكن لـ shared_buffers بحجم 8 غيغابايت عادي احتواؤه لهذا الفهرس وحده.
  • كل إخفاق في ذاكرة التخزين المؤقت يؤدي إلى قراءة من القرص. عند 10,000 إدراج/ثانية، يمكن أن يُولّد هذا آلاف القراءات العشوائية من القرص في الثانية، مما يُشبع حتى محركات الأقراص الصلبة SSD على الجداول الكبيرة.

مع UUID v7، تقع جميع الـ 10,000 إدراج في الثانية على صفحة الورقة الأكثر يمينًا (أو عدد قليل من الصفحات الأخيرة). يحتاج تجمع المخزن المؤقت فقط إلى الحفاظ على تلك الصفحات القليلة دافئة. تقترب نسبة إصابة ذاكرة التخزين المؤقت للكتابة من 100٪. تنخفض تضخيم الكتابة بشكل كبير.

تنطبق نفس الفائدة على فحص النطاقات: يتطلب WHERE created_at BETWEEN x AND y على جدول v4 فحصًا كاملًا للفهرس أو فهرسًا منفصلًا للطابع الزمني. على جدول v7، المفتاح الأساسي نفسه هو فهرس الطابع الزمني — يمكن للاستعلام الانتقال مباشرةً إلى النطاق الصحيح.

كيفية استخدام UUID v7 في PostgreSQL

-- Works natively with UUID type in PostgreSQL 16+
CREATE TABLE events (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- v4 (random)
  -- Switch to UUIDv7 via extension or application-side generation
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

-- With UUIDv7: the timestamp is already embedded, so
-- this separate created_at column is often redundant.

يشحن PostgreSQL 17 بالدالة uuidv7() بشكل مدمج. للإصدارات 14–16، توفر إضافة pg_uuidv7 نفس الدالة. يخزن نوع البيانات UUID كلا من v4 وv7 بشكل متطابق — 16 بايت، بدون عبء إضافي. الاختلاف الوحيد هو نمط البتات الذي يحدد ترتيب الفرز.

نتيجة مفيدة واحدة: نظرًا لأن الطابع الزمني مضمّن، لا تحتاج كثير من الجداول بعد الآن إلى عمود created_at منفصل للترتيب أو العرض. يمكنك استخراج الطابع الزمني من UUID v7 باستدعاء دالة واحدة. هذا يُقلّل تعقيد المخطط ويلغي كتابة إضافية لكل إدراج.

UUID v4 مقابل v7 — المقايضات

الخاصية UUID v4 UUID v7
بتات العشوائية 122 74
ترتيب الفرز عشوائي زمني
أداء INSERT في B-tree ضعيف (تشتت) ممتاز (متسلسل)
كشف الطابع الزمني لا يوجد نعم (بدقة الميلي ثانية)
معيار RFC RFC 4122 (2005)، RFC 9562 (2024) RFC 9562 (2024)
دعم المكتبات شامل ينمو بسرعة (2024–2026)
created_at مضمّن لا نعم

دليل الترحيل — من v4 إلى v7

ترحيل جدول موجود من مفاتيح UUID v4 الأساسية إلى v7 هو عملية متعددة الخطوات. القيد الأساسي: لا يمكنك تغيير قيمة مفتاح أساسي تشير إليه مفاتيح خارجية دون تحديث جميع الجداول المرجعية أيضًا. خطط لنافذة صيانة أو استخدم نهج الكتابة المزدوجة.

-- 1. Add new column
ALTER TABLE orders ADD COLUMN id_v7 UUID;

-- 2. Backfill existing rows (preserve original created_at as the
--    timestamp source; use a UUIDv7 library function in your app)
UPDATE orders SET id_v7 = generate_uuidv7(created_at);

-- 3. Verify no NULLs remain
SELECT COUNT(*) FROM orders WHERE id_v7 IS NULL;

-- 4. Swap columns (requires brief maintenance window)
ALTER TABLE orders ALTER COLUMN id_v7 SET NOT NULL;
ALTER TABLE orders ALTER COLUMN id_v7 SET DEFAULT generate_uuidv7(now());
ALTER TABLE orders RENAME COLUMN id TO id_v4_old;
ALTER TABLE orders RENAME COLUMN id_v7 TO id;
ALTER TABLE orders ADD PRIMARY KEY (id);

للترحيل بدون توقف على الجداول ذات الحركة العالية، النهج الموصى به هو:

  1. أضف عمود id_v7 الجديد بقيمة افتراضية من جانب الخادم تولّد معرّفات v7 من الآن فصاعدًا.
  2. اكمل الصفوف القديمة على دفعات خلال فترات انخفاض حركة المرور، باستخدام الطابع الزمني created_at الموجود كبذرة للجزء الزمني من v7.
  3. حدّث جميع أعمدة المفاتيح الخارجية في الجداول المرجعية لتشير إلى id_v7.
  4. أعد تسمية الأعمدة وأسقط قيد المفتاح الأساسي القديم خلال نافذة صيانة قصيرة.

خطوة ملء الصفوف هي الأطول. بمعدل 10,000 صف في كل دفعة مع تأخير 50 ميلي ثانية بين الدفعات، يستغرق جدول بـ 100 مليون صف نحو 8 ساعات. ابدأ مبكرًا.

الأثر في العالم الحقيقي

نشرت عدة فرق هندسية معايير مقارنة بين UUID v4 وv7 على مجموعات بيانات بحجم إنتاجي. النتائج المتسقة:

  • إنتاجية INSERT: تحسّن بمقدار 2–5 أضعاف على الجداول التي تحتوي على 50+ مليون صف عند التبديل من v4 إلى v7، ويزداد الكسب مع نمو حجم الجدول.
  • زمن انتظار الكتابة p99: ينخفض من مئات الميلي ثانية (v4، تحت الحمل) إلى ميلي ثانية فردية (v7) على نفس الأجهزة.
  • حجم الفهرس: فهارس B-tree على أعمدة v7 أصغر بنسبة 15–30٪ من فهارس v4 المكافئة على نفس البيانات لأن التشتت يترك صفحات نصف فارغة أقل.
  • كفاءة تجمع المخزن المؤقت: ترتفع نسبة إصابة الذاكرة المشتركة للمفتاح الأساسي من ~40٪ (v4، جدول كبير) إلى ~99٪ (v7)، لأن الصفحات الأخيرة فقط تحتاج إلى البقاء دافئة.

الكسب لا يُذكر دون مليون صف تقريبًا. إذا ظل جدولك صغيرًا، التزم بـ v4 لبساطته. فوق 10 ملايين صف بأي معدل INSERT ذي معنى، v7 هو الافتراضي الأفضل.

متى تستخدم كل منهما

استخدم UUID v7 عندما:

  • تُصمّم مخططًا جديدًا وسيكبر الجدول كثيرًا (10+ ملايين صف).
  • يمتلك الجدول معدل INSERT مرتفعًا — أحداث، سجلات، طلبات، رسائل، إشعارات.
  • تريد مفتاحًا أساسيًا يُضاعف كطابع زمني للإنشاء (يلغي عمودًا).
  • أنت على PostgreSQL 17 أو MySQL 8.0+ أو إطار عمل حديث يدعم توليد v7.
  • الفرز بالمعرّف يعادل دلاليًا الفرز بوقت الإنشاء — وهو ما ينطبق على معظم الجداول ذات الإلحاق الكثيف.

استخدم UUID v4 عندما:

  • المعرّف مكشوف للمستخدمين ويجب ألّا يكشف وقت الإنشاء (رموز الدعوة، روابط المشاركة، مقابض الفوترة).
  • الجدول صغير ومستقر — لا تبرر أي فائدة في الأداء تكلفة الترحيل.
  • تولّد معرّفات في سياق يكون فيه الطابع الزمني للإنشاء حساسًا (سجلات المستخدمين الخاصة في منتج يتنافس على مظاهر النمو).
  • تحتاج شيئًا كبيانات اعتماد لمرة واحدة أو رمز — استخدم مولد أسرار مخصصًا، وليس أي إصدار UUID.

ملخص

UUID v4 عشوائي وخاص ومدعوم عالميًا. UUID v7 مرتب زمنيًا وصديق لقواعد البيانات ومعياري الآن في المكتبات وقواعد البيانات الرئيسية. للمخططات الجديدة ذات الجداول الكبيرة أو سريعة النمو، v7 هو الافتراضي الأفضل. للمعرّفات المكشوفة للمستخدمين حيث يُثير كشف الطابع الزمني قلقًا، v4 لا يزال الخيار الصحيح.

ولّد كلا الإصدارين فورًا بـ مولد UUID في Toova — بدون حساب مطلوب. للرموز الأقصر، يُنتج مولد السلاسل العشوائية معرّفات أبجدية رقمية بأي طول.