Snowflake IDs: When Sequence Exhaustion Meets Clock Rollback

Published

A generator can reject a backward clock jump and still produce a duplicate later if the failed call leaves its sequence counter in the wrong state. A recent Coola fix demonstrates why clock handling needs to be tested together with sequence exhaustion.

What changed in Coola

Coola PR #2060 merged on October 22, 2026 in Prague (October 21 at 23:58 UTC). It fixes a failure path after all 4,096 sequence values in one millisecond have been used. During the wait for a later millisecond, a backward clock jump raised a RuntimeError, but the counter had already wrapped to zero. When time returned to the previous millisecond, another call could advance to sequence one and repeat an ID.

The patch restores the exhausted sequence before raising, provided another thread has not already advanced the generator. Its regression tests cover recovery without duplicates and preservation of another thread's newer state. These details matter because the wait releases the lock, allowing another caller to make progress.

Why an ordinary rollback test misses the bug

A test that moves the clock backward before generation may exercise an early rejection without touching the overflow path. Here, the dangerous transition happened after sequence exhaustion, while waiting for time to advance. The failed operation changed state even though it returned no identifier.

For a generator with a timestamp, worker ID, and sequence, reusing the same combination repeats the identifier. Random collision probabilities do not describe this failure: it comes from reusing generator state.

Make the clock policy explicit

Coola documents an exception on rollback because waiting for the clock to recover could block indefinitely. Other implementations may wait or maintain a logical timestamp. Document the chosen behavior, including retry expectations and whether a logical timestamp can differ from wall time. The correct recovery path must preserve every combination already issued.

When reviewing a Snowflake implementation, exercise this sequence:

  1. Exhaust the sequence space at a fixed millisecond.
  2. Move the clock backward during the overflow wait.
  3. Check that the failed call leaves no already-used sequence available.
  4. Restore time, generate again, and compare against every ID issued before the failure.
  5. Repeat with another thread advancing state while the waiting call has released its lock.

Also assign distinct worker IDs to independent generators, including forked processes. Correct rollback handling cannot compensate for two processes issuing the same timestamp and sequence under one worker ID.

Explore the format with the Snowflake ID generator, or read about a different shared-state failure in monotonic ULID concurrency.


Browse all UUID articles →