1Show the Level 1 solution
2**A.**
31. **Auto-increment.** One database; simplest and fastest.
42. **Snowflake-style or UUID v7.** Very high write rate, no central counter, time-ordered.
53. **Long random token** (at least 128 bits from a secure random generator), single-use and expiring.
64. **Counter + ranges + base62** (optionally scrambled), or random codes with a uniqueness check (Chapter 31).
75. **Snowflake-style IDs** (fit in BIGINT, roughly time-ordered, no per-ID coordination), optionally with the shard ID inside.
89**B.**
101. **False.** Always check authorisation. IDs leak through logs, emails and shared links.
112. **False.** Different machines' clocks differ by a few milliseconds, so the order is only approximate.
123. **False.** Rolled-back transactions and crashes leave gaps; count rows with `COUNT(*)` or a counter.
Show the Level 2 solution
1. 2⁴¹ ms ÷ (1,000 × 86,400 × 365.25) ≈ **69.7 years**. With 39 bits: a quarter of that, about **17.4 years**.
2. 2¹² = **4,096 per millisecond** per machine → **4,096,000 per second** per machine. With 1,024 machines: about **4.2 billion a second** in total.
3. 62¹⁰ ≈ 8.4 × 10¹⁷ is less than 2⁶⁴ ≈ 1.8 × 10¹⁹, and 62¹¹ ≈ 5.2 × 10¹⁹ is more, so **11 characters**. For 6 billion links: 62⁵ ≈ 0.9 billion is too few, 62⁶ ≈ 56.8 billion is enough, so **6 characters**.
4. (10¹²)² ÷ (2 × 2¹²²) ≈ **9.4 × 10⁻¹⁴**, about 1 in 10 trillion.
1Show the Level 3 solution
2Tested solution (Python 3), the key parts:
34```python
5EPOCH_MS = 1_767_225_600_000 # 2026-01-01 00:00:00 UTC
6MACHINE_BITS, SEQ_BITS = 10, 12
7MAX_MACHINE, MAX_SEQ = (1 << MACHINE_BITS) - 1, (1 << SEQ_BITS) - 1
8910class Snowflake:
11 def __init__(self, machine_id, clock_ms):
12 self.machine_id, self.clock_ms = machine_id, clock_ms
13 self.last_ms, self.seq = -1, 0
1415 def next_id(self):
16 now = self.clock_ms()
17 if now < self.last_ms:
18 raise ClockMovedBackwards(f"clock went back {self.last_ms - now} ms")
19 if now == self.last_ms:
20 self.seq = (self.seq + 1) & MAX_SEQ
21 if self.seq == 0: # 4,096 used this millisecond: wait
22 while now <= self.last_ms:
23 now = self.clock_ms()
24 else:
25 self.seq = 0
26 self.last_ms = now
27 return ((now - EPOCH_MS) << (MACHINE_BITS + SEQ_BITS)) | (self.machine_id << SEQ_BITS) | self.seq
282930def uuid7(ms):
31 rand = int.from_bytes(os.urandom(10), "big")
32 value = (ms & ((1 << 48) - 1)) << 80 # 48-bit timestamp
33 value |= 0x7 << 76 # version 7
34 value |= ((rand >> 62) & 0xFFF) << 64 # 12 random bits
35 value |= 0b10 << 62 # variant
36 value |= rand & ((1 << 62) - 1) # 62 random bits
37 return uuid.UUID(int=value)
38```
3940All checks pass. A sample ID from the test run, `4198518784`, decodes to machine 5, sequence 0, one second after the epoch, and is `4A8ywU` in base62. The full file is in `code/ch10_ids.py`.
1Show the Level 4 solution
2**The bug:** when the clock steps **backwards**, `now != last_ms`, so the code treats it as a fresh millisecond and **resets the sequence to 0**.
341. At 500 ms, the server creates ID (500, machine 7, seq 0).
52. NTP corrects the clock back to 499 ms. The next ID is (499, machine 7, seq 0), with the sequence reset.
63. The clock reaches 500 ms **again**. `now (500) != last_ms (499)`, so the sequence resets to 0 again.
74. The ID is (500, machine 7, seq 0): **identical to step 1**. The insert fails with a duplicate key, or worse, if IDs are used outside the database, two things share one ID.
89**The fix:** never go back in time. Remember the last timestamp; if the clock is behind it, wait (small steps) or refuse (big steps):
1011```python
12def next_id(self):
13 now = self.clock_ms()
14 if now < self.last_ms:
15 if self.last_ms - now <= 5: # small step back: wait it out
16 while now < self.last_ms:
17 now = self.clock_ms()
18 else:
19 raise ClockMovedBackwards(self.last_ms - now)
20 if now == self.last_ms:
21 self.seq = (self.seq + 1) & MAX_SEQ
22 if self.seq == 0:
23 while now <= self.last_ms:
24 now = self.clock_ms()
25 else:
26 self.seq = 0
27 self.last_ms = now
28 return ((now - EPOCH_MS) << 22) | (self.machine_id << 12) | self.seq
29```
3031Also fix the cause: configure time sync to slew rather than step where possible, and alert on large clock corrections.
1Show the Level 5 (sketch) solution
21. **Order IDs:** Snowflake-style 64-bit: 41 bits of time from a 2026 epoch, **3 bits of shard** (8 shards; leave room by using more bits, say 5 for up to 32), the remaining bits split between generator ID and sequence. Fits BIGINT, roughly time-ordered, and the shard can be read from the ID.
32. **Generator IDs:** leased at startup from a coordinator (ZooKeeper or etcd) with a time-limited lease, so a crashed server's ID can be reused only after it's surely gone; or set per deployment in configuration and checked for duplicates.
43. **Clock backwards:** wait for small steps, refuse and alert for large ones; time sync set to slew; monitor clock offset on every generator.
54. **Wishlist links:** a separate **random** public token (UUID v4 or 16 random bytes in base62, about 22 characters), stored next to the internal ID, revocable by the owner.
65. **Existing IDs:** keep them. Start the new scheme's values well **above** 40 million (any Snowflake ID with a real timestamp is already in the trillions), so old and new never collide and both fit the same BIGINT column.