cuid و cuid2: چرا نسخهی دوم عملاً از صفر بازنویسی شد
وقتی میخوای برای رکوردهای دیتابیست یک شناسهی یکتا بسازی، احتمالاً اول به auto increment فکر میکنی، بعد به UUID، و اگه دنبال چیزی سبکتر و url-friendly تر باشی، به cuid میرسی. ولی نکتهای که خیلی وقتها گم میشه اینه که cuid و cuid2 دو چیز کاملاً متفاوتن — نه یک آپدیت جزئی، بلکه یک بازطراحی کامل با فرضهای امنیتی متفاوت.
cuid اصلی چی بود
cuid (نسخهی اول) حدود سال ۲۰۱۲ ساخته شد تا یک جایگزین برای auto-increment ID و UUID باشه که هم یکتا باشه، هم قابل مرتبسازی بر اساس زمان تولید (chronologically sortable)، و هم برای استفاده توی URL راحتتر از UUID باشه (چون کوتاهتره و کاراکترهای عجیب مثل خطتیرهی اجباری UUID رو نداره).
ساختار یک cuid از چند بخش تشکیل شده: یک پیشوند ثابت (c)، timestamp، یک شمارنده (counter)، اطلاعاتی از fingerprint ماشین و پردازش (که شامل چیزهایی مثل هاستنیم و شناسهی پردازش میشد)، و یک بخش تصادفی. مثال یک cuid واقعی این شکلیه:
cljk3f2j40000qzrmn831i7rn
چرا cuid کنار گذاشته شد
مشکل اصلی cuid وقتی خودش رو نشون داد که مسائل امنیتی جدیتری مطرح شد. چون بخشی از cuid از fingerprint ماشین (host/process info) و timestamp قابل پیشبینی ساخته میشه، این یعنی تا حدی میشه از روی خود ID، اطلاعاتی دربارهی زیرساخت سرور یا ترتیب دقیق تولید رکوردها استنباط کرد. علاوه بر این، چون بخش زمانی cuid قابل پیشبینیه، در برخی سناریوها میشه با حدس زدن IDها، رکوردهای مجاور رو enumerate کرد — دقیقاً همون چیزی که یک شناسهی یکتا نباید اجازه بده.
مشکل دیگه، performance بود: تولید fingerprint ماشین و بعضی محاسبات cuid اصلی روی برخی محیطها (مخصوصاً serverless که هر بار یک instance جدید بالا میاد) کند و ناسازگار عمل میکرد.
cuid2 چطور این مشکلات رو حل کرد
cuid2 عملاً یک طراحی از نو بود، نه یک پچ روی نسخهی قبلی. تفاوتهای اصلیش اینهاست:
اول، امنیت رمزنگاری. cuid2 بهجای تکیه به fingerprint قابلپیشبینی ماشین، از یک تابع هش امن (SHA3) روی ترکیبی از entropy تصادفی، timestamp، counter، و یک مقدار fingerprint ضعیفتر و کماهمیتتر استفاده میکنه. نتیجهی نهایی یک رشتهست که از نظر آماری غیرقابلپیشبینیه — یعنی حتی اگه بدونی الگوریتم چطور کار میکنه، نمیتونی ID بعدی رو حدس بزنی یا از روی یک ID موجود، ID های مجاورش رو enumerate کنی.
دوم، طول متغیر. cuid اصلی طول ثابتی داشت، ولی cuid2 بهصورت پیشفرض طول قابل تنظیم داره (پیشفرض ۲۴ کاراکتر) و میتونی بین ۴ تا ۳۲ کاراکتر تنظیمش کنی، بسته به اینکه چقدر فضای نام یا خوانایی برات مهمه.
سوم، شروع با حرف. هر cuid2 با یک حرف (نه عدد) شروع میشه، که این یک نکتهی فنی مهمه: بعضی سیستمها و زبانهای برنامهنویسی وقتی یک شناسه با عدد شروع بشه، ممکنه اون رو بهاشتباه بهعنوان عدد تفسیر کنن (مثلاً توی نام متغیر جاوااسکریپت یا بعضی زبانهای کوئری). با اجباریکردن شروع با حرف، cuid2 این کلاس از باگها رو کامل حذف میکنه.
چهارم، همچنان قابل مرتبسازی تقریبی بر اساس زمانه (نه دقیق مثل ULID، ولی بهاندازهی کافی برای اکثر کاربردهای عملی)، بدون اینکه این ترتیب، اطلاعات حساس رو لو بده.
مقایسهی مستقیم
| ویژگی | cuid | cuid2 |
|---|---|---|
| الگوریتم | fingerprint + timestamp + counter | SHA3 هش روی entropy تصادفی |
| قابل پیشبینی بودن | نسبتاً بله (ریسک امنیتی) | خیر |
| طول | ثابت | متغیر (پیشفرض ۲۴، قابل تنظیم ۴ تا ۳۲) |
| شروع رشته | حرف c ثابت | همیشه یک حرف تصادفی |
| عملکرد در serverless | ضعیفتر | بهینهشده |
| نگهداری پروژه | متوقف شده | فعال |
مثال کد
استفاده از cuid2 توی جاوااسکریپت خیلی سادهست:
import { createId } from '@paralleldrive/cuid2';
const id = createId();
// مثال خروجی: tz4a98xxat96iws9zmbrgj3a
// اگه بخوای طول متفاوتی داشته باشی
import { init } from '@paralleldrive/cuid2';
const createShortId = init({ length: 10 });
const shortId = createShortId();
توی پروژههای Prisma هم، تعریف یک فیلد با cuid2 به این شکله:
model User {
id String @id @default(cuid2())
}
کِی از هرکدوم استفاده کنیم
اگه پروژهی جدیدی شروع میکنی، عملاً هیچ دلیلی برای انتخاب cuid اصلی وجود نداره — نگهداریش متوقف شده و مشکلات امنیتیش شناختهشدهست. سراغ cuid2 برو، مگر اینکه نیازهای خاصتری داشته باشی: اگه به ترتیب دقیق زمانی (نه فقط تقریبی) نیاز داری، ULID انتخاب بهتریه؛ اگه سازگاری با استانداردهای جهانی و اکوسیستمهای قدیمیتر مهمه، UUID (بهخصوص نسخهی ۷ که خودش هم قابل مرتبسازی زمانیه) گزینهی امنتریه. ولی برای اکثر پروژههای وب مدرن که شناسهی کوتاه، url-friendly، و امن میخوان، cuid2 دقیقاً همونجاییه که باید بری.
