SYSTEM DESIGN:Lesson 14: Snowflake IDs at Scale
Mastering lesson 14: snowflake ids at scale concepts and implementation.
4096 new IDs per millisecond — per machine
"Our order service was doing 8,000 creates per second during Big Billion Days. Postgres nextval() became the bottleneck — every insert waited on the same sequence lock. We moved to Snowflake-style IDs and the queue vanished."When a single database sequence becomes the choke point, you need IDs that any machine can generate independently without calling a central coordinator — while staying unique, sortable, and compact.
Twitter's Snowflake is the canonical answer. This chapter covers the bit layout, clock skew, datacenter bits, throughput math, and alternatives (Sonyflake, ULID). We tie it back to the URL shortener — because every short code you generate is ultimately a Snowflake or counter in disguise.
Prerequisites: Counting Without Collisions for UUID vs auto-increment basics.

Snowflake ID meets two clocks that disagree
4096 IDs per millisecond per machine — unless NTP has other plans.
Snowflake bit layout — 64 bits, zero coordination
A Snowflake ID is a 64-bit integer. Each bit region encodes metadata so two machines never produce the same ID — without talking to each other.
Twitter Snowflake 64-bit ID structure
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
├─┬───────────────────────┬───────────┬───────────┬───────────┤
│0│ timestamp (41 bits) │ datacenter│ machine │ sequence │
│ │ ms since epoch │ (5 bits) │ (5 bits) │ (12 bits) │
└─┴───────────────────────┴───────────┴───────────┴───────────┘
| Field | Bits | Range | Purpose |
|---|---|---|---|
| Sign | 1 | Always 0 | Keeps ID positive in languages with signed ints |
| Timestamp | 41 | ~69 years from custom epoch | Sortable by creation time |
| Datacenter ID | 5 | 0–31 (32 DCs) | Prevents cross-DC collision |
| Machine ID | 5 | 0–31 (32 machines per DC) | Prevents cross-machine collision |
| Sequence | 12 | 0–4095 per ms | Handles burst within same millisecond |
# Simplified Snowflake generation (conceptual)
EPOCH = 1288834974657 # Twitter custom epoch (Nov 2010)
def generate_id(dc_id: int, machine_id: int, seq: int, now_ms: int) -> int:
timestamp = now_ms - EPOCH
return (timestamp << 22) | (dc_id << 17) | (machine_id << 12) | seq
# dc_id=1, machine_id=5, seq=42, now → single 64-bit integer
Twitter's original epoch gives 69 years of timestamps. Sonyflake uses a more recent epoch with 39 timestamp bits — shorter lifespan, more sequence bits. The trade-off is configurable.
Throughput math — napkin numbers
Snowflake throughput is bounded by the sequence field:
Per machine: 4096 IDs / millisecond = 4,096,000 IDs / second
32 machines per DC: 32 × 4096 = 131,072 IDs / ms per datacenter
32 datacenters: 32 × 131,072 = 4,194,304 IDs / ms globally
| Scale | IDs/sec needed | Snowflake enough? |
|---|---|---|
| Indian startup MVP | 100 | Yes — by orders of magnitude |
| E-commerce sale peak | 10,000 | Yes — one machine handles it |
| Twitter 2012 | ~5,000 tweets/sec | Yes — with headroom |
| UPI peak (system-wide) | millions/sec | No — needs multiple ID spaces or wider sequence |
In an interview, do the math out loud. "12 sequence bits gives 4096 per ms per machine. At 10k IDs/sec I need 10 IDs per ms — well within limits." That beats reciting the bit diagram from memory.
If you exceed 4096 IDs in the same millisecond on one machine, the generator waits for the next millisecond (busy-spin or sleep). Throughput drops but uniqueness holds.
Clock skew — the problem nobody mocks in tests
Snowflake IDs depend on wall-clock time. If Machine A's clock is 500ms ahead of Machine B, Machine A generates IDs with future timestamps. When Machine B catches up, it might generate IDs that sort before IDs already issued — breaking monotonic ordering.
Defences:
1. NTP sync on every machine (mandatory, not optional)
2. Reject IDs if local clock < last generated timestamp
3. Wait until clock catches up before generating next ID
4. Alert if clock drift exceeds threshold (e.g., 100ms)
| Scenario | What happens | Fix |
|---|---|---|
| Clock jumps forward (NTP correction) | Sequence resets, possible duplicate if not handled | Track last_timestamp; reject backward jumps |
| Clock jumps backward | New IDs sort before old IDs | Block generation until clock ≥ last_timestamp |
| VM migration between hosts | Clock may shift | Re-sync NTP before resuming ID generation |
| Container without NTP | Drift accumulates | Run NTP sidecar or use coordinated service |
This is why some teams run a lightweight ID allocation service instead of pure local Snowflake — one source of truth for timestamps. You lose zero-coordination purity but gain clock sanity.
Datacenter and machine ID assignment
Each machine needs a unique (datacenter_id, machine_id) pair. Duplicate assignment = guaranteed collision, no matter how good your timestamp logic is.
Common assignment strategies:
Option 1 — Config file / env var
SNOWFLAKE_DC=1 SNOWFLAKE_MACHINE=5
Simple. Human error assigns same pair twice.
Option 2 — ZooKeeper / etcd lease
Machine boots → claims next free (dc, machine) slot → heartbeat
Slot released on graceful shutdown. Crash = slot locked until lease expires.
Option 3 — Kubernetes StatefulSet ordinal
Pod name "order-service-7" → machine_id = 7
Works if pod count ≤ 32 per DC.
For a two-datacenter setup (ap-south-1 + ap-south-2), assign DC 0 to Mumbai and DC 1 to Hyderabad. 32 machines per DC is 64 total generators — enough for most Indian product companies until you are operating at Flipkart tier.
Alternatives: Sonyflake, ULID, UUID v7
Snowflake is not the only option. Know these for interview bonus points:
| System | Format | Sortable | Coordination | Notes |
|---|---|---|---|---|
| Twitter Snowflake | 64-bit int | Yes | DC + machine IDs | Industry default reference |
| Sonyflake | 64-bit int | Yes | 16-bit machine ID | More machine bits, fewer DC bits |
| ULID | 128-bit string (26 chars) | Yes | None (random component) | Crockford base32, lexicographically sortable |
| UUID v7 (RFC draft) | 128-bit UUID | Yes | None | Timestamp in high bits, random in low |
| Instagram ID sharding | 64-bit int | Yes | DB shard embedded in ID | Custom layout per company |
ULID example:
01ARZ3NDEKTSV4RRFFQ69G5FAV
├──────── timestamp ────────┤├──── random ────┤
26 characters, case-insensitive, URL-safe
ULID needs no machine ID assignment — the random suffix handles uniqueness. Trade-off: 128 bits instead of 64, and no embedded datacenter metadata. Good for logs and event IDs. Less ideal as a narrow DB primary key.
UUID v7 is gaining adoption because it is a standard, works in any language, and sorts by time. If your team already uses UUIDs everywhere, v7 is an easier migration than a custom Snowflake implementation.
Tie-back: URL shortener short codes
Remember the URL shortener from URL Shortener Core? The recommended approach was unique numeric ID + base62 encode.
Snowflake ID: 139281847291263 → base62 → "kF3xP2q"
Short URL: schoolabe.com/x/kF3xP2q
Why Snowflake fits the shortener perfectly:
- Unique across all app servers and datacenters — no collision on insert
- Sortable — "latest links" query works on the numeric ID before encoding
- Compact after encoding — 7 base62 chars holds up to 62^7 ≈ 3.5 trillion IDs
- No central bottleneck — each app server generates IDs locally at redirect-create time
At scale (URL Shortener at Scale), the read path hits Redis and CDN. The write path still needs a fast unique ID — Snowflake removes the Postgres sequence from the critical path.
Contrast with hash-based codes: Snowflake guarantees uniqueness without retry loops. Hash needs collision detection and fallback — fine at small scale, messy at 10k creates/sec.
Operating a Snowflake service in production
Deployment checklist:
- NTP synced on every host (chrony or systemd-timesyncd)
- Unique (datacenter_id, machine_id) per generator instance
- Monitor: IDs generated/sec, clock drift alerts, sequence overflow waits
- Graceful shutdown: finish in-flight IDs before releasing machine slot
- Idempotent API: client retries must not create duplicate records (use client-supplied idempotency key separately from Snowflake ID)
When NOT to build Snowflake:
- Below 1,000 IDs/sec — Postgres sequence is simpler and battle-tested
- IDs must be opaque strings — use ULID or UUID v7 instead
- Strict global monotonic order required — Snowflake is per-machine roughly sorted, not globally strict
Libraries exist in every language: github.com/bwmarrin/snowflake (Go), flake-id (Node), pysnowflake (Python). Do not rewrite from scratch unless you enjoy debugging clock skew at 3 AM.
Common mistakes in Snowflake interviews
Mistake 1 — forgetting clock synchronization.
Drawing the bit layout without mentioning NTP is incomplete. Clock skew is the #1 production issue with timestamp-based IDs.
Mistake 2 — duplicate machine IDs.
Two Docker containers with machine_id=0 will collide. Always explain assignment strategy — env var, etcd, or StatefulSet ordinal.
Mistake 3 — assuming global strict ordering.
Snowflake IDs from different machines are roughly time-ordered, not perfectly ordered. Machine A at T+1ms can produce a lower ID than Machine B at T if B's clock is ahead. Use a DB timestamp for strict ordering if needed.
Mistake 4 — using Snowflake as a UUID string.
A 64-bit integer is 8 bytes. Stringifying it ("139281847291263") wastes space in JSON APIs. Base62-encode for URLs, keep integer in the database.
Mistake 5 — no plan for epoch exhaustion.
41 bits ≈ 69 years. Twitter's epoch expires around 2080. You will not be in that interview, but mentioning it shows you think long-term.
Interview talking points
When asked "design a distributed unique ID generator":
- Requirements — sortable? 64-bit int or string? QPS per machine? Multi-DC?
- Draw Snowflake — label each bit field with size and purpose
- Throughput — 4096/ms/machine, do the math for their stated QPS
- Clock skew — NTP + last_timestamp guard + wait on backward jump
- Machine ID assignment — etcd/ZooKeeper or config with validation
- Alternatives — UUID v7 if standard compliance matters, ULID for string IDs
- Tie to use case — "For a URL shortener I base62-encode this 64-bit ID"
End with: "I would use an off-the-shelf Snowflake library, assign machine IDs via etcd, and monitor clock drift." Practical beats theoretical.
Phase 3: full interview designs
You have the primitives. Phase 3 applies them to complete product designs — web crawler, notifications, news feed, and chat.
Continue here: How Web Crawlers Actually Work.