Machine Coding Problem

Distributed ID Generator

maco60macoAllinfrastructuresnowflake-strategyclock-providersresilient-clock-skew-backoffsthread-safe-bitwise-math
Commonly Asked By:TwitterMetaMicrosoftAmazon

Functional Scope (In-Scope)

  • Resilient Generation Strategies (Strategy Pattern): Support switching algorithms (Snowflake vs UUID) based on storage and sortability needs.
  • 64-Bit k-Sortable Snowflake Layouts: Bitwise layout structuring timestamp (41 bits), machineId (10 bits), and millisecond sequences (12 bits).
  • Clock Skew Safety (Clock Providers): Decouple system clocks behind mockable providers, implementing active backoff sleeps.
  • Zero Inter-Node Coordination: Generate unique, collision-free numbers without database sequences.

Explicit Boundaries (Out-of-Scope)

  • No Automatic NTP Server Sync Sync: Out-of-scope to sync local server clocks using active NTP daemons.
  • No Human-Friendly String Obfuscation: Generates strict binary BigInt numbers; does not compile readable short hashes.

Clean reference designs demonstrating Snowflake generators in Java and Python:

// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;

interface ClockProvider {
    long currentTimeMillis();
}

class SystemClockProvider implements ClockProvider {
    @Override
    public long currentTimeMillis() {
        return System.currentTimeMillis();
    }
}

// ─── STRATEGY PATTERN (GENERATOR STRATEGIES) ──────────────────────────────────
interface IdGenerationStrategy {
    String nextId();
}

class UUIDGenerationStrategy implements IdGenerationStrategy {
    @Override
    public String nextId() {
        return UUID.randomUUID().toString();
    }
}

class SnowflakeIdStrategy implements IdGenerationStrategy {
    private final long machineId;
    private final ClockProvider clockProvider;
    private final long epoch = 1609459200000L; // Custom Epoch: Jan 1, 2021

    private final long machineIdBits = 10L;
    private final long maxMachineId = -1L ^ (-1L << machineIdBits);
    private final long sequenceBits = 12L;

    private final long machineIdShift = sequenceBits;
    private final long timestampLeftShift = sequenceBits + machineIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    private long lastTimestamp = -1L;
    private long sequence = 0L;
    private final ReentrantLock lock = new ReentrantLock();

    public SnowflakeIdStrategy(long machineId, ClockProvider clockProvider) {
        if (machineId < 0 || machineId > maxMachineId) {
            throw new IllegalArgumentException("Machine ID out of range");
        }
        this.machineId = machineId;
        this.clockProvider = clockProvider;
    }

    @Override
    public String nextId() {
        lock.lock();
        try {
            long timestamp = clockProvider.currentTimeMillis();

            if (timestamp < lastTimestamp) {
                // Clock-skew backoff handling
                long drift = lastTimestamp - timestamp;
                if (drift < 10) { // Graceful drift: wait a brief moment
                    try {
                        Thread.sleep(drift);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                    }
                    timestamp = clockProvider.currentTimeMillis();
                }
                
                if (timestamp < lastTimestamp) {
                    throw new IllegalStateException("Clock moved backwards! Drift exceeds recovery threshold.");
                }
            }

            if (timestamp == lastTimestamp) {
                sequence = (sequence + 1) & sequenceMask;
                if (sequence == 0) {
                    timestamp = waitNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0L;
            }

            lastTimestamp = timestamp;

            long id = ((timestamp - epoch) << timestampLeftShift)
                    | (machineId << machineIdShift)
                    | sequence;
            return String.valueOf(id);
        } finally {
            lock.unlock();
        }
    }

    private long waitNextMillis(long lastTimestamp) {
        long timestamp = clockProvider.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = clockProvider.currentTimeMillis();
        }
        return timestamp;
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== Distributed ID Generator ===");
        ClockProvider clock = new SystemClockProvider();
        IdGenerationStrategy snowflake = new SnowflakeIdStrategy(1, clock);

        // Thread Pool simulation
        ExecutorService executor = Executors.newFixedThreadPool(4);
        Set<String> generatedIds = ConcurrentHashMap.newKeySet();
        CountDownLatch latch = new CountDownLatch(100);

        for (int i = 0; i < 100; i++) {
            executor.submit(() -> {
                try {
                    String id = snowflake.nextId();
                    generatedIds.add(id);
                } finally {
                    latch.countDown();
                }
            });
        }

        latch.await();
        executor.shutdown();
        System.out.println("Generated unique IDs: " + generatedIds.size() + " / 100");
    }
}