A potential C# Snowflake Generator

A potential C# Snowflake Generator

Introduction

A Snowflake Generator generates unique IDs. Creating a class in C# for generating Snowflake IDs involves adhering to the structure of Snowflake IDs, as mentioned in my previous posts. Let’s implement a basic version of such a class. This implementation will consider a simplified scenario and may need to be adjusted based on your specific requirements, like the exact epoch start date, data centre ID, and worker ID.

A simple Snowflake Generator

using System;

public class SnowflakeIdGenerator
{
    private const long Epoch = 1288834974657L; // Custom epoch (Twitter's original epoch)
    private const int WorkerIdBits = 5;
    private const int DatacenterIdBits = 5;
    private const int SequenceBits = 12;

    private const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits);
    private const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits);
    private const long SequenceMask = -1L ^ (-1L << SequenceBits);

    private long _workerId;
    private long _datacenterId;
    private long _sequence = 0L;

    private long _lastTimestamp = -1L;

    public SnowflakeIdGenerator(long workerId, long datacenterId)
    {
        if (workerId > MaxWorkerId || workerId < 0)
        {
            throw new ArgumentException($"Worker ID must be between 0 and {MaxWorkerId}");
        }

        if (datacenterId > MaxDatacenterId || datacenterId < 0)
        {
            throw new ArgumentException($"Datacenter ID must be between 0 and {MaxDatacenterId}");
        }

        _workerId = workerId;
        _datacenterId = datacenterId;
    }

    public long NextId()
    {
        lock (this)
        {
            var timestamp = CurrentTimeMillis();
            if (timestamp < _lastTimestamp)
            {
                throw new InvalidOperationException("Clock moved backwards. Refusing to generate id.");
            }

            if (_lastTimestamp == timestamp)
            {
                _sequence = (_sequence + 1) & SequenceMask;
                if (_sequence == 0)
                {
                    timestamp = TilNextMillis(_lastTimestamp);
                }
            }
            else
            {
                _sequence = 0;
            }

            _lastTimestamp = timestamp;
            return ((timestamp - Epoch) << (WorkerIdBits + DatacenterIdBits + SequenceBits))
                   | (_datacenterId << (WorkerIdBits + SequenceBits))
                   | (_workerId << SequenceBits)
                   | _sequence;
        }
    }

    private long TilNextMillis(long lastTimestamp)
    {
        var timestamp = CurrentTimeMillis();
        while (timestamp <= lastTimestamp)
        {
            timestamp = CurrentTimeMillis();
        }
        return timestamp;
    }

    private static long CurrentTimeMillis()
    {
        return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
    }
}

Usage:

var generator = new SnowflakeIdGenerator(workerId: 1, datacenterId: 1);
long id = generator.NextId();
Console.WriteLine($"Generated Snowflake ID: {id}");

Notes:

  • Thread Safety: The NextId method is synchronised to ensure that two threads do not generate the same ID simultaneously.
  • Time Handling: The implementation checks for clock rollback and waits for the next millisecond if the sequence limit for the current millisecond is reached.
  • Customisation: You should customise the Epoch, WorkerIdBits, DatacenterIdBits, and SequenceBits according to your application’s needs.
  • Exception Handling: The code throws an exception if the clock goes backward or if invalid worker/datacenter IDs are provided.
  • Scalability: This basic implementation does not handle scenarios where the system clock is skewed or adjusted frequently. For large-scale systems, more robust handling of these scenarios is required.

Remember, generating Snowflake IDs at a very high rate with this generator can exhaust all available IDs for a given millisecond, leading to potential delays as the generator waits for the next millisecond. Ensure that the ID generation rate aligns with your system’s requirements. This snowflake generator is designed for a situations where the next id would only be called intermittently.

I am not sure that a ‘long’ is the right ID type for my use, so I am working on producing a version of this routine that will generate an 8-character UUID. Stay tuned!

You can find a great write up on snowflake id’s here: – https://en.wikipedia.org/wiki/Snowflake_ID

My previous post on this subject is here: – Snowflake Id’s – Unique Id’s – sort of.

Stephen

Hi, my name is Stephen Finchett. I have been a software engineer for over 30 years and worked on complex, business critical, multi-user systems for all of my career. For the last 15 years, I have been concentrating on web based solutions using the Microsoft Stack including ASP.Net, C#, TypeScript, SQL Server and running everything at scale within Kubernetes.