مقایسه UUID، ULID، Nano ID و شناسه‌های Auto-Increment

پنج روش رایج ساخت شناسه، مقایسه‌شده بر اساس چیزهایی که در عمل اهمیت دارند: اندازه، ترتیب، احتمال تکرار، رفتار در پایگاه داده و اطلاعاتی که یک شناسه لو می‌دهد.

همین حالا امتحان کنید: تولید UUID v7 تولید UUID v4

انتخاب قالب شناسه به سه سؤال برمی‌گردد. آیا شناسه‌ها باید بر اساس زمان ساخت مرتب شوند؟ آیا در URLها یا APIهای عمومی دیده می‌شوند؟ و آیا پایگاه داده آن‌ها را در یک ستون بومی ۱۲۸ بیتی نگه می‌دارد یا به شکل متن؟ UUID v7 که در مه ۲۰۲۴ در RFC 9562 استاندارد شد، پاسخ این سؤال‌ها را برای بسیاری از پروژه‌ها عوض کرد: همان ترتیب زمانی را که ULID را محبوب کرده بود، در قالبی ارائه می‌دهد که هر ستون UUID و هر کتابخانه‌ای از قبل می‌پذیرد.

نگاه کلی

Auto-incrementUUID v4UUID v7ULIDNano ID
نمونه1042f47ac10b-58cc-4372-a567-0e02b2c3d47901926f3a-7c2b-7d4e-9a1f-3b5c8d2e6f1001J9QKMZ1BFN79M7SVBJ6JWVRGV1StGXR8_Z5jdHi6B-myT
اندازه باینری۴ یا ۸ بایت۱۶ بایت۱۶ بایت۱۶ بایتبه شکل متن ذخیره می‌شود
طول متنیتا ۱۰ یا ۱۹ رقم۳۶۳۶۲۶۲۱ به‌طور پیش‌فرض
بیت‌های تصادفیندارد۱۲۲تا ۷۴۸۰۱۲۶ به‌طور پیش‌فرض
مرتب شدن بر اساس زمان ساختبلهخیربله، تا دقت میلی‌ثانیهبله، تا دقت میلی‌ثانیهخیر
ساخت بدون پایگاه دادهخیربلهبلهبلهبله
مشخصاتقابلیت خود پایگاه دادهRFC 9562RFC 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ها.

سوالات متداول

آیا ULID از UUID v7 بهتر است؟

هر دو به یک شکل ساخته شده‌اند: یک برچسب زمانی ۴۸ بیتی بر حسب میلی‌ثانیه و بعد بیت‌های تصادفی، در مجموع ۱۲۸ بیت. UUID v7 استانداردی از IETF است که با ستون‌ها و اعتبارسنج‌های UUID سازگار است. ULID شکل متنی کوتاه‌تر و غیرحساس به بزرگی و کوچکی حروف دارد و به‌جای حداکثر ۷۴ بیت، ۸۰ بیت تصادفی.

آیا Nano ID به اندازه UUID یکتاست؟

با طول پیش‌فرض ۲۱ نویسه، بله: ۱۲۶ بیت تصادفی دارد، در برابر ۱۲۲ بیت UUID v4. Nano IDهای کوتاه‌تر این مزیت را سریع از دست می‌دهند؛ با ۱۰ نویسه، بعد از حدود ۱٫۳ میلیارد شناسه احتمال تکرار به ۵۰ درصد می‌رسد.

آیا مقدارهای UUID v7 ممکن است تکراری شوند؟

فقط شناسه‌هایی که در یک میلی‌ثانیه و توسط سازنده‌های مستقل ساخته می‌شوند. با ۱٬۰۰۰ شناسه در یک میلی‌ثانیه این احتمال حدود ۲٫۶ × ۱۰ به توان منفی ۱۷ است، پس سالم بودن منبع تصادف بسیار مهم‌تر از خود قالب است.

چرا UUIDهای تصادفی به‌عنوان کلید اصلی کند هستند؟

هر کلید جدید در جایی تصادفی از ایندکس B-tree می‌نشیند و در جدول‌های بزرگ باعث تقسیم صفحه‌ها، صفحه‌های نیمه‌خالی و خواندن بیشتر از دیسک می‌شود. شناسه‌های مرتب بر اساس زمان، مثل UUID v7 یا ULID، در عوض به انتهای ایندکس اضافه می‌شوند.
همین حالا امتحان کنید: تولید UUID v7 تولید UUID v4