Backend Development 17 min read

Snowflake ID Algorithm: Theory, Advantages, and .NET Implementation with Redis

This article explains the principles of primary keys, compares auto‑increment IDs and GUIDs, introduces the Snowflake ID structure, discusses its benefits and drawbacks, and provides a complete .NET implementation using Redis for dynamic worker‑ID allocation, including handling clock rollback and code examples.

Fulu Network R&D Team
Fulu Network R&D Team
Fulu Network R&D Team
Snowflake ID Algorithm: Theory, Advantages, and .NET Implementation with Redis

Background

Primary keys uniquely identify each row in a table, so the most basic requirement for a primary key is uniqueness.

Many developers initially choose an auto‑increment integer because it is simple to configure, but auto‑increment keys have obvious pros and cons.

Advantages:

No coding required, generated by the database, fast, sequential storage.

Numeric format, small space consumption.

Disadvantages:

Limited range; risk of exhausting the values.

When importing old data, ID collisions or resets may occur.

Sharding scenarios become cumbersome.

GUID

A GUID (Globally Unique Identifier) is a 128‑bit binary number generated by an algorithm; in theory no two computers will generate the same GUID, guaranteeing uniqueness, but it also has pros and cons.

Advantages:

Unique in distributed scenarios.

Facilitates merging data from multiple servers.

Disadvantages:

Large storage overhead.

Unordered, leading to poor performance in sorting scenarios.

The biggest drawback of GUIDs is that they are unordered; because database primary keys are usually clustered indexes, unordered data degrades performance when sorting. Ordered GUIDs can be generated, but they still consume more space.

Concept Introduction

A good primary key should have the following characteristics:

Uniqueness.

Monotonically increasing order.

Minimal storage footprint.

Support for distributed environments.

The optimized Snowflake algorithm satisfies all these properties.

The following diagram shows the Snowflake algorithm architecture:

Snowflake ID consists of 1 sign bit + 41‑bit timestamp + 10‑bit worker ID + 12‑bit sequence, totaling 64 bits stored in a long.

1 sign bit : The highest bit of a signed long; 0 for positive IDs, ensuring generated IDs are positive.

41‑bit timestamp : Allows about 69 years of timestamps. By setting the epoch to 2020 instead of 1970, the usable period is extended.

10‑bit worker ID : Supports up to 1024 nodes in a distributed cluster.

12‑bit sequence : Supports up to 4096 IDs per millisecond (≈4,096,000 IDs per second). Using a larger sequence (e.g., 13 bits) can increase per‑node throughput.

Development Plan

Implementing the algorithm requires careful handling of potential pitfalls, such as worker‑ID allocation, reclamation, and clock rollback.

Worker‑ID allocation can be done via Redis. When the Redis INCR command exceeds the maximum worker ID, reclaimed IDs (identified by stale scores) are fetched from a sorted set.

Clock rollback is handled by detecting a timestamp that is less than the last timestamp; in that case the worker ID is re‑initialized to avoid duplicate IDs.

Below is the core implementation of the Snowflake ID generator in C#:

public class SnowflakeIdMaker : ISnowflakeIdMaker
{
    private readonly SnowflakeOption _option;
    static object locker = new object();
    private long lastTimestamp = -1L;
    private uint lastIndex = 0;
    private readonly int _workIdLength;
    private readonly int _maxWorkId;
    private readonly int _indexLength;
    private readonly int _maxIndex;
    private int? _workId;
    private readonly IServiceProvider _provider;

    public SnowflakeIdMaker(IOptions
options, IServiceProvider provider)
    {
        _provider = provider;
        _option = options.Value;
        _workIdLength = _option.WorkIdLength;
        _maxWorkId = 1 << _workIdLength;
        _indexLength = 22 - _workIdLength;
        _maxIndex = 1 << _indexLength;
    }

    private async Task Init()
    {
        var distributed = _provider.GetService
();
        if (distributed != null)
        {
            _workId = await distributed.GetNextWorkId();
        }
        else
        {
            _workId = _option.WorkId;
        }
    }

