TL;DR

1. The problem: why is generating an ID hard?

On a single-server app you write id SERIAL PRIMARY KEY and forget about it: PostgreSQL hands out 1, 2, 3… Instagram is different. Posts, comments and likes are spread across thousands of database shards (see Sharding), and the write volume is enormous — assume, for practice, ~100M posts, ~1B likes and ~100M comments per day, peaking at well over 100,000 new objects per second.

We want IDs that are unique without asking anyone, small, and that sort by creation time — so "show the newest posts" is just "sort by ID descending".

🧠 Analogy Imagine a national chain of delis where every customer needs a globally unique order number. One central ticket dispenser for the whole country would mean phoning headquarters for every sandwich. Instead, each order number is written as time + shop number + counter: "14:50:01.123 — shop 1341 — order #7". No two shops share a shop number, and within one shop the counter never repeats within the same millisecond, so numbers are unique without any phone calls. And because the time comes first, sorting order numbers sorts orders by when they were placed.

2. Requirements for Instagram's IDs

RequirementWhy it matters for Instagram
Globally uniqueIDs are used as cache keys, Kafka message keys, URL slugs and foreign keys across services. A duplicate means one user's post overwriting another's.
64 bitsFits in a native BIGINT/long: 8 bytes per ID instead of 16 for a UUID — halving index size for billions of rows, and comparisons are a single CPU instruction.
Roughly time-sortable (k-sorted)Sort by ID ≈ sort by creation time without an extra created_at index; feed pagination uses "give me posts with ID < cursor"; new rows append at the right edge of B-tree indexes.
High throughput, low latencyHundreds of thousands of IDs/s overall, generated in microseconds, on the write path.
No single point of failureID generation must not be the component that takes Instagram down.
No per-ID coordinationNo network round trip or lock shared across machines for each new ID.
Bonus: routableIf the ID tells you which shard holds the object, you can fetch it without a directory lookup.

"Roughly" time-sortable is deliberate: IDs generated on different machines in the same few milliseconds may be slightly out of order (because their clocks differ slightly). That's fine for a feed; it is not a substitute for a strict global order.

3. The options, one by one

3.1 Database auto-increment

One database with an auto-increment column (SERIAL, AUTO_INCREMENT, IDENTITY) generates every ID.

3.2 Multi-master with step offsets

Run N ID databases; server i starts at i and increments by N. With N = 2: server A produces 1, 3, 5…, server B produces 2, 4, 6… MySQL supports this directly with auto_increment_increment = N and auto_increment_offset = i.

3.3 UUID v4 — and the index locality problem

