יוצר ULID - צור מזהים ייחודיים וניתנים למיון באופן מקוון בחינם
כלי יוצר ULID בחינם יוצר מזהים ייחודיים וניתנים למיון באופן מיידי. צור מזהי ULID מאובטחים קריפטוגרפית למסדי נתונים, API ומערכות מבוזרות באופן מקוון.
יוצר ULID
תיעוד
מהו ULID?
ULID (מזהה ייחודי אוניברסלי הניתן למיון לקסיקוגרפי) הוא קוד באורך 26 תווים המשמש לסימון רשומות במסד נתונים או אירוע במערכת מבוזרת. הוא משלב את הזמן הנוכחי עם נתונים אקראיים, ולכן רשימת ULID-ים הממוינת לפי סדר אלפביתי מתקבלת ממוינת לפי הזמן שבו כל אחד מהם נוצר. מחולל ULID הוא כלי שמפיק קודים אלה.
מבנה ULID
ל־ULID יש שני חלקים, הכתובים זה אחר זה ללא מפריד:
- חותמת זמן (10 תווים): מספר אלפיות השנייה מאז התאריך 1 בינואר 1970 (תחילת תקופת Unix), המיוצג בעד 48 סיביות.
- אקראיות (16 תווים): 80 סיביות של נתונים אקראיים, המתווספות כדי ששני ULID-ים שנוצרו באותה אלפית שנייה יהיו כמעט בוודאות שונים.
שני החלקים נכתבים באמצעות אלפבית Base32 של קרופורד: הספרות 0–9 והאותיות A-Z, ללא האותיות I, L, O ו־U. ארבע האותיות האלה מושמטות משום שניתן לטעות בינן לבין הספרות 0 ו־1, או בינן לבין עצמן. התוצאה היא מחרוזת קצרה, שקל להקריא בקול ושבטוח להציב ישירות בכתובת URL.
כיצד מחשבים ULID
מחולל ULID בונה מזהה בשלושה שלבים.
- קריאת הזמן הנוכחי באלפיות השנייה מאז תקופת יוניקס. זהו מספר בן 48 סיביות.
- יצירת 80 סיביות של נתונים אקראיים, בדרך כלל ממחולל מספרים אקראיים מאובטח קריפטוגרפית, המפיק 10 בתים אקראיים.
- קידוד שני החלקים בנפרד באמצעות אלפבית Base32 של קרופורד: חותמת הזמן בת 48 הסיביות הופכת ל־10 תווים, והערך האקראי בן 80 הסיביות הופך ל־16 תווים. שתי המחרוזות המקודדות מחוברות ליצירת ULID סופי באורך 26 תווים.
הקידוד פועל באמצעות קריאת המספר בקבוצות של 5 סיביות בכל פעם, משום שכל תו Base32 מייצג אחד מתוך 32 ערכים אפשריים (2^5 = 32). עשרה תווים יכולים להכיל עד 50 סיביות, די מקום לחותמת הזמן בת 48 הסיביות. שישה־עשר תווים מכילים בדיוק 80 סיביות, בהתאם לחלק האקראי ללא שארית.
דוגמה פתורה
נניח שמחולל ULID פועל בחותמת הזמן 1712345678901 (אלפיות השנייה מאז תקופת יוניקס) ומקבל את הבתים האקראיים [12, 240, 88, 3, 199, 45, 6, 231, 128, 17].
שלב 1 – קידוד חותמת הזמן. מחלקים שוב ושוב את 1712345678901 ב־32, לוקחים בכל פעם את השארית וקוראים את הספרות מהאחרונה לראשונה (זוהי המרת בסיס רגילה). מתקבלת המחרוזת בת 10 התווים 01HTQW311N.
שלב 2 – קידוד האקראיות. אורזים את 10 הבתים האקראיים לזרם של 80 סיביות, ואז קוראים 5 סיביות בכל פעם וממירים כל מקטע בן 5 סיביות לתו Base32. מתקבלת המחרוזת בת 16 התווים 1KR5G0Y75M3EF00H.
שלב 3 – חיבור החלקים. ה־ULID הסופי הוא:
101HTQW311N 1KR5G0Y75M3EF00H
2ללא הרווח: 01HTQW311N1KR5G0Y75M3EF00H, בסך הכול 26 תווים.
שימוש במחולל זה
המחולל פועל בדפדפן. כל ULID נבנה משעון הדפדפן ומ־80 סיביות אקראיות חדשות שנלקחות ממחולל המספרים האקראיים הקריפטוגרפי של הדפדפן, ולכן שום מזהה אינו נשלח לשרת או נשלף ממנו.
- מספר ה־ULID-ים מקבל מספר שלם מ־1 עד 50. ברירת המחדל היא 1.
- יצירת ULID מפיקה אצווה חדשה. שינוי הכמות מפיק גם הוא אצווה חדשה.
- העתקה בתוצאה מעתיקה את אותו ULID. כאשר מוצג יותר מ־ULID אחד, העתקת הכול מעתיקה את הרשימה כולה, ULID אחד בכל שורה.
- איפוס מחזיר את הכמות ל־1 ומפיק ULID חדש אחד.
מתחת לתוצאות, החלונית ״מבנה ULID״ מציגה את ה־ULID הראשון באצווה כשהוא מחולק לשני חלקיו: 10 התווים הראשונים (חותמת הזמן) ו־16 התווים האחרונים (האקראיות).
ULID לעומת UUID
| תכונה | ULID | UUID (גרסה 4) |
|---|---|---|
| אורך | 26 תווים | 36 תווים (עם מקפים) |
| ניתן למיון לפי זמן היצירה | כן | לא |
| קידוד | Base32 של קרופורד | הקסדצימלי |
| כולל חותמת זמן | כן (48 סיביות) | לא |
| בטוח לשימוש בכתובת URL ללא קידוד | כן | כן |
שני הפורמטים נועדו ליצור מזהים ייחודיים בין מערכות, ללא גורם מרכזי שמקצה מספרים. ההבדל המעשי העיקרי הוא המיון: מכיוון ש־ULID מתחיל בחותמת זמן, מיון ULID-ים כטקסט רגיל ממיין אותם גם לפי הזמן. ל־UUID אקראי אין תכונה זו, משום שכל חלק בו אקראי.
שימושים נפוצים
- מפתחות ראשיים במסד נתונים: ULID-ים מאפשרים למסד נתונים לאנדקס שורות חדשות בקירוב לפי סדר הכנסתן, דבר שמנועי מסדי נתונים רבים מעבדים ביעילות רבה יותר מאשר מפתחות אקראיים לחלוטין.
- מערכות מבוזרות: שרתים נפרדים יכולים ליצור ULID-ים באופן עצמאי, ללא תיאום עם מונה מרכזי, והתוצאות עדיין כמעט בוודאות ייחודיות.
- יומני אירועים: מכיוון ש־ULID-ים ממוינים לפי זמן, רשימה של מזהי אירועים יכולה לשמש גם כציר זמן משוער.
- מזהי API וקבצים: הפורמט הקבוע, באורך 26 תווים ובטוח לשימוש בכתובות URL, מתאים היטב לכתובות אינטרנט ולשמות קבצים.
פורמטים קשורים של מזהים כוללים את KSUID, המקודד גם הוא חותמת זמן לצורך מיון, ואת מזהי Snowflake, המשמשים את Twitter ואחרים ומשלבים חותמת זמן, מזהה מכונה ומונה.
דוגמאות קוד
כל דוגמה להלן בונה ULID באותה דרך המתוארת במפרט ULID: קידוד חותמת הזמן בת 48 הסיביות ל־10 תווי Base32 של קרופורד, קידוד 80 סיביות של אקראיות ל־16 תווים נוספים וחיבורם.
JavaScript
1const ENCODING_CHARS = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
2
3function encodeULID(time, randomBytes) {
4 // Timestamp: 48 bits -> 10 characters
5 let timestampStr = '';
6 let t = time;
7 for (let i = 0; i < 10; i++) {
8 timestampStr = ENCODING_CHARS[t % 32] + timestampStr;
9 t = Math.floor(t / 32);
10 }
11
12 // Randomness: 80 bits -> 16 characters
13 let result = timestampStr;
14 let bits = 0;
15 let bitCount = 0;
16 for (let i = 0; i < 10; i++) {
17 bits = (bits << 8) | randomBytes[i];
18 bitCount += 8;
19 while (bitCount >= 5) {
20 bitCount -= 5;
21 result += ENCODING_CHARS[(bits >> bitCount) & 0x1f];
22 }
23 }
24 return result;
25}
26
27function generateULID() {
28 const randomBytes = crypto.getRandomValues(new Uint8Array(10));
29 return encodeULID(Date.now(), randomBytes);
30}
31
32console.log(generateULID());
33Python
1import os
2import time
3
4ENCODING_CHARS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
5
6def encode_ulid(time_ms, random_bytes):
7 # Timestamp: 48 bits -> 10 characters
8 chars = []
9 t = time_ms
10 for _ in range(10):
11 chars.append(ENCODING_CHARS[t % 32])
12 t //= 32
13 timestamp_part = "".join(reversed(chars))
14
15 # Randomness: 80 bits -> 16 characters
16 bits = 0
17 bit_count = 0
18 random_part = []
19 for byte in random_bytes:
20 bits = (bits << 8) | byte
21 bit_count += 8
22 while bit_count >= 5:
23 bit_count -= 5
24 random_part.append(ENCODING_CHARS[(bits >> bit_count) & 0x1F])
25
26 return timestamp_part + "".join(random_part)
27
28def generate_ulid():
29 return encode_ulid(int(time.time() * 1000), os.urandom(10))
30
31print(generate_ulid())
32Java
1import java.security.SecureRandom;
2import java.time.Instant;
3
4public class ULIDGenerator {
5 private static final SecureRandom random = new SecureRandom();
6 private static final char[] ENCODING_CHARS =
7 "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
8
9 public static String generateULID() {
10 long timestamp = Instant.now().toEpochMilli();
11 byte[] randomBytes = new byte[10];
12 random.nextBytes(randomBytes);
13
14 // Timestamp: 48 bits -> 10 characters
15 char[] timestampChars = new char[10];
16 long t = timestamp;
17 for (int i = 9; i >= 0; i--) {
18 timestampChars[i] = ENCODING_CHARS[(int) (t % 32)];
19 t /= 32;
20 }
21
22 // Randomness: 80 bits -> 16 characters
23 StringBuilder result = new StringBuilder(new String(timestampChars));
24 long bits = 0;
25 int bitCount = 0;
26 for (byte b : randomBytes) {
27 bits = (bits << 8) | (b & 0xFF);
28 bitCount += 8;
29 while (bitCount >= 5) {
30 bitCount -= 5;
31 result.append(ENCODING_CHARS[(int) ((bits >> bitCount) & 0x1F)]);
32 }
33 }
34 return result.toString();
35 }
36
37 public static void main(String[] args) {
38 System.out.println(generateULID());
39 }
40}
41שאלות נפוצות
למה משמש ULID? ULID משמש כמזהה ייחודי של שורה במסד נתונים, משאב API או אירוע שנרשם ביומן, במקרים שבהם גם מיון לפי זמן היצירה מועיל.
מהו אורכו של ULID? אורכו של ULID הוא תמיד 26 תווים: 10 תווים עבור חותמת הזמן ו־16 עבור החלק האקראי.
באיזה קידוד משתמש ULID? ULID משתמש באלפבית Base32 של קרופורד, המורכב מהספרות 0–9 ומהאותיות A-Z, לאחר הסרת I, L, O ו־U כדי למנוע בלבול עם תווים אחרים.
במה ULID שונה מ־UUID? ULID ממוין לפי זמן היצירה ואורכו 26 תווים. UUID אקראי (גרסה 4) אינו ממוין לפי זמן ואורכו 36 תווים, כולל מקפים.
מה הסבירות להתנגשות בין שני ULID-ים? 80 סיביות האקראיות הופכות התנגשויות לבלתי סבירות ביותר. כדי להגיע לסיכוי של 50% לכך ששניים כלשהם יתאימו, יהיה צורך ליצור בערך 1.3 טריליון ULID-ים באותה אלפית שנייה.
האם ניתן ליצור ULID-ים ללא חיבור לאינטרנט? כן. ULID זקוק רק לשעון המקומי ולמחולל מספרים אקראיים, ולכן ניתן ליצור אותו כולו במצב לא מקוון.
מקורות
- "ULID Specification." GitHub, https://github.com/ulid/spec.
- "Crockford's Base32 Encoding." http://www.crockford.com/base32.html.