    public long NextId(int? workId = null)
    {
        if (workId != null) _workId = workId.Value;
        if (_workId > _maxWorkId) throw new ArgumentException($"Worker ID out of range 0-{_maxWorkId}");
        lock (locker)
        {
            if (_workId == null) Init().Wait();
            var currentTimeStamp = TimeStamp();
            if (lastIndex >= _maxIndex)
            {
                currentTimeStamp = TimeStamp(lastTimestamp);
            }
            if (currentTimeStamp > lastTimestamp)
            {
                lastIndex = 0;
                lastTimestamp = currentTimeStamp;
            }
            else if (currentTimeStamp < lastTimestamp)
            {
                Init().Wait();
                return NextId();
            }
            var time = currentTimeStamp << (_indexLength + _workIdLength);
            var work = _workId.Value << _workIdLength;
            var id = time | work | lastIndex;
            lastIndex++;
            return id;
        }
    }

    private long TimeStamp(long lastTimestamp = 0L)
    {
        var current = (DateTime.Now.Ticks - _option.StartTimeStamp.Ticks) / 10000;
        if (lastTimestamp == current) return TimeStamp(lastTimestamp);
        return current;
    }
}

The IDistributedSupport interface abstracts the worker‑ID allocation mechanism, allowing implementations based on Redis, databases, or other middleware.

public interface IDistributedSupport
{
    Task
GetNextWorkId();
    Task RefreshAlive();
}

An example Redis‑based implementation:

public class DistributedSupportWithRedis : IDistributedSupport
{
    private IRedisClient _redisClient;
    private readonly string _currentWorkIndex = "current.work.index";
    private readonly string _inUse = "in.use";
    private readonly RedisOption _redisOption;
    private int _workId;

    public DistributedSupportWithRedis(IRedisClient redisClient, IOptions
redisOption)
    {
        _redisClient = redisClient;
        _redisOption = redisOption.Value;
    }

    public async Task
GetNextWorkId()
    {
        _workId = (int)(await _redisClient.IncrementAsync(_currentWorkIndex)) - 1;
        if (_workId > (1 << _redisOption.WorkIdLength))
        {
            var newWorkId = await _redisClient.SortedRangeByScoreWithScoresAsync(_inUse, 0, GetTimestamp(DateTime.Now.AddMinutes(5)), 0, 1, Order.Ascending);
            if (!newWorkId.Any()) throw new Exception("No available nodes");
            _workId = int.Parse(newWorkId.First().Key);
        }
        await _redisClient.SortedAddAsync(_inUse, _workId.ToString(), GetTimestamp());
        return _workId;
    }

    private long GetTimestamp(DateTime? time = null)
    {
        var dt = time ?? DateTime.Now;
        var epoch = new DateTime(1970, 1, 1);
        return (dt.Ticks - epoch.Ticks) / 10000;
    }

    public async Task RefreshAlive()
    {
        await _redisClient.SortedAddAsync(_inUse, _workId.ToString(), GetTimestamp());
    }
}

To register the service in Startup :

services.AddSnowflakeWithRedis(opt =>
{
    opt.InstanceName = "aaa:";
    opt.ConnectionString = "10.0.0.146";
    opt.WorkIdLength = 9;
    opt.RefreshAliveInterval = TimeSpan.FromHours(1);
});

When needed, obtain ISnowflakeIdMaker from DI and call NextId() to generate a unique ID.

Conclusion

The article has covered the composition of Snowflake IDs, the pitfalls that may arise during implementation, and a complete .NET solution using Redis for dynamic worker‑ID allocation.

If you found this article helpful, feel free to recommend it or star the GitHub repository.

GitHub: https://github.com/fuluteam/ICH.Snowflake

Distributed SystemsRedisC++Snowflake ID.NETunique identifier
Fulu Network R&D Team
Written by

Fulu Network R&D Team

Providing technical literature sharing for Fulu Holdings' tech elite, promoting its technologies through experience summaries, technology consolidation, and innovation sharing.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.