UUID vs. Sequential ID as Primary Key


1. Overview:

در این آموزش، قصد داریم به تفاوت های بین UUID و Sequential ID به عنوان primary key بپردازیم.
هنگام طراحی و چیدن یک دیتابیس، انتخاب نوع primary key مناسب برای performance، scalability، و یکپارچگی داده های آن سیستم حیاتیه.

جداول درون دیتابیس باید یک ستون primary key داشته باشن که هم unique باشه و هم null نشه. به این ترتیب، مقدار primary key میتونه به عنوان مقداری منحصر به فرد برای هر ردیف شناخته بشه.

یک از تصمیمات اولیه (primary) در انتخاب یک primary key اینه که از UUID استفاده کنیم یا از sequential ID. از آنجا که هر دو رویکرد مزایا و معایبی داره، بهترین گزینه بستگی به use case و هدف مشخص آن سیستم داره.

2. UUID:

تعریف شده توسط استاندارد RFC 4122، یک UUID (Universally Unique IDentifier) مقدار bit 128 را نشان می‌دهد.

امروزه، بیشتر relational database (RDB) ها از تایپ UUID پشتیبانی میکنند:

  • Oracle - the RAW(16) type
  • SQL Server - the NEWID() function
  • PostgreSQL - the UUID type
  • MySQL - the BINARY(16) type or the UUID() function

با این حال، اگر database چنین تایپی رو ساپورت نکنه، باید آن را به عنوان تایپ BINARY(16) معرفی کنیم. (CHAR(32) هم میتونه به خوبی کار کنه. ولی اون فضای اضافی و غیر ضروری برای ذخیره کردن مقادیر می‌خواد)
حالا. بیاید بپردازیم به مزایا و معایب استفاده از UUID به عنوان primary key.

2.1. Distributed Systems:

یک UUID میتونه تو کار با distributed system ها که database ها را به اشتراک می‌گذارند، کاربردی و مفید باشه.
هدف اصلی وجود UUID به عنوان primary key، توانایی انتقال دیتا بین distributed system ها هست.
با UUID به عنوان primary key، میتونیم گارانتی کنیم که تصادفی بین primary key ها اتفاق نمی‌افته و نیاز نیست یک سیستم هماهنگی مرکزی برای مدیریت unique بودن primary key ها بسازیم.
به علاوه، استفاده از UUID، پیچیدگی integrate کردن بین دیتابیس ها رو کاهش میده.
دیتابیس های Distributed و NoSQL (یعنی MongoDB یا CouchDB) از UUID به جای مقادیر عددی برای key هاشون استفاده می‌کنند.

2.2. Uniqueness:

و UUID ها به صورت global منحصر به فرد هستند. یعنی میتونیم هر ردیف دیتابیسمون رو با یک ID که بین table هامون، دیتابیس هامون و سیستم هامون unique هست تعریف کنیم. مورد دوم خیلی تو distributed system ها مهمه، جایی که ممکنه node ها رو به صورت dynamic حذف یا اضافه کنیم.
به علاوه، آنها امنیت بیشتری ارائه میدن چرا که پیش‌بینی مقدار بعدیشون خیلی سخته، بنابراین، این موضوع باعث میشه که حدس زدن ID برای user های مخرب تقریبا غیر‌ممکن باشه.
علاوه بر این، آنها اطلاعاتی در مورد business data را در معرض نمایش قرار نمی‌دهند. پس میتونیم به صورت امن ازشون به عنوان بخشی از آدرس URL استفاده کنیم.

2.3. Generating the Value:

یکی از مزایای دیگر UUID اینه که میتونه توسط اپلیکیشن یا دیتابیس به طور خودکار تولید بشه.
معمولا هنگام داشتن sequential ID به دیتابیس سیستممان برای تولید آن و افزایش مقدار primary key به طور خودکار، تکیه می‌کنیم. در غیر این صورت، پیدا کردن مقدار بعدی ای که باید استفاده کنیم (مثلا اینکه بعد از ID یک باید ID دو باشه) کمی پیچیده خواهد بود.
با این حال، کنار گذاشتن مسئولیت تولید sequential primary key یک جنبه منفی داره، اونم اینه که ما مقدار ID ردیف جدید رو بعد از اجرا کردن دستور insert می‌گیریم.
در حالی که بر عکس می‌تونیم UUID رو تو کدمون بسازیم جای اینکه به دیتابیس بگیم برامون بسازه و از اونجایی که UUID منحصر به فرد و unique هست و به صورت توالی نیست، نیازی نیست که نگران مقادیر قبلی باشیم.
بنابراین، میتونیم بلافاصله مقدار primary key رو داشته باشیم و نمی‌خواد صبر کنیم تا کوئری insert اجرا بشه.

