השוואה מעמיקה בין UUID v4, UUID v7 ו-ULID: איך מאזנים סדר כרונולוגי, יעילות וקריאות
UUID ו-ULID הם מנגנונים להפקת מזהים ייחודיים במערכות מבוזרות ובמסדי נתונים. הם דומים בצורתם, אך נבדלים ברמת התקינה, ביעילות האחסון, בקריאות אנושית ובסיכון לחשיפת מידע. במאמר זה אנו משווים בין שלוש הגישות המרכזיות – UUID v4, UUID v7 ו-ULID – ומסיימים בתוספת קצרה על UUID v1 ו-UUID v6.
רוצים להתנסות בדפדפן? הכלים הבאים פועלים מקצה לקצה בצד הלקוח:
UUID v4: גרסה אקראית לחלוטין
UUID v4 מקצה 122 מתוך 128 הביטים לאקראיות, וכך ההסתברות להתנגשות זעירה מאוד. המחיר הוא ש-אין סדר כרונולוגי במזהים, ולכן אינדקסים מסוג B-Tree חווים פיצולי עמודים תכופים וסובלים מירידה בלוקאליות ההוספה. הוא מתאים למצבים שבהם “אקראיות טהורה” היא כל מה שצריך – למשל מזהי סשן או אסימונים חד-פעמיים.
UUID v7: האבולוציה המסודרת כרונולוגית
UUID v7, שסטנדרטיזציה שלו הושלמה ב-RFC 9562 בשנת 2024, שומר את 48 הביטים הראשונים כחיתום זמן UNIX במילישניות, ואת היתר ממלא באקראיות. סדר לקסיקוגרפי (השוואת מחרוזות) זהה לסדר ההפקה (ציר הזמן), ולכן אפשר להשתמש ב-v7 כמפתח ראשי ועדיין ליהנות מלוקאליות באינדקס. הוא נכנס ישירות לטיפוסי uuid
או BINARY(16)
קיימים – יתרון משמעותי.
- תקציב האקראיות: מעבר לביטי ה-
version
וה-variant
, ל-v7 נשארים כ-74 ביט של אקראיות. מימושים מסוימים מבצעים Monotonic Generation (התאמות בתוך אותה מילישניה למניעת התנגשות), אך רמת האנטרופיה עדיין גבוהה מאוד. - חשיפת מידע: מזהה v7 חושף את זמן ההפקה בדיוק של מילישניה. אם מייצרים כמות גדולה של מזהים עם מחולל אקראי חלש, ייתכן שקל יותר לנחש מזהים סמוכים. במערכות פנימיות זה בדרך כלל נסבל, אך כאשר מפרסמים מזהים לציבור יש לבחון את הסיכון.
ULID: מזהים ידידותיים לבני אדם
ULID, שהוצג ב-2016 והפך ל-תקן דה-פקטו, משתמש ב-Crockford Base32 באורך 26 תווים תוך השמטת תווים מבלבלים (O/I/L/1). הוא נוח לשימוש ב-URL או בהעתק-הדבק. בדומה ל-v7, 48 הביטים הראשונים הם חותמת זמן במילישניות, ולכן סדר מילולי תואם תמיד את סדר הזמן.
- הבטחת סדר: השוואה מילולית בין ULID-ים (Base32) היא תמיד כרונולוגית, ומנגנון Monotonic Generation נפוץ כדי להימנע מהתנגשויות.
- צורת אחסון: אחסון כ-
TEXT(26)
מעניק קריאות גבוהה, אך במסדי נתונים נדרש המרה בין Base32 ל-16 בתים אם רוצים לשמור בבינארי. ברוב ה-RDBMS טיפוס ה-UUID המובנה לא תומך ב-ULID. - חשיפת מידע: בדומה ל-v7, נחשפת חותמת הזמן (מילישניה). אם אינכם רוצים לחשוף סדר הנפקה ב-API ציבורי, שקלו אלטרנטיבה.
טבלת השוואה (מבט מעשי)
היבט | UUID v4 | UUID v7 | ULID |
---|---|---|---|
סטנדרטיזציה | RFC 4122 | RFC 9562 (2024) | דה-פקטו, ללא תקן רשמי |
מבנה ביטים | 128 ביט עם 122 ביט אקראיים | 48-bit UNIX ms + יתר האקראיות (Monotonic אופציונלי) |
48-bit UNIX ms + 80-bit אקראיים |
אנטרופיית אקראיות | ≈122 ביט | ≈74 ביט (לאחר version/variant) | 80 ביט |
ייצוג מחרוזת | 36 תווים (הקסה + מקפים) | 36 תווים (הקסה + מקפים) | 26 תווים (Crockford Base32) |
סדר טבעי בטקסט | ✗ (אקראי) | ✓ (סדר מילולי = סדר זמן) | ✓ (סדר מילולי = סדר זמן) |
סדר טבעי בבינארי | ✗ | ✓ (BINARY(16) בסדר עולה לפי זמן) |
✓ (אם 6 בתים של הזמן מופיעים תחילה בביג-אנדיאן) |
התאמה למסדי נתונים | uuid / BINARY(16) אידאליים |
uuid / BINARY(16) אידאליים |
TEXT(26) לקריאות; BINARY(16) דורש המרה ורוב טיפוסי ה-UUID לא מקבלים |
לוקאליות אינדקס | נמוכה (הוספה אקראית) | גבוהה (הוספה בקצה; להיזהר מהוט-ספוט) | גבוהה (אותו עיקרון) |
עלות בדיקת שוויון | נמוכה (השוואת 16 בתים) | נמוכה (השוואת 16 בתים) | TEXT יקר יותר; BINARY זול |
עלות קידוד | המרה בין הקסה ל-16 בתים (קלה) | המרה בין הקסה ל-16 בתים (קלה) | המרה בין Base32 ל-16 בתים (כבדה יותר) |
קריאות אנושית / התאמה ל-URL | נמוכה | נמוכה | גבוהה |
חשיפת מידע | אין | חותמת זמן (ms) | חותמת זמן (ms) |
שימושים אופייניים | מזהי סשן, אסימונים חד-פעמיים | מפתחות ראשיים במסדי נתונים, מזהי אירועים, לוגים בזמן | מזהים ציבוריים ולוגים שבהם הקריאות חשובה |
השלמה (Hot Spot ב-v7/ULID): כאשר כל ההוספות מתנקזות לשארד או ל-Primary בודד, גם סדר עולה יכול לייצר עומס בקצה ה-B-Tree. שקלו Prefix Shuffle קל או שילוב של מפתח שארד כדי לפזר את העומס.
הערות יישום במסדי נתונים (PostgreSQL / MySQL / SQLite)
- PostgreSQL
- v4/v7: טיפוס
uuid
הוא הבחירה המועדפת. מזרימים כמחרוזת וממירים ל-uuid
כדי ליהנות מהסדר. - ULID: השתמשו ב-
char(26)
/text
או ב-bytea(16)
לאחר המרה מ-Base32.
- v4/v7: טיפוס
- MySQL / InnoDB
BINARY(16)
מהיר וחסכוני עבור v4/v7. v7 נשאר מסודר בזמן כאשר מאחסנים בביג-אנדיאן. ULID מתאים ל-CHAR(26)
אוBINARY(16)
עם המרה.
- SQLite
- אין טיפוס UUID מובנה. עובדים עם
BLOB(16)
אוTEXT
. כאשר ה-BLOB ממודד, ההשוואה יעילה למדי.
- אין טיפוס UUID מובנה. עובדים עם
חיזוק היבטי האבטחה
- עמידות לניחוש: v7 ו-ULID חולקים את ביטי הזמן. אם מפיקים כמות גדולה של מזהים באותה מילישניה והאקראיות חלשה, קיים סיכון יחסי לניחוש מזהים סמוכים. השתמשו ב-PRNG קריפטוגרפי ושמרו על Monotonic Generation ללא הטיה.
- דליפת מטא-נתונים: המזהים חושפים את זמן ההפקה וכמות ההנפקות המשוערת. בממשקי API ציבוריים עדיף להפריד בין מזהים פנימיים לבין המזהים שנחשפים החוצה.
סיכום: איך לבחור
- UUID v4: אקראיות מלאה. אין סדר טבעי. סיכוי ההתנגשות תיאורטי בלבד. מתאים למזהי סשן, אסימוני CSRF וכדומה.
- UUID v7: UUID מסודר שסטנדרטיזציה שלו הושלמה. בחירה מעולה למערכות פנימיות ולמפתחות ראשיים. Monotonic Generation מעקר כמעט כל סיכון להתנגשות.
- ULID: הידידותי ביותר לאדם. נהדר ל-URL וללוגים כאשר הקריאות חשובה, אך לעיתים פחות נוח כמפתח פנימי לעומת v7. גם כאן Monotonic Generation מצמצם התנגשויות.
נספח: UUID v1 ו-v6 (בקצרה)
-
UUID v1 (מבוסס זמן + MAC) משלב חותמת זמן של 60 ביט (דיוק 100 ננו-שניות) עם מזהה צומת (לרוב כתובת MAC). מציע סדר כרונולוגי חזק, אך חושף מידע על המארח ורגיש לבעיות של Clock Rollback. בשל שיקולי פרטיות מודרניים, ממליצים לעיתים קרובות להימנע ממנו.
-
UUID v6 (סידור מחדש של v1) מסדר מחדש את חותמת הזמן של v1 לביג-אנדיאן כדי להבטיח סדר מילולי. הוצג היסטורית כ"v1 שקל למיין", אך התקינה התכנסה אל v7. בפרויקטים חדשים כמעט תמיד עדיף לבחור ב-v7.
צ’ק-ליסט ליישום
- השתמשו ב-מחולל אקראי קריפטוגרפי כדי להבטיח אנטרופיה מספקת.
- בעת Monotonic Generation עבור v7/ULID, ודאו שההתאמות אינן יוצרות הטיית אקראיות בזמן שמונעים התנגשויות.
- העדיפו עמודות
uuid
/BINARY(16)
כדי להימנע מעלויות Collation של טקסט. - הגדירו מדיניות למזהים חיצוניים (האם חושפים סדר הנפקה?). במידת הצורך הפרידו בין מזהים פנימיים וחיצוניים.
בשורה התחתונה: בעולם הנוכחי UUID v7 הוא ברירת המחדל למפתחות ראשיים פנימיים, בעוד ULID מבריק כאשר קריאות אנושית או אילוצי עבר נכנסים לתמונה. שמרו את v4 או מנגנונים אחרים למקרים יוצאי דופן שבהם חשיפת זמן ההפקה אסורה לחלוטין.