System designCore11 min

Unique ID generation

One database hands out 1, 2, 3 for free. Spread the data over many servers and that stops working. Here is how systems make IDs that never collide, sort by time, and don't need a phone call to a central counter for every new row.

Watch it evolve1 / 8
The problem

A photo app on eight shards

A photo-sharing app stores its photos on eight database shards. Each shard is a normal database with its own auto-increment counter.

80
Start
Stage 1
Stage 2
Stage 3
Stage 4
Final

1 · The problem

Auto-increment stops working the day you shard

A photo-sharing app stores its photos on eight database shards (Sharding & partitioning). Each shard is a normal database with an auto-increment counter, so each one happily numbers its photos 1, 2, 3. A thousand uploads later, photo 1,001 exists on every shard:

Photo serviceShard 1next ID: 1,001Shard 2next ID: 1,001/photos/1001which one?new photo → 1,001new photo → 1,001Photo serviceShard 1next ID: 1,001Shard 2next ID: 1,001/photos/1001which one?new photo → 1,001new photo → 1,001

Two photos, one ID. Links break, and moving a photo to another shard during rebalancing collides with a photo already there.

The obvious fix, one central counter for everyone, works until it doesn't. Every upload in the world now waits for one server, and when that server is down nobody can upload anything. The ID service has become the most shared part of the system (Scalability explains why that limits everything).

So the goal is IDs that each server can make on its own. What else should a good ID do?

Property

Why it matters

Unique everywhere

Across shards, servers and data centers, forever

Made without asking anyone

No network call per ID, so no bottleneck and no single point of failure

Roughly sorted by time

"Newest photos first" is just "highest IDs first", and new rows land at the end of the index

Compact

A 64-bit number fits a normal integer column; every index and foreign key carries it

Hard to guess, when public

Sequential IDs in URLs let anyone count your uploads or walk through them

No scheme scores perfectly on all five. The rest of this page is three families of answers, and what each one gives up.

2 · Counters

Counters still work if you share them out

The first family keeps counters but stops them colliding. There are two classic ways.

Interleaved counters. Two ID servers both count, one in odd numbers (1, 3, 5) and one in even (2, 4, 6). Databases support this directly: MySQL's auto_increment_increment = 2 with offsets 1 and 2. Clients alternate between them, and if one is down they use the other. Flickr generated its IDs this way, on two small "ticket servers". Adding a third server later means re-planning the steps.

Block allocation. A central counter hands out ranges instead of single IDs. Each server takes a block of, say, 100,000 numbers, issues them from memory, and only goes back for a new block when it runs low:

Range storenext free: 300,001Upload server Aissuing 1–100,000Upload server Bissuing 100,001–200,000Upload server Cissuing 200,001–300,000block 1block 2block 3Range storenext free: 300,001Upload server Aissuing 1–100,000Upload server Bissuing 100,001–200,000Upload server Cissuing 200,001–300,000block 1block 2block 3

One trip to the range store per 100,000 IDs instead of per ID. If server B crashes halfway through its block, the unused numbers are simply never issued: a gap, never a duplicate.

Both keep IDs small and mostly increasing, and both still lean on a central store, just far less often. Two costs remain:

  • Order is only per server. Server B's IDs are all larger than server A's, even for photos uploaded earlier, so "highest ID" no longer means "newest".
  • Counters leak. Anyone who sees two of your IDs a day apart knows how many photos were uploaded that day, and can try the numbers in between.

3 · Random

Random IDs need no coordination, if they are long enough

The second family drops counting entirely: every server picks a random number and trusts that nobody else will pick the same one. The standard form is the UUID version 4: 128 bits, of which 122 are random, usually written as 36 characters like 9f1c…-….

How safe is "random"? The birthday problem answers it: in a space of S possible values, a collision becomes likely after only about √S picks, not S. (In a room of 23 people, two probably share a birthday.) So the number of bits matters far more than intuition suggests:

Random ID

Possible values

50% chance of a collision after

32 random bits

4.3 billion

≈ 77,000 IDs

6 characters, a–z, A–Z, 0–9

62⁶ ≈ 56.8 billion

≈ 281,000 IDs

UUID version 4 (122 bits)

5.3 × 10³⁶

≈ 2.7 × 10¹⁸ IDs: a billion a second for 86 years

A 6-character random code, the kind short links use, collides after a few hundred thousand links, so a system using one must check every new code and retry on a clash. A UUID can skip the check for all practical purposes, provided the random numbers come from a good generator: a badly seeded one breaks the arithmetic.

UUIDs pay for that safety in two ways:

  • Size. 16 bytes instead of 8, in every index and every table that refers to the row.
  • Scattered inserts. Consecutive random IDs land at random places in a database index, so each insert touches a different page and the index's working set no longer fits in memory. The UUID standard itself names this "poor database-index locality".

4 · Time first

Put the time first, and IDs sort themselves

The third family builds the ID from parts, with the time at the front. The best-known layout is Twitter's Snowflake. It packs three fields into one 64-bit integer:

