The Clock Went Backwards

This generator has run in production for a year. An NTP step correction moves the host clock 400 ms into the past.

func (g *Gen) Next() int64 {
    g.mu.Lock()
    defer g.mu.Unlock()
    now := time.Now().UnixMilli()
    if now == g.lastMs {
        g.seq = (g.seq + 1) & 0xFFF
        if g.seq == 0 {
            for now <= g.lastMs {
                now = time.Now().UnixMilli()
            }
        }
    } else {
        g.seq = 0
    }
    g.lastMs = now
    return ((now - epoch) << 22) | (g.machineID << 12) | g.seq
}

What breaks, and what is the fix?

A

time.Now().UnixMilli() is a wall clock reading, so ids are not sortable. Read a monotonic clock instead and derive the timestamp from process start.

B

With now < g.lastMs the code falls into the else branch, resets the sequence to 0 and re-issues ids for milliseconds it already used, so it can emit a duplicate. Add a now < g.lastMs guard that waits or returns an error until the clock catches up.

C

The spin loop can burn a whole millisecond of CPU under load. Replace it with a sleep so the generator does not peg a core.

D

g.seq is masked to 12 bits after the increment, so on the 4,096th id it overflows into the machine field and the id is attributed to the wrong host.