SDSystem design
Start learning free

LESSON 21 OF 140 · BUILDING BLOCKS

Unique IDs

UUID, Snowflake Read the written walkthrough

SYSTEM DESIGN · INTERVIEW WALKTHROUGH

Unique ID Generation Explained: Auto-Increment, UUID and Snowflake

The problem: ids once the data is split

Every row needs an id no other row has: it goes into links, receipts and every table that points to the row. With one database it's easy: the database sees new rows one at a time, so its auto-increment counter hands out 1, 2, 3… and never repeats. The ids are short, cost nothing, and sort in the order rows arrived.

That breaks once the data is sharded. Say orders are split by customer over 3 shards, each its own database with its own counter, and Shards 0 and 1 have used ids 1 to 6. Ana's order goes to Shard 0 and gets id 7. A minute later Ben's order goes to Shard 1, which knows only its own counter, so it gets id 7 too. Nothing fails, but "order 7" now names two orders, and a payment, a link like /orders/7 or a support ticket can't tell them apart.

Two quick fixes and their costs

A central counter hands out every id, whatever the shard. Every insert now waits for one more call (a round trip inside a data center is about 0.5 ms), and if the counter is down, no shard can take a new row: a single point of failure. Handing out blocks of 1,000 ids cuts the calls to one per block, as the URL shortener lesson does for its codes, but everything still depends on one part.

Or each shard counts with a step: it adds 3 instead of 1 and starts at its own number.

shard     its ids
Shard 0   1, 4, 7, 10, …
Shard 1   2, 5, 8, 11, …
Shard 2   3, 6, 9, 12, …

The ids never meet and inserts cost nothing extra. Flickr described a mix of both fixes in 2010: two counter servers, one handing out odd ids and the other even ones. The catch: every number already belongs to a shard, so a fourth shard has nowhere to start, and all shards must switch to a step of 4 at the same moment.

UUIDs: random ids nobody coordinates

A UUID is 128 bits. Version 4, the common one, fills 122 of them with random bits (the other 6 mark the version and variant), written as 36 characters:

3f9c2a71-5e0b-4d8f-9a26-c1e47b830d5f

Any server, or a phone with no connection, makes one alone. They don't clash: there are 2¹²² possible values, a number 37 digits long. Even counting every pair (the birthday effect), a billion UUIDs have less than one chance in a billion billion that any two match; you'd need about a billion a second for about 86 years before one clash is as likely as not. That assumes a proper random source.

  • Size: 36 characters as text, 16 bytes even as binary, twice a 64-bit number, in every row and index that holds it.
  • No order: sorting by a random id says nothing about time, so "newest first" needs a separate time column and index.
  • Scattered inserts: an index is a sorted tree of pages. Counted ids always go into the last page, which stays in memory; random ids land on a random page each time, so inserts read, write and split pages all over it.

UUID version 7, standardized in 2024, puts a millisecond timestamp in its first 48 bits, so it sorts by time with no coordination, but it's still 128 bits.

Snowflake ids: time, machine, counter

Twitter published a design in 2010, Snowflake, for ids made alone that still grow with time: one 64-bit number in four parts, highest bits first.

part      bits   holds
sign      1      always 0, so the id is positive
time      41     milliseconds since a start date
machine   10     which server made it
counter   12     ids this server made in this millisecond

41 bits of milliseconds last about 69 years from the start date, 10 bits name 1,024 machines, and 12 bits count 4,096 ids per millisecond per machine, about 4 million a second each. Because the time sits in the highest bits, a later millisecond always means a bigger id, so sorting by id sorts by time.

A worked example

Server A has machine id 1, server B machine id 2; small times keep the numbers readable (ms 105 is 105 milliseconds after the start date):

id        time   machine   counter   the number
A's 1st   105    1         0         440,406,016
A's 2nd   105    1         1         440,406,017
B's 1st   105    2         0         440,410,112
A's 3rd   106    1         0         444,600,320

A second id in the same millisecond bumps the counter; B's ids can't match A's, because the machine part differs; a new millisecond starts the counter at 0, and every id from ms 106 beats every id from ms 105. In code:

class IdMaker:
    def __init__(self, machine, clock_ms):
        self.machine, self.clock_ms = machine, clock_ms
        self.last_ms, self.counter = -1, 0

    def next_id(self):
        now = self.clock_ms()                 # ms since the start date
        if now < self.last_ms:
            raise RuntimeError('clock moved back')
        if now == self.last_ms:
            self.counter = (self.counter + 1) % 4096
            if self.counter == 0:             # all 4,096 used: wait
                while now <= self.last_ms:
                    now = self.clock_ms()
        else:
            self.counter = 0
        self.last_ms = now
        return (now << 22) | (self.machine << 12) | self.counter

When the counter runs out, or the clock runs back

If a burst uses all 4,096 counter values in one millisecond, the generator waits for the next one. Counting on to 4,096 would need a 13th bit, which belongs to the machine part, so the id would repeat one already made. Likewise, a clock that jumps back (servers correct their clocks over the network) could repeat a time and counter, so the generator refuses to make ids, or waits, until the clock passes the last time it used.

Each server needs its own machine id, handed out once when it starts (from its configuration or a coordination service), not on every insert. And the order is only rough: clocks never agree exactly, and within one millisecond ids are ordered by machine.

Which id to choose

kind              made by        sorts by time   guessable   size
auto-increment    one database   yes             yes         4 or 8 bytes
UUID version 4    anyone         no              no          16 bytes
Snowflake-style   each server    roughly         partly      8 bytes

One database and nothing to hide, like a small internal tool: auto-increment. Unguessable, or made where no machine id can be handed out, like a shared document's link or ids a phone makes offline: a UUID. Counted ids also leak your size: order number 1,042 says you've had about a thousand orders. Many servers and newest first, like tweets, chat messages or orders on several shards: Snowflake-style ids.

Unique IDs in system design interviews

Ids come up whenever data is sharded: short links, tweets, messages, orders. Say how ids stay unique without a call on every insert, then ask what they must do: sort by time (feeds, chat) or be unguessable (share links, invite codes). Name the limits: a Snowflake-style generator needs a machine id per server and a clock that doesn't run back; a UUID is long and scatters the index.

Common interview questions

How do you generate unique ids in a distributed system?

Without a call on each insert: random UUIDs, or Snowflake-style ids from a timestamp, a machine id and a per-millisecond counter. A central counter handing out blocks of ids also works, at the price of one service everything depends on.

UUID or Snowflake id?

A Snowflake-style id is 8 bytes, sorts by time and keeps index inserts at the end: feeds, messages, orders. A random UUID needs no machine ids and can't be guessed: public links and ids made on clients.

What happens if a server's clock goes backwards?

It could reuse a time and counter, and so repeat an id. The generator remembers the last time it used and refuses, or waits, until the clock passes it.

Summary

  • Auto-increment: simple and sorted, but each shard's own counter hands out the same ids.
  • Quick fixes: a central counter costs a call and can fail; a step breaks when you add a shard.
  • UUID: 122 random bits, made anywhere, practically never clashes; long, unsorted, scatters the index.
  • Snowflake: time, machine, counter in 64 bits; sorted by time; waits when the counter runs out or the clock runs back.
  • Choosing: ask whether the ids must sort by time or be unguessable.
Keep going with system designInteractive lessons from the building blocks to Twitter, Uber and payments, the way interviews ask. Free to start.
Start learning free