Field

Bits

What it gives

Sign

1

Always 0, so the ID is a positive 64-bit number

Timestamp

41

Milliseconds since a chosen start date: 2⁴¹ ms ≈ 69.7 years

Machine ID

10

Up to 1,024 generators that never collide with each other

Sequence

12

4,096 IDs per machine per millisecond: about 4 million a second

Each machine makes IDs alone: same machine, same millisecond, next sequence number. Because the time comes first, sorting by ID sorts by creation time, and new rows land at the end of the index. The order is only rough across machines, since their clocks differ by a few milliseconds; Twitter called this k-sorted. And anyone can read the creation time straight out of an ID.

Variants move the bits around for their own needs:

  • Instagram replaced the machine ID with a 13-bit logical shard ID and a 10-bit sequence, generated inside each shard's database. An ID then tells you which shard holds the row, the trick Sharding & partitioning recommends.
  • UUID version 7, standardized in 2024, puts a 48-bit millisecond timestamp in front of 74 random bits. It needs no machine IDs at all, and sorts by time.
  • ULID does the same in 128 bits, written as 26 sortable characters.

Version 7 replaces the older time-based versions. If you want UUIDs, want them sortable, and don't want to manage machine numbers, it is the default choice today.

5 · The catch

Time-based IDs trust the clock and the machine number

A Snowflake-style generator is only as unique as its inputs. Three things can go wrong:

The clock jumps back. Time synchronization (NTP) can step a server's clock backwards. If the generator keeps going, it can reissue timestamps it has already used, and with the same sequence numbers that means duplicate IDs. A careful generator remembers the last timestamp it used and refuses to issue IDs until the clock passes it:

Refusing for 20 ms is a small cost; issuing IDs from a time already used risks duplicates that surface weeks later as two photos with one ID.

Two machines share a number. Ten bits of machine ID only help if every generator has a different one. Hard-coding them in config breaks when a server is cloned or a container restarts somewhere else. Assign them from a coordination service, or a database row each machine claims, with a lease that expires when the machine dies.

The sequence runs out. At 4,096 IDs in one millisecond on one machine, the next request has to wait for the next millisecond. That is rarely a problem, but it means a single generator can't be pushed past about 4 million IDs a second.

And all three families leak something. Counters reveal how many rows you have; time-based IDs reveal when a row was created. Neither is a security problem by itself, as long as nobody treats an ID as a password: UUIDs must not be used as capabilities whose mere possession grants access. Check permissions on every request, whatever the ID looks like.

6 · Choosing

Pick the ID by what it has to do

If you need

Use

Because

One database, no sharding planned

The database's own auto-increment

Simplest, smallest, ordered

Small, sortable IDs across many servers

Snowflake-style 64-bit IDs

No coordination per ID; fits an integer column

Sortable IDs with no machine numbers to manage

UUID version 7 (or ULID)

Time-ordered, standard, random within each millisecond

IDs made anywhere, even offline on a phone

UUID version 4 or 7

Collisions are negligible at 122 or 74 random bits

Short public codes, like short links

A counter or pre-made blocks, written in base 62

Short and guaranteed unique, without collision checks

Short codes deserve a word. Writing a number in base 62 (a–z, A–Z, 0–9) packs it tightly: six characters cover 62⁶ ≈ 56.8 billion values, seven cover about 3.5 trillion. Hashing the long URL and keeping the first few characters looks tempting, but it is the random case again: collisions after a few hundred thousand links. Counting and encoding never collides.

Whatever you pick, decide it early. An ID ends up in URLs, in other tables, in logs and in other companies' systems, and changing its format later is a migration of everything that ever stored one.

7 · In a real system

Where the URL shortener gets its keys

URL Shortener System Design

Creating a short link, with keys made in advance

The URL shortener's Key Generation Service hands each worker a batch of 10,000 keys, so creating a link takes a key from memory: no collision check and no database round trip. Step through the write path to see it.

Check yourself

5 questions

1. Photos are spread over 8 shards, and each shard uses its own auto-increment counter. What goes wrong?
2. Short links use random 6-character codes from a–z, A–Z and 0–9 (about 56.8 billion values). Roughly when does a collision become more likely than not?
3. A Snowflake generator gets 5,000 requests in the same millisecond. What happens?
4. Time sync steps a server's clock back 20 ms. What should a Snowflake-style generator do?
5. Why is UUID version 7 usually a better primary key than version 4 in a database with a B-tree index?

Takeaways

Remember this

  • Per-shard auto-increment collides across shards, and one central counter is a bottleneck and a single point of failure.
  • Counters can be shared out by interleaving steps or handing out blocks; crashes leave gaps, never duplicates.
  • Random IDs collide after about the square root of the space: fine for 122-bit UUIDs, not for 6-character codes.
  • Snowflake-style IDs put the time first, then a machine ID and a sequence, so 64-bit IDs sort by time with no coordination per ID.
  • Time-based IDs need a clock that never runs backwards and machine IDs that are truly unique; no ID should ever act as a password.