مقایسه UUID، ULID، Nano ID و شناسههای Auto-Increment
پنج روش رایج ساخت شناسه، مقایسهشده بر اساس چیزهایی که در عمل اهمیت دارند: اندازه، ترتیب، احتمال تکرار، رفتار در پایگاه داده و اطلاعاتی که یک شناسه لو میدهد.
انتخاب قالب شناسه به سه سؤال برمیگردد. آیا شناسهها باید بر اساس زمان ساخت مرتب شوند؟ آیا در URLها یا APIهای عمومی دیده میشوند؟ و آیا پایگاه داده آنها را در یک ستون بومی ۱۲۸ بیتی نگه میدارد یا به شکل متن؟ UUID v7 که در مه ۲۰۲۴ در RFC 9562 استاندارد شد، پاسخ این سؤالها را برای بسیاری از پروژهها عوض کرد: همان ترتیب زمانی را که ULID را محبوب کرده بود، در قالبی ارائه میدهد که هر ستون UUID و هر کتابخانهای از قبل میپذیرد.
نگاه کلی
| Auto-increment | UUID v4 | UUID v7 | ULID | Nano ID | |
|---|---|---|---|---|---|
| نمونه | 1042 | f47ac10b-58cc-4372-a567-0e02b2c3d479 | 01926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f10 | 01J9QKMZ1BFN79M7SVBJ6JWVRG | V1StGXR8_Z5jdHi6B-myT |
| اندازه باینری | ۴ یا ۸ بایت | ۱۶ بایت | ۱۶ بایت | ۱۶ بایت | به شکل متن ذخیره میشود |
| طول متنی | تا ۱۰ یا ۱۹ رقم | ۳۶ | ۳۶ | ۲۶ | ۲۱ بهطور پیشفرض |
| بیتهای تصادفی | ندارد | ۱۲۲ | تا ۷۴ | ۸۰ | ۱۲۶ بهطور پیشفرض |
| مرتب شدن بر اساس زمان ساخت | بله | خیر | بله، تا دقت میلیثانیه | بله، تا دقت میلیثانیه | خیر |
| ساخت بدون پایگاه داده | خیر | بله | بله | بله | بله |
| مشخصات | قابلیت خود پایگاه داده | RFC 9562 | RFC 9562 | مشخصات جامعهمحور | کتابخانه |
| چه چیزی را لو میدهد | تعداد و ترتیب | هیچ | زمان ساخت | زمان ساخت | هیچ |
ساختار هرکدام
- Auto-increment شمارندهای است که پایگاه داده نگه میدارد.
INTعلامتدار ۳۲ بیتی در ۲٬۱۴۷٬۴۸۳٬۶۴۷ وBIGINTعلامتدار در ۹٬۲۲۳٬۳۷۲٬۰۳۶٬۸۵۴٬۷۷۵٬۸۰۷ به سقف خود میرسد. اپلیکیشن فقط بعد از درج رکورد از شناسه باخبر میشود، مگر اینکه از قبل مقدارهایی را از یک sequence رزرو کند. - UUID v4 از ۱۲۲ بیت تصادفی و ۶ بیت ثابت تشکیل شده که نسخه و variant را مشخص میکنند.
- UUID v7 با یک برچسب زمانی ۴۸ بیتی Unix بر حسب میلیثانیه شروع میشود و پس از آن نسخه، ۱۲ بیت به نام
rand_a، variant و ۶۲ بیت به نامrand_bمیآید. RFC 9562 به سازنده اجازه میدهد بخشی از این بیتها را بهعنوان شمارنده به کار ببرد تا شناسههایی که در یک میلیثانیه ساخته میشوند هم به ترتیب بیرون بیایند. سازنده این سایت همین کار را با ۱۲ بیتrand_aانجام میدهد. - ULID یک برچسب زمانی ۴۸ بیتی بر حسب میلیثانیه بهعلاوه ۸۰ بیت تصادفی است که به شکل ۲۶ نویسه در Base32 کراکفورد نوشته میشود: ۱۰ نویسه برای زمان و ۱۶ نویسه برای بخش تصادفی. این الفبا حروف I، L، O و U را ندارد و به بزرگی و کوچکی حروف حساس نیست. در حالت monotonic، هر ULID که در همان میلیثانیه ULID قبلی ساخته شود، بخش تصادفی آن را برمیدارد و یک واحد به آن اضافه میکند.
- Nano ID رشتهای از نویسههای تصادفی است؛ بهطور پیشفرض ۲۱ نویسه از الفبای ۶۴ نویسهای
A-Z a-z 0-9 _ -. هر نویسه ۶ بیت دارد که در مجموع ۱۲۶ بیت میشود. هم طول و هم الفبا قابل تغییرند و تعداد بیتهای تصادفی هم همراه آنها تغییر میکند.
ULID و UUID هر دو عددهای ۱۲۸ بیتیاند که به شکلهای متفاوتی نوشته میشوند. 01J9QKMZ1BFN79M7SVBJ6JWVRG و 01926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f10 یک مقدارند، یکی در Base32 و دیگری در هگز. به همین دلیل میتوان ULID را در ستون بومی UUID، مثل uuid در PostgreSQL، ذخیره کرد و ترتیبش آنجا هم حفظ میشود. البته ULID که از ابتدا یک UUID v7 نبوده، در جای بیتهای نسخه و variant بیتهای دلخواه دارد، پس ممکن است یک اعتبارسنج سختگیر UUID آن را رد کند.
احتمال تکرار، با محاسبه
برای شناسههای کاملاً تصادفی، تقریب مسئله روز تولد (birthday) صدق میکند: اگر n شناسه از میان N حالت ممکن انتخاب شود، که N برابر است با ۲ به توان تعداد بیتهای تصادفی، احتمال دستکم یک تکرار تقریباً برابر است با یک منهای e به توان منفی n²/2N. این احتمال وقتی n به حدود ۱٫۱۸ برابر جذر N برسد، به ۵۰ درصد میرسد.
| روش | بیتهای تصادفی | تعداد شناسه برای ۵۰ درصد احتمال تکرار | احتمال تکرار در میان ۱ میلیارد شناسه |
|---|---|---|---|
| UUID v4 | ۱۲۲ | ۲٫۷ × ۱۰ به توان ۱۸ | ۹٫۴ × ۱۰ به توان منفی ۲۰ |
| Nano ID با ۲۱ نویسه | ۱۲۶ | ۱٫۱ × ۱۰ به توان ۱۹ | ۵٫۹ × ۱۰ به توان منفی ۲۱ |
| Nano ID با ۱۰ نویسه | ۶۰ | ۱٫۳ × ۱۰ به توان ۹ | حدود ۳۵ درصد |
ردیف آخر همان است که باید به خاطر بسپارید: کوتاه کردن Nano ID برای زیباتر شدن URLها خیلی سریع مقاومت در برابر تکرار را از بین میبرد، پس پیش از انتخاب طول، همین حساب را انجام دهید.
در UUID v7 و ULID، دو شناسه فقط وقتی ممکن است تکراری شوند که میلیثانیه یکسانی داشته باشند؛ پس n تعداد شناسههایی است که در یک میلیثانیه، توسط سازندههایی که شمارنده مشترکی ندارند، ساخته میشوند. با ۱٬۰۰۰ شناسه از این نوع در یک میلیثانیه، احتمال تکرار با ۷۴ بیت تصادفی حدود ۲٫۶ × ۱۰ به توان منفی ۱۷ و با ۸۰ بیت ULID حدود ۴٫۱ × ۱۰ به توان منفی ۱۹ است. در عمل، خطر واقعی در همه این روشها منبع تصادف ضعیف است، مثل Math.random، نه این حسابوکتاب.
رفتار در ایندکس پایگاه داده
کلیدهای اصلی معمولاً در یک ایندکس B-tree نگهداری میشوند. وقتی هر کلید جدید از کلید قبلی بزرگتر باشد، مثل Auto-increment، UUID v7 و ULID، درجها در لبه سمت راست ایندکس مینشینند، صفحهها کاملاً پر میشوند و ردیفهایی که بهتازگی نوشته شدهاند نزدیک هم میمانند. کلیدهای تصادفی، یعنی UUID v4 و Nano ID، هر جایی ممکن است بنشینند: صفحهها تقسیم میشوند (page split)، نیمهخالی میمانند و وقتی ایندکس دیگر در حافظه جا نشود، هر درج ممکن است به یک خواندن تصادفی منجر شود.
این اثر در MySQL و MariaDB با موتور InnoDB شدیدتر است، چون خود ردیفها را به ترتیب کلید اصلی ذخیره میکنند؛ پس کلیدهای تصادفی نهفقط ایندکس، بلکه کل جدول را پراکنده میکنند. PostgreSQL ردیفها را در یک heap جداگانه نگه میدارد، پس تفاوت فقط به ایندکس محدود میشود.
قالب ذخیرهسازی به همان اندازه روش ساخت شناسه اهمیت دارد:
- UUIDها و ULIDها را هر جا نوع بومی ۱۶ بایتی وجود دارد، در همان ذخیره کنید:
uuidدر PostgreSQL،UUIDدر MariaDB 10.7 و بعد از آن، وBINARY(16)در MySQL.CHAR(36)بیش از دو برابر فضا میگیرد و همه ایندکسها را بزرگتر میکند. - در MySQL، برای مقدارهای v7 پرچم جابهجایی را به
UUID_TO_BIN(uuid, 1)ندهید. این پرچم فیلدهای زمان را به نفع UUIDهای v1 جابهجا میکند و ترتیب v7 را به هم میزند. - نوع
uniqueidentifierدر SQL Server اول شش بایت آخر را مقایسه میکند، پس مقدارهای v7 که آنجا ذخیره میشوند بر اساس زمان مرتب نمیشوند. جزئیات را در راهنمای تفاوت UUID و GUID ببینید. - Nano IDها رشتهاند و به شکل
CHARیاVARCHARذخیره میشوند؛ اگر بزرگی و کوچکی حروف باید اهمیت داشته باشد، با یک collation باینری.
URLها و اطلاعاتی که یک شناسه لو میدهد
هر پنج قالب بدون کدگذاری درصدی در URL امناند: رقمهای هگز، خط تیره، Base32 کراکفورد و الفبای Nano ID همگی جزو نویسههای unreserved در RFC 3986 هستند. تفاوت در طول (۳۶، ۲۶ یا ۲۱ نویسه) و در حساسیت به بزرگی و کوچکی حروف است. UUIDها و ULIDها به بزرگی و کوچکی حروف حساس نیستند، پس از سیستمهایی که حروف را بزرگ یا کوچک میکنند سالم عبور میکنند؛ Nano IDها اینطور نیستند.
اینکه یک شناسه چه چیزی را لو میدهد، اغلب عامل تعیینکننده است:
- Auto-increment حجم کار را لو میدهد. اگر شماره فاکتور شما امروز ۱۰۴۲ باشد و یک هفته بعد ۱۳۴۲، یعنی شرکت در آن هفته حدود ۳۰۰ فاکتور صادر کرده است. شناسههای ترتیبی امتحان کردن رکوردهای مجاور را هم بسیار ساده میکنند و همین، هر بررسی دسترسی جاافتاده را به نشت داده تبدیل میکند.
- UUID v7 و ULID زمان ساخت رکورد را تا دقت میلیثانیه لو میدهند. برای سفارشها این بهندرت مشکلی ایجاد میکند؛ برای حسابهای کاربری یا پروندههای پزشکی ممکن است مشکلساز باشد. یک UUID v7 را در بخش بررسی تولیدکننده UUID v7 بچسبانید تا برچسب زمانی آن را ببینید.
- UUID v4 و Nano ID هیچ اطلاعاتی درباره زمان یا مکان ساختشان لو نمیدهند.
هیچکدام از این شناسهها راز به حساب نمیآید. شناسهای که قابل حدس نیست کاوش را سختتر میکند، اما جای بررسی اینکه آیا کاربر اجازه دیدن آن رکورد را دارد نمیگیرد.
کدام را انتخاب کنیم
- جدولهای داخلی در یک پایگاه داده که هیچوقت به کاربر نشان داده نمیشوند: Auto-increment از نوع
BIGINT. کوچکترین و سادهترین کلید است. - کلیدهای اصلی که اپلیکیشن یا چند سرویس مختلف میسازند، جایی که ستون بومی UUID در دسترس است: UUID v7.
- شناسههای عمومی که زمان ساختشان باید پنهان بماند، شناسههای همبستگی (correlation ID) و نام فایلها: UUID v4، از تولیدکننده UUID v4 یا
crypto.randomUUID(). - شناسههای کوتاه در URL که ترتیب در آنها مهم نیست: Nano ID با طول پیشفرض یا نزدیک به آن.
- ULID وقتی مجموعه فناوری شما از قبل از آن استفاده میکند یا شکل متنی کوتاهتر و غیرحساس به بزرگی و کوچکی حروف را میخواهید. برای پروژههای جدید، UUID v7 همین نیازها را در قالبی استاندارد پوشش میدهد.
ترکیبی رایج، مزیتهای هر دو طرف را جمع میکند: یک کلید اصلی BIGINT یا UUID v7 برای joinها و ایندکسها، بهعلاوه یک شناسه عمومی تصادفی جداگانه با ایندکس یکتا برای URLها و APIها.