A UUID is a 128-bit identifier, written as 36 characters like 9f1c2a7e-3b4d-4e8f-a1b2-c3d4e5f60718. Version 4 fills 122 bits with randomness. Any machine can generate one offline; the chance of a collision is negligible (you'd need ~2.7 × 1018 UUIDs for a 50% chance of a single duplicate).

So why not use them everywhere? Two reasons: size (16 bytes binary, 36 as text — every index and every foreign key that references it doubles or more) and, more importantly, randomness destroys index locality.

 B-tree primary index (leaf pages ordered by key)

 Time-ordered IDs:  [p1][p2][p3] ... [p97][p98][p99*] ← every insert lands in the LAST page
                                                       (it stays hot in RAM; pages fill 100%)

 Random UUID v4:    [p1][p2*][p3] ... [p41*][p42] ... [p97][p98*][p99]
                         ↑ inserts land on random pages all over the index
                         → each insert may read a cold page from disk
                         → full pages split in half (~50-70% fill), index bloats
                         → WAL / replication volume grows, cache hit ratio falls

With billions of rows, the index is far larger than RAM. Time-ordered keys keep the "insert frontier" (the right-most pages) in memory; random keys make every insert a potential random disk read and page split. Benchmarks routinely show insert throughput dropping several-fold once a random-UUID index outgrows memory. Random IDs also give no time ordering, so you need an extra created_at index for "newest first" queries. (See Indexing for how B-trees work.)

3.4 UUID v7 and ULID — time-ordered 128-bit IDs

Newer formats keep the "generate anywhere" property but put a timestamp first:

 UUID v7 (RFC 9562, 2024) — 128 bits
 | unix_ts_ms (48) | ver=7 (4) | rand_a (12) | var (2) | rand_b (62) |

 ULID — 128 bits, encoded as 26 Crockford base32 chars, e.g. 01JAZ3K5T8X2N9QW4VBCDEFGHJ
 | timestamp ms (48) | randomness (80) |

3.5 Ticket servers (Flickr)

Flickr publicly described a "ticket server": a dedicated MySQL database whose only job is handing out IDs from an auto-increment column, using a one-row table trick:

CREATE TABLE tickets64 (
  id   BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  stub CHAR(1) NOT NULL DEFAULT '',
  PRIMARY KEY (id), UNIQUE KEY (stub)
) ENGINE=InnoDB;

REPLACE INTO tickets64 (stub) VALUES ('a');   -- deletes + reinserts the single row, bumping id
SELECT LAST_INSERT_ID();                       -- per-connection, so concurrent clients don't clash

For availability they ran two ticket servers, one producing odd and one even numbers (step offsets), with clients round-robining between them.

3.6 Twitter Snowflake

In 2010 Twitter moved off MySQL auto-increment and published Snowflake, a service that composes a 64-bit ID from three fields, so each generator can work independently:

Timestamp — 41 bits Machine — 10 Sequence — 12 ms since custom epoch (2010-11-04) → ~69 years 5 DC + 5 worker 0–4095 per ms 0 bit 63 bit 0 sign bit always 0 → IDs stay positive in signed 64-bit integers (Java long, SQL BIGINT) id = (ms_since_epoch << 22) | (machine_id << 12) | sequence
Twitter Snowflake's 64-bit layout. The timestamp in the high bits makes IDs sort by time; machine ID + sequence make them unique within a millisecond.

Snowflake-style IDs are used widely: Discord, Mastodon and many others use variants; Sony's Sonyflake uses 39 bits of 10 ms units, 8 bits of sequence and 16 bits of machine ID (more machines, fewer IDs per machine, longer lifetime).

3.7 Comparison

ApproachSizeTime-sortableCoordination per IDSPOFThroughputNotes
DB auto-increment64-bitYes (strictly)Yes (DB round trip)YesOne DB's write rateLeaks counts; not shard-friendly
Step offsets (N masters)64-bitOnly per serverYesNoN × one DBHard to change N
UUID v4128-bitNoNoNoUnlimitedPoor B-tree locality, big indexes
UUID v7 / ULID128-bitYes (ms)NoNoUnlimitedGreat default if 128 bits OK
Ticket server (+segments)64-bitRoughlyYes (or per block)Mitigated with 2+High with blocksCentral dependency
Snowflake64-bitRoughly (ms)No (machine ID at startup)No4,096/ms per workerClock-dependent; needs machine IDs
Instagram scheme64-bitRoughly (ms)No (inside the shard's DB)No1,024/ms per shardEncodes shard → routing for free

4. Instagram's ID scheme

In 2012 Instagram's engineering team published a well-known post ("Sharding & IDs at Instagram") describing how they generate IDs for their sharded PostgreSQL. They considered the options above and rejected them: UUIDs were too big and unordered; a Snowflake service was another moving part to run (with ZooKeeper for machine IDs); ticket servers were a central dependency. Their solution: make each logical shard generate its own IDs inside PostgreSQL, embedding the shard number in the ID.

4.1 The bit layout

Time — 41 bits Shard ID — 13 bits Seq — 10 bits milliseconds since Instagram's custom epoch (2011) logical shard 0–8191 0–1023 per ms bit 63 bit 22 bit 9 bit 0 id = (now_ms − epoch) << 23 | shard_id << 10 | (nextval(seq) mod 1024) decode: time = id >> 23 shard = (id >> 10) & 8191 seq = id & 1023 41 + 13 + 10 = 64 bits — the whole BIGINT, no separate sign bit
Instagram's publicly described 64-bit ID: time first (sortable), then the logical shard that owns the row (routable), then a per-shard sequence (unique within a millisecond).

4.2 Generated inside PostgreSQL

The ID is produced by a PL/pgSQL function used as the column default, so every INSERT gets an ID atomically in the same database that stores the row — no extra service, no extra network hop. A function in the spirit of the published one (each logical shard is a schema, here shard_1341, with its own copy of the function and its own shard constant):

CREATE SEQUENCE shard_1341.post_id_seq;

CREATE OR REPLACE FUNCTION shard_1341.next_id(OUT result BIGINT) AS $$
DECLARE
    our_epoch  BIGINT := 1293840000000;   -- 2011-01-01T00:00:00Z in ms (custom epoch)
    shard_id   INT    := 1341;            -- this schema's logical shard number
    seq_id     BIGINT;
    now_millis BIGINT;
BEGIN
    SELECT nextval('shard_1341.post_id_seq') % 1024 INTO seq_id;
    SELECT FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000) INTO now_millis;
    result := (now_millis - our_epoch) << 23;   -- 41 bits of time in the high bits
    result := result | (shard_id << 10);          -- 13 bits of shard
    result := result | seq_id;                    -- 10 bits of sequence
END;
$$ LANGUAGE plpgsql;

CREATE TABLE shard_1341.posts (
    id         BIGINT NOT NULL DEFAULT shard_1341.next_id() PRIMARY KEY,
    user_id    BIGINT NOT NULL,
    caption    TEXT,
    media_key  TEXT NOT NULL
);

Notes: clock_timestamp() (not now()) is used because now() is frozen at the start of the transaction; PostgreSQL sequences are non-transactional and lock-free enough to handle very high rates; and the PRIMARY KEY constraint is a final safety net — in the unlikely event of a duplicate, the insert fails and can be retried instead of silently corrupting data. (Instagram's post used its own epoch constant and schema names; the structure is the same.)

4.3 Worked example: encoding an ID

User 31341 posts a photo on 2026-10-23 at 09:20:00.000 UTC (14:50 IST). Suppose users map to logical shards by user_id % 2000, so the shard is 31341 mod 2000 = 1341, and this shard's sequence returns 1001.

 Step 1 — time since epoch
   now   = 1,792,747,200,000 ms   (2026-10-23T09:20:00Z in Unix ms)
   epoch = 1,293,840,000,000 ms   (2011-01-01T00:00:00Z)
   t     =   498,907,200,000 ms   (needs 39 of the 41 bits)

 Step 2 — shift each field into position
   t     << 23  = 4,185,136,929,177,600,000
   1341  << 10  =                 1,373,184
   1001 mod 1024 =                     1,001

 Step 3 — OR them together (no overlapping bits, so OR = add)
   id = 4,185,136,929,177,600,000 + 1,373,184 + 1,001
      = 4,185,136,929,178,974,185          (hex 0x3A1497DD0014F7E9)

 Binary view (64 bits):
   00111010000101001001011111011101000000000 | 0010100111101 | 1111101001
   └──────────── time (41) ────────────────┘   └ shard 1341 ┘   └ seq 1001 ┘

4.4 Worked example: decoding an ID

id    = 4185136929178974185
t     = id >> 23            = 498907200000      → + epoch = 1792747200000 ms = 2026-10-23 09:20:00 UTC
shard = (id >> 10) & 0x1FFF = 1341              → lives on logical shard 1341
seq   = id & 0x3FF          = 1001

Any service — the feed, the like service, a debugging tool — can therefore answer "when was this created?" and "which shard holds it?" from the ID alone.

4.5 Why embedding the shard ID enables routing without lookups

Without it, a request like GET /media/4185136929178974185 would need a directory lookup ("which shard owns post X?") — another distributed table with billions of entries, another hop, another thing to cache and keep consistent. With the shard in the ID, routing is pure arithmetic plus a tiny, static map:

GET postid 4185…974185 App service(id >> 10) & 8191 = 1341 Shard map (cached)1024–2047 → pg-db-07 pg-db-06 pg-db-07schema shard_1341 pg-db-08 no network lookup: bit-shift + an 8,192-entry in-memory map
Routing a read: extract the logical shard from the ID, map it to a physical server with a tiny cached table, query that server directly.

5. Clocks: skew, NTP and time going backwards

Time-based IDs are only as good as the clocks that produce them. Server clocks are quartz oscillators that drift (tens of parts per million — up to a few seconds per day if uncorrected), so they're continuously disciplined by NTP (Network Time Protocol) or PTP, which typically keeps servers in a data center within about a millisecond of each other (better with modern setups like chrony + PTP).

5.1 Clock skew between machines: harmless-ish

If machine A's clock runs 3 ms ahead of machine B's, an ID created on B slightly after one on A may get a smaller value. Result: IDs are k-sorted ("roughly sorted, within the skew") rather than strictly ordered. For a feed this is invisible. It only matters if you wrongly rely on IDs for strict ordering — e.g. "process events in ID order to get causality". Use a log/sequence (e.g. Kafka offsets within a partition) for that. See Consistency Models.

5.2 The clock going backwards: dangerous

A clock can jump backwards when NTP steps a badly-off clock, after a VM migration or snapshot restore, from manual changes, or around leap seconds. If a generator issued IDs at t = 1000 ms and the clock jumps back to t = 990, it could re-issue the same (time, machine, sequence) triples → duplicate IDs. Defences:

  1. Remember the last timestamp used. If now < last_ms, never generate with the smaller time.
  2. Small regression (a few ms): wait until the clock passes last_ms again, then continue. Costs a few ms of latency, rarely.
  3. Large regression: refuse (raise an error, mark the node unhealthy, alert) so a load balancer routes ID generation elsewhere. Twitter's original Snowflake did exactly this.
  4. Alternatively, "borrow from the future": keep using last_ms (logically) and keep incrementing the sequence until real time catches up. Never goes backwards, but drifts from real time while borrowing.
  5. Prevent big jumps operationally: configure NTP/chrony to slew (gradually speed up/slow down) instead of step after boot; use leap-second smearing (spreading the extra second over many hours, as Google and AWS time services do); monitor clock offset.

Instagram's in-database scheme has a natural extra safety net: the 10-bit sequence comes from a monotonically increasing PostgreSQL sequence, so after a small backwards jump a duplicate would require the sequence to wrap to the same value within the same repeated millisecond, and the primary-key constraint would reject it anyway.

6. Throughput and lifetime math

Snowflake (per worker): 2^12 = 4,096 IDs/ms  → 4,096,000 IDs/s per worker
   × 1,024 workers (2^10)                     → ~4.2 billion IDs/s theoretical ceiling

Instagram (per logical shard): 2^10 = 1,024 IDs/ms → 1,024,000 IDs/s per shard
   × 8,192 shards (2^13)                      → ~8.4 billion IDs/s theoretical ceiling
   Realistic need (assumption): ~1.2B objects/day ≈ 14K/s avg, ~50K/s peak
   Even a single hot shard at 10K inserts/s uses only ~10 of 1,024 sequence values per ms.

Lifetime: 2^41 ms = 2,199,023,255,552 ms ≈ 69.7 years
   Twitter epoch 2010-11-04 → exhausted ~2080
   41-bit signed caveat (all 64 bits used): top bit flips after 2^40 ms ≈ 34.8 years

The design question is how to split the bits: more machine/shard bits → more generators; more sequence bits → more IDs per generator per ms; more time bits → longer lifetime or finer resolution. Instagram traded Snowflake's 12 sequence bits for 13 shard bits because their bottleneck is per-shard insert rate (a database can't do a million inserts per second anyway), while having many logical shards gives room to grow.

7. A Snowflake-style generator in Python

import threading
import time


class Snowflake:
    """64-bit IDs: 1 unused sign bit | 41 bits ms since EPOCH | 10 bits worker | 12 bits sequence."""
    EPOCH_MS = 1288834974657            # custom epoch (Twitter's: 2010-11-04T01:42:54.657Z)
    WORKER_BITS, SEQ_BITS = 10, 12
    MAX_WORKER = (1 << WORKER_BITS) - 1  # 1023
    MAX_SEQ = (1 << SEQ_BITS) - 1        # 4095
    MAX_TIME = (1 << 41) - 1
    MAX_BACKWARD_MS = 10                 # tolerate tiny clock regressions by waiting

    def __init__(self, worker_id: int):
        if not 0 <= worker_id <= self.MAX_WORKER:
            raise ValueError("worker_id must be in [0, 1023]")
        self.worker_id = worker_id       # must be unique per running generator!
        self.last_ms = -1
        self.seq = 0
        self._lock = threading.Lock()

    def _now(self) -> int:
        return time.time_ns() // 1_000_000 - self.EPOCH_MS

    def next_id(self) -> int:
        with self._lock:
            now = self._now()
            if now < self.last_ms:                       # clock moved backwards
                drift = self.last_ms - now
                if drift > self.MAX_BACKWARD_MS:
                    raise RuntimeError(f"clock moved back {drift} ms; refusing to generate")
                while now < self.last_ms:                # small drift: wait it out
                    time.sleep((self.last_ms - now) / 1000)
                    now = self._now()
            if now == self.last_ms:
                self.seq = (self.seq + 1) & self.MAX_SEQ
                if self.seq == 0:                         # 4,096 IDs used this ms
                    while now <= self.last_ms:            # spin until the next millisecond
                        now = self._now()
            else:
                self.seq = 0                              # new millisecond: restart sequence
            if now > self.MAX_TIME:
                raise RuntimeError("41-bit timestamp exhausted; pick a new epoch")
            self.last_ms = now
            return (now << (self.WORKER_BITS + self.SEQ_BITS)) | (self.worker_id << self.SEQ_BITS) | self.seq

    @classmethod
    def decode(cls, id_: int) -> dict:
        return {
            "unix_ms": (id_ >> (cls.WORKER_BITS + cls.SEQ_BITS)) + cls.EPOCH_MS,
            "worker_id": (id_ >> cls.SEQ_BITS) & cls.MAX_WORKER,
            "sequence": id_ & cls.MAX_SEQ,
        }


gen = Snowflake(worker_id=37)
ids = [gen.next_id() for _ in range(5)]
assert ids == sorted(ids) and len(set(ids)) == 5      # unique and increasing on one worker
print(ids[0], Snowflake.decode(ids[0]))

Implementation notes:

8. How Instagram uses ID generation

📱 AppPOST /posts
→
Upload / Post serviceuser 31341 → shard 1341
→INSERT
Shard 1341 (PostgreSQL)DEFAULT next_id()
→id returned
Kafka eventkey = post id
→
Feed cache / search / CDN keysall use the same id
  1. When user 31341 creates a post, the service computes the user's logical shard and inserts the row there; the database assigns the ID via next_id().
  2. That single ID then flows everywhere: object-storage keys for media renditions (with a hash prefix for load spreading — see Object Storage), the "post created" Kafka event (see Message Queues), the follower timelines in Redis (see News Feed), the search index and the URL.
  3. Feed pagination uses the ID as a cursor: "next page = posts with ID < last_seen_id", which is "older than" thanks to time-ordering (see API Design).
  4. Any service holding an ID can route reads to the owning shard without a lookup.
📸 In Instagram Instagram's 2012 engineering post describes exactly this: thousands of logical shards as PostgreSQL schemas on fewer physical servers, and IDs composed of 41 bits of time in milliseconds since a custom epoch, 13 bits of logical shard ID and 10 bits of an auto-increment sequence mod 1024, generated by a PL/pgSQL function in each schema. Data stored in Cassandra in our diagram (post metadata, comments) uses the same kind of 64-bit, time-ordered IDs, which also suit Cassandra clustering keys ("newest first" within a partition). Publicly, people have also observed that the short code in a post's URL (instagram.com/p/<code>/) is a base64-style encoding of the numeric media ID — a compact, URL-friendly representation of the same number.

9. Trade-offs & common pitfalls

✅ Snowflake / Instagram-style IDs

  • 64-bit: compact indexes and fast comparisons
  • Time-sortable: B-tree friendly, free "newest first", cursor pagination
  • No coordination per ID, no SPOF, millions per second
  • Self-describing: creation time (and shard) from the ID

⚠️ Costs

  • Depends on reasonably correct clocks; backwards jumps need handling
  • Worker/shard IDs must be assigned and kept unique
  • Only roughly ordered across machines
  • Leaks creation time and approximate volume
  • Fixed bit budget: lifetime and max shards are baked in

Common pitfalls

10. Real-world technologies

TechnologyFormatUse when
Twitter Snowflake (and many open-source ports)64-bit: 41 time / 10 machine / 12 seqYou need compact, sortable IDs at very high rates across many services
Sonyflake64-bit: 39 time (10 ms units) / 8 seq / 16 machineMany machines, lower per-machine rate, longer lifetime (~174 years)
Instagram-style PL/pgSQL function64-bit: 41 time / 13 shard / 10 seqSharded PostgreSQL where IDs should encode the shard
UUID v7 (RFC 9562), ULID, KSUID128-bit (KSUID 160-bit) time-orderedDecentralised generation, no worker IDs, 128 bits acceptable
UUID v4128-bit randomUnguessable tokens, idempotency keys, low-volume tables — not hot primary keys
MongoDB ObjectId96-bit: 4-byte seconds + 5-byte random + 3-byte counterDefault document IDs in MongoDB
Flickr ticket servers, Meituan Leaf (segment mode), Baidu UidGenerator64-bit from DB sequences / blocksCentral allocation with batching is acceptable; want dense IDs
DB sequences / IDENTITY64-bit denseSingle database or per-shard local IDs that never leave the shard

11. Interview corner

Design a unique ID generator for Instagram posts.

Requirements: unique, 64-bit, roughly time-ordered, >100K/s, no SPOF, no per-ID coordination. Use a Snowflake-style layout: 41 bits of milliseconds since a custom epoch (~69 years), N bits identifying the generator, and the remaining bits as a per-millisecond sequence. Instagram's variant puts 13 bits of logical shard ID and a 10-bit sequence and generates IDs inside each PostgreSQL shard via a PL/pgSQL default, so the ID also tells you where the row lives. Handle clock regressions by tracking the last timestamp and waiting or refusing; assign worker/shard IDs uniquely at startup; return IDs as strings to JS clients.

Why not just use UUIDs?

UUID v4 is 128 bits (2× storage in every index and foreign key) and random, so inserts scatter across the B-tree, causing page splits, poor cache locality and much slower inserts once the index exceeds RAM; you also lose free time ordering and need an extra created_at index. UUID v7/ULID fix ordering but are still 128 bits and carry no routing information. They're good choices when you don't control the scale or can't assign worker IDs.

What happens if the clock goes backwards on a Snowflake node?

If the generator naively used the new, smaller time, it could reissue (time, worker, sequence) combinations already used → duplicates. The generator remembers the last timestamp; on a small regression it waits until real time passes it, on a large one it refuses to generate and alerts (the node is removed from rotation). Operationally, slew rather than step NTP corrections and use leap-second smearing. Some implementations keep issuing IDs with the last timestamp and an incremented sequence ("borrowing" future time).

How does putting the shard ID in the post ID help?

Routing becomes arithmetic: (id >> 10) & 8191 gives the logical shard, and a tiny cached map gives the physical server — no directory service, no extra hop, no extra failure point, for every read of every object. It also enables co-locating related data on the same shard, and because it's a logical shard, moving it to new hardware changes one map entry instead of any IDs.

How many IDs per second can Instagram's scheme produce, and is that enough?

1,024 per millisecond per logical shard = ~1M/s per shard, and with up to 8,192 shards ~8.4B/s theoretical. Real demand is maybe tens of thousands of inserts per second overall, and a single PostgreSQL shard can't do anywhere near 1M inserts/s, so the sequence bits are never the bottleneck. If a shard did exceed 1,024 in one ms, the sequence would wrap and could collide — guarded by the primary key constraint (retry).

Are Snowflake IDs strictly ordered? Can I use them to order events?

Only roughly (k-sorted). Within a single generator they increase strictly; across generators ordering is only as good as clock synchronisation (typically ~1 ms). They're fine for "newest first" feeds and pagination but not for causal ordering of events — use a single partition's log offsets, version numbers, or logical/hybrid clocks for that.

How would you assign worker IDs in a Kubernetes deployment that autoscales?

Options: a StatefulSet ordinal (stable but limits scaling patterns); lease a worker ID from etcd/ZooKeeper with an ephemeral, TTL'd key so a dead pod's ID is freed and two live pods can never hold the same one; or allocate from a DB table with a lease timestamp. On startup the generator must acquire a lease before serving, and stop generating if it loses the lease.

12. Check your understanding

Decode the Instagram-style ID 4185136929178974185 (epoch 2011-01-01T00:00:00Z = 1293840000000 ms).

id >> 23 = 498,907,200,000 ms after the epoch → 1,792,747,200,000 Unix ms = 2026-10-23 09:20:00 UTC; (id >> 10) & 8191 = shard 1341; id & 1023 = sequence 1001.

How long does a 41-bit millisecond timestamp last, and why use a custom epoch?

241 ms ≈ 69.7 years. Counting from 1970 would have used up ~40 of those years before the company even existed; a custom epoch near launch maximises remaining lifetime.

Why do random UUID primary keys slow down inserts once the table is large?

Each insert goes to a random leaf page of the B-tree. When the index is bigger than RAM, those pages are often not cached → random disk reads, plus frequent page splits that leave pages half-empty, bloating the index and write-ahead log. Time-ordered keys always insert at the right edge, which stays hot in memory.

A Snowflake worker receives 10,000 ID requests within one millisecond. What happens?

The 12-bit sequence allows 4,096 IDs per ms. After 4,096, the sequence wraps to 0 and the generator waits for the next millisecond. The 10,000 requests take ~3 ms. No duplicates — just a tiny delay.

Two pods accidentally share worker ID 37. What can go wrong and how would you detect it?

If both generate an ID in the same millisecond with the same sequence number, they produce identical IDs — e.g. two posts with one ID, one overwriting the other in caches. Detect via unique-constraint violations, and prevent by leasing worker IDs from etcd/ZooKeeper.

Your web client shows post 4185136929178974185 as 4185136929178974000 and opens the wrong post. Why?

The ID was sent as a JSON number; JavaScript stores numbers as 64-bit doubles that are exact only up to 253 ≈ 9×1015, so the 19-digit ID is rounded. Send IDs as strings.

Instagram moved a logical shard to a new physical server. Do any post IDs change?

No. IDs contain the logical shard number. Only the logical → physical map entry changes, which every app server picks up from configuration.

🎯 Key takeaways

Related lessons