Why UUID4? and What are the Tradeoffs?
Backstory
In my last publication, I discussed building a Student Database Management System designed to sync records across multiple institutions operating independently. While implementing this system, I ran into a critical issue that made me pause:
- Exposing sequential IDs through REST endpoints/URLs which is not ideal (although there are other ways to handle this without adopting uuids),
- Then I foresaw a clash between the Auto-incrementing IDs if I were to sync the data from different standalone apps using the same auto-incrementing IDs.
As for the records I have already had some students registered with auto-incrementing integer as the primary keys, and then I switched the primary key to a UUIDField natively. (More details)
As there were inevitable ID incompatibility, this experience highlighted a limitation of the method used. I could have blended the use of both IDs and UUIDs using sequential integers as local base and unique identifiers across distributed systems(global base). But then I was curious as to why I just could not natively proceed changing the primary key to uuid.
So I began searching for a better alternative to making the uuid the primary key. (more details).
After publishing my migration process, many asked: What are UUIDs?, Why UUIDv4?, and Are there other options?
This article explains:
- What IDs are and why they matter,
- Why there are multiple types of IDs,
- How various ID types compare,
- Why I chose UUIDv4 for my project (with engineering tradeoffs),
- The compromises involved,
- How I optimized performance,
- And what you might want to consider in your Django or Django REST Framework project.
What Are IDs?
IDs (identifiers) are values used to distinguish individual records in a database. They play a central role in relational models, data syncing, and external referencing (such as through URLs or QR codes as implemented in my Student Database Management System (SDMS)).
Traditionally, most developers default to auto-incrementing integers (1, 2, 3...) as primary keys. While simple and readable, these IDs assume a single source of truth; a major limitation when working across systems.
Get Lawal Tajudeen’s stories in your inbox
Join Medium for free to get updates from this writer.
This limitation sparked the need for globally unique identifiers such as UUID or GUID.
What Are UUIDs?
A UUID (Universally Unique Identifier) is a 128-bit number designed to be unique across time and space. The most common format looks like this:
f47ac10b-58cc-4372-a567-0e02b2c3d479UUIDs solve the distributed uniqueness problem: they can be generated on different machines, at different times, with zero coordination; and still remain globally unique.
UUID Versions (v1 to v8)
Each UUID version generates identifiers differently:
- UUIDv1: Is Based on timestamp + MAC address. from the research I have done, The timestamp of its creation is a core part of the UUID value itself. It is Good for sorting, but it can leak information (like your machine’s identity and creation time).
- UUIDv2: Includes POSIX user IDs which Rarely used, and are largely obsolete.
- UUIDv3: These are Deterministic, generated from a name and namespace using MD5. Same input provides same UUID.
- UUIDv4: (which was my choice) is Entirely random. Strong privacy, high entropy. Low risk of collisions. Not sorted(but can be sorted in other ways depending on the sorting parameter such as
Created_at). (The percentage rate of having two identical UUIDv4s is effectively zero for practical purposes. You would need to generate an astronomical number of UUIDs (billions per second for decades) to even have a 50% chance of a single collision.) - UUIDv5: Like v3, but uses SHA-1 instead of MD5 for the Deterministic hashing of a namespace + name string.
- UUIDv6: A restructured v1 for better database performance. Sortable by time. Still a draft.
- UUIDv7: This combines Unix timestamp with random bits. Useful for time-ordered, globally unique events. Still experimental.
- UUIDv8: Custom/experimental — reserved for application-defined logic. (Could this be the key to automatically assigning a matric number or a registration number to a student or employee; the reg. number carying a part of the name of the institution, a part of the faculty/department and the serial number of the total individuals in the department?)
GUID? Same Thing, Different Name
A GUID (Globally Unique Identifier) is essentially the same as a UUID. It is a Microsoft’s term and often appears in uppercase (6F9619FF-8B86-D011-B42D-00C04FC964FF). Format-wise and functionally, GUIDs and UUIDs are interchangeable.
Why Not Auto-Increment IDs?
Auto-incrementing integers are great in centralized systems. But in distributed systems; like mine; they collapse. Why?
- Security: Predictable IDs can be exploited (e.g., scraping, ID enumeration in APIs).
- Potential Collision: Two schools inserting different students with the same ID (
id=7) will leads to conflict when syncing. - Scalability: Difficult to merge or shard.
- Global Uniqueness: No native way to guarantee it across multiple sources.
Why I Chose UUIDv4 (and the Tradeoffs)
After evaluating options, I chose UUIDv4. Here is why; and the software engineering tradeoffs I accepted along the way:
Advantages (Why It Worked)
- Truly Random
UUIDv4 uses 122 bits of randomness, making collisions astronomically unlikely; even without coordination between sources. - Privacy-Preserving
Unlike v1, v4 does not leak MAC addresses or timestamps. IDs are opaque, non-predictable, and safe to expose in URLs and QR codes. - Simple to Use in Django
Django and DRF have great support for UUIDs:
import uuid
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)4. Global Sync, Zero Conflict
Every school could create records independently — and syncing them to the central DB will be seamless. without key collisions.
Tradeoffs (What I Sacrificed)
In software engineering, every choice has a cost. Here is what I gave up:
- Performance: Indexing & Insert Speed
UUIDv4 is not ordered. Random insertion into a B-tree index (used by most RDBMSs) leads to index fragmentation. Inserts slow down slightly compared to sequential integers. - Storage: Size Matters
UUIDs are 128-bit (16 bytes), whereas integers are just 4 bytes. Multiply this across millions of records, and the space cost adds up — both in memory and disk. - Readability
You can easily glance at ID=5 and know it’s the fifth entry. UUIDs? Not so much. They’re ugly to debug and verbose in logs. - Joining Tables
Foreign keys on UUIDs are slower than on integers, especially when deeply nested joins are involved.
How to Managed These Tradeoffs
To strike a balance between scale and speed, the following could be adopted:
- Use PostgreSQL: It supports native UUIDs (as 16-byte values), reducing overhead.
- Making use of Dual-ID Strategy: this involves keeping an internal
idfield (auto-incremented) for performance and used auuidfor public-facing logic:
class Student(models.Model):
id = models.AutoField(primary_key=True)
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)- Indexing the UUID fields properly, but only when needed.
- Avoiding UUIDs in joins where possible; to keep relational operations internal.
- Paginating data using
created_atinstead ofuuid, to avoid sorting issues.
This hybrid approach will give the safety of UUIDv4 for external systems and the efficiency of integers inside the app.
Software Engineering Lesson
The decision to use UUIDv4 was not because it was perfect, but because it was pragmatic for my distributed architecture.
In software engineering, there are no silver bullets. everything is Just tradeoffs. (You give something up to gain something)
I chose global uniqueness and privacy over readability and slightly faster inserts. Your project might demand the opposite. so I will advice you always evaluate your priorities.
What Should You Use?
Let us simplify it:
Use Auto-Increment if you:
- Have a monolithic system.
- Don’t expose IDs in URLs.
- Want top insert performance.
Use UUIDv4 if you:
- Have distributed writes.
- Sync across systems.
- Want secure, unguessable IDs.
Consider using the UUIDv6/v7 (once stable) if:
- You want the best of both worlds time-sortable and globally unique.
Use UUIDv5 if:
- You need deterministic IDs based on input names.
My Words
Choosing an ID strategy is a small decision that carries big consequences. UUIDv4 gave me the flexibility and safety I needed to build across distributed sources without fear of collisions.
But it wasn’t free; and I had to engineer around its drawbacks.
Design decisions in engineering are not about what is perfect. They are about what fits your architecture, use case, and constraints.
So next time you are designing a system; whether centralized or distributed; take a moment to ask:
- What do I gain, and what do I lose? (what are the tradeoffs)
That is how good engineering happens.