2.4. Memory Usage:

سایز UUID صد و بیست و هشت بیت هست، که دو برابر سایز یک BIGINT و چهار برابر سایز INTEGER میشه.
تو RDB ها معمولا از BIGINT برای ذخیره کردن شناسه های عددی استفاده می‌کنیم، و این کار ممکنه تفاوت بزرگی رقم نزنه چرا که UUID تنها دو برابر بزرگ تره.
با این حال، داشتن یک primary key بزرگ ممکنه منجر به مشکلات performance بشه، به خصوص وقتی که صحبت از query های select و indexing میشه.

2.5. Readability:

یک UUID از سی و دو hex digit که با چهار تا dash از هم جدا شدن تشکیل شده، که حفظ کردنش رو مشکل می‌کنه.
بنابراین، نمایش یک UUID ممکنه user-friendly در نظر گرفته نشه و ممکنه به سختی قابل بیان باشه و خواندن و به خاطر سپردن آن راحت نیست.
از طرف دیگه، sequential ID ها خوندن و حفظ کردنشون راحته.
با این حال، این سؤال جای بحث داره که آیا مقدار ID باید قابل خوندن برای انسان ها باشه یا نه.

2.6. Sorting:

یک عیب دیگر این است که ما نمی‌توانیم به طور طبیعی UUID ها رو sort کنیم.
به خاطر محدودیتشون، ممکنه مجبور شیم از یک column دیگه هم استفاده کنیم. برای مثال، ستون created_at که نشانگر زمان ساخت آن آیتم می باشد، می‌تونه برای دستیابی به آیتم های sort شده استفاده بشه. بنابراین، این امکان وجود داره که زمان اجرای هر query افزایش پیدا کنه.

3. Sequential ID:

و sequence به نوع مشخصی از یک شی، که مقادیر unique و مرتب شده ای ایجاد می کند، اشاره دارد. که معمولا به عنوان شناسه‌ی هر رکورد در دیتابیس استفاده می‌شود.
به علاوه، ما میتونیم فقط از شناسه های عددی در sequence استفاده کنیم که هر بار که تولید میشن مقدارشون افزایش پیدا کنه، روندی که برای تایپ UUID متفاوته و آنها ذاتا مرتب و sequential نیستند و معمولا با استفاده از الگوریتم هایی که تضمین می‌کند یک UUID در سراسر دیتابیس unique هست تولید می‌شوند. به همین دلیل دنباله ها برای تولید مقادیر عددی مرتب طراحی شده اند و آنها برای تولید UUID ها به دلیل ماهیت غیر مرتبشان مناسب نیستند.
sequential ID ها معمولا به UUID ها ترجیح داده میشن چرا که اونا به فضای کمتری نیاز دارند.
دیتابیس ها به طور طبیعی از sequential ID پشتیبانی می‌کنند:

  • PostgreSQL – SERIAL
  • SQL Server – IDENTITY
  • MySQL – AUTO_INCREMENT
  • SQLite – AUTOINCREMENT

بیاید یک نگاهی به مزایا و معایب استفاده از sequential ID به عنوان primary key، بیندازیم.

3.1. Readability:

بر خلاف مقادیر UUID، شناسه های عددی، خواندن و به خاطر سپردنشان راحت تر است.
با شناسه های عددی میتونیم به راحتی ترتیب رکورد ها در دیتابیس را بیابیم.
به علاوه، میتونیم سریعا رابطه بین رکورد ها رو بر اساس ID هاشون شناسایی کنیم.

3.2. Indexing:


ما اغلب از index های primary key ها و foreign key ها استفاده میکنیم تا query های select و join رو سریع تر کنیم.
بعضی از دیتابیس ها از ساختار B+Tree برای index گذاری کردن استفاده می‌کنند. در حالی که برخی دیگر مانند SQL و MySQL، از clustered indexe ها بهره می‌گیرند.
علاوه بر این، مقادیر UUID به خوبی index بندی نمی‌شوند. هر چه کلید ها طولانی تر باشند، این امر، رم بیشتری اشغال می‌کند.
به علاوه، UUID ها از آنجا که مقادیرشون رندوم هست، فاکتور های کمی برای index گذاری شدن را دارند. هر وقت که یک جدول را اصلاح می‌کنیم، index های آن باید آپدیت شوند. که این موضوع میتونه روی performance سیستممون مؤثر باشه و بیهوده از رم استفاده کنه.
افزون بر این موضوع، ما همچنین میتونیم foreign key ها را هم برای جداول join شده index گذاری کنیم. که اگر این کار را با UUID ها بکنیم یک ضربه دیگری به performanceمون میخوره.

3.3. Batch Actions

و Batch actions ها به پروسه اجرای چندین عملیات دیتابیس به عنوان single unit of work (به این معنا که تمام عملیات دیتابیس در batch به عنوان یک موجودیت منسجم رفتار می‌کند که به این معنی است که یا همه عملیات ها با موفقیت انجام می‌شوند یا هیچکدام از آنها انجام نمی‌شود) اشاره دارند.

یکی از دلایل اینکه چرا batch action ها با sequential ID ها بهتر کار میکنن اینه که اونا به شکل یک دنباله‌ی قابل پیش‌بینی تولید میشن و چندین عملیات دیتابیس میتونه در قالب یک batch اجرا بشه. که performance سیستم رو بهینه می‌کنه.
primary key ها به ترتیبی قابل پیش‌بینی تولید میشن و رکورد های جدید به انتهای دنباله آن اضافه می‌شوند. این موضوع این امکان را فراهم میکنه که بتونیم از انواع مشخص query ها استفاده کنیم. مثل range query ها که نیاز دارند رکورد ها با primery keyشون sort شده باشند.
به علاوه، sequential ID ها به مراتب کوتاه تر و کم حجم تر از UUID ها هستند که این موضوع باعث بهبود performance سیستم با کاهش مقدار حافظه‌ی مورد نیاز، می‌شود.
با این حال، اینم مهمه که احتمال زیاد تداخل در دنباله را هنگام استفاده از sequential ID ها درdistributed system ها در نظر بگیری. در این سناریو، استفاده از UUID ها به عنوان primary key میتونه گزینه بهتری باشه.

3.4. Predictable

شناسه های دنباله از یک ساختار مشخص که آنها را قابل پیش‌بینی می‌سازد پیروی می‌کنند.
این ممکنه به کاربران مخرب این اجازه رو بده که اطلاعاتی رو بخونن که نباید در اختیارشون باشه.
ما ممکنه در نهایت برخی private data ها و business logic رو به طور غیر عمدی در معرض نمایش قرار دهیم. برای مثال، اخرین ID میتونه نشان دهنده تعداد کل فاکتور های ساخته شده توسط یک کاربر باشه که میتونه اطلاعات درآمد رو آشکار کنه.

3.5. Concurrency

همانطور که پیش‌تر ذکر کردم، تولید sequntial ID توسط app میتونه پیچیده باشه. برای ساخت ID، ما باید از دیتابیس بخواهیم تا مقدار موجود بعدی رو پیدا کنه.
در distributed system ها، جایی که بیش از یک system دیتا رو به دیتابیس insert می‌کنه، این مسئله به این معناس که ما ممکنه در نهایت تو دیتاهامون تداخل داشته باشیم و این احتمال وجود داره که سیستم های مختلف یک key مشابه بسازن.
برای حل این مشکل، ما نیاز داریم سرویس های مختلفی برای تولید sequential value بسازیم. به علاوه، همان سرویس میتونه به تنها نقطه ضعف تبدیل بشه.

3.6. Size Limit:

در آخر، sequential ID ها محدودیت اندازه دارند، گر چه مقدار max آنها خیلی بزرگه، ولی بازم احتمال تموم شدنشون وجود داره.
اگر ما داریم از INT به عنوان primary key استفاده میکنیم، در نهایت، میتونیم به عدد 2.147.483.647 برسیم.
که میتونه دلیل ارور های بی سرو صدای زیادی باشه. بنابراین، میتونیم در نهایت مقادیر منفی به عنوان primary key داشته باشیم.

4. Difference Between UUID and Sequential ID:

برای جمع‌بندی، جدول زیر تفاوت بین UUID و Sequential ID را نشون میده:

تفاوت بین UUID و Sequential ID
تفاوت بین UUID و Sequential ID


5. Conclusion:

در این آموزش، ما تفاوت بین UUID و Sequential ID ها رو یاد گرفتیم.
در نتیجه، استفاده از UUID یا sequential ID به عنوان primary key بستگی به use case و هدف مشخص سیستمت داره.
اگر ما با distributed system ها کار می‌کنیم و به global uniquness نیاز داریم، UUID ممکنه بهترین انتخاب باشه. در سمت دیگر، اگر ما محدودیت حافظه داریم و performance اجرای query ها حیاتیه، بهترین گزینه میتونه sequential ID ها باشه.
در نهایت، انتخاب primary key باید با ارزیابی دقیق الزامات و محدودیت های سیستممان انجام شود.

با مقداری تاخیر، عیدتان مبارک.
حقیر را از دعای خیرتان بی‌نصیب نکنید.

اگر سوالی داشتید، در کامنت ها عرض کنید. بنده در خدمتم.

منبع