How a Snowflake ID is built
Twitter designed Snowflake IDs in 2010 to hand out unique, roughly time-ordered 64-bit IDs from many machines without a central counter. The ID is a long with the sign bit left at 0, then three parts:
| Bits | Part | Holds |
|---|---|---|
| 1 | sign | always 0, so the ID is a positive long |
| 41 | timestamp | milliseconds since the epoch: about 69 years |
| 10 | machine (generator) | 1,024 generators; Twitter split it into 5 bits of datacenter and 5 of worker, Discord into worker and process |
| 12 | sequence | 4,096 IDs per millisecond per generator |
So the time is simply (id >> 22) + epoch. Because the timestamp comes first, IDs sort by time, which suits bigint primary keys and clustered indexes.
Epochs: Twitter, Discord and IdGen
The timestamp counts from a custom epoch rather than 1970, which buys decades. Twitter's epoch is 1288834974657 (2010-11-04T01:42:54.657Z). Discord's is 1420070400000, the start of 2015, and IdGen's default epoch is the same moment. The same ID decodes to different dates under different epochs, so an ID from another system only makes sense with its own epoch. With 41 bits, Twitter's IDs run out in 2080 and Discord's in 2084.
Snowflake IDs in .NET with IdGen
The IdGen package implements Snowflake IDs with a configurable IdStructure (the three bit counts must add up to 63) and epoch. Give each process its own generator ID, for example from configuration or a pod ordinal, and create one IdGenerator per process: two generators with the same ID produce duplicates. The C# above follows the epoch, layout and generator ID you choose.
- By default, IdGen throws
SequenceOverflowExceptionwhen a generator makes more than 4,096 IDs in one millisecond. PassSequenceOverflowStrategy.SpinWaitinIdGeneratorOptionsto wait for the next millisecond instead (what the generator on this page does). - If the system clock goes back,
CreateIdthrowsInvalidSystemClockExceptionrather than risk a duplicate. FromIdcomputes the time without an overflow check. With the usual 41 bits that never matters; with timestamp fields of 50 bits or more it can return a wrong date, which the decoder above points out.
Snowflake IDs and JavaScript
A JavaScript number is a double, exact only up to 253 (9,007,199,254,740,992), and nearly every Snowflake ID is larger. JSON.parse('{"id":1541815603606036480}') gives 1541815603606036500, a different ID, without an error. That is why Twitter's API added id_str and Discord sends every ID as a string. Do the same in ASP.NET Core:
using System.Text.Json;
using System.Text.Json.Serialization;
// JavaScript numbers are exact only up to 2^53, and Snowflake IDs are larger.
// Send them to browsers as strings, as Twitter's id_str and Discord's API do.
var asString = new JsonSerializerOptions { NumberHandling = JsonNumberHandling.WriteAsString | JsonNumberHandling.AllowReadingFromString };
Console.WriteLine(JsonSerializer.Serialize(new { Id = 1541815603606036480L }, asString)); // {"Id":"1541815603606036480"}
Console.WriteLine(JsonSerializer.Serialize(new { Id = 1541815603606036480L })); // {"Id":1541815603606036480}
Snowflake ID vs GUID and ULID
A Snowflake ID is 8 bytes instead of 16, fits in a bigint column and is readable, but it needs a unique generator ID per process and a sane clock. GUIDs and ULIDs need no coordination at all: version 7 GUIDs and ULIDs are also time-ordered, at twice the size. See the GUID tool and the ULID generator.
FAQ
How do I get the date from a Snowflake ID?
Shift the ID right by 22 bits to get the milliseconds since the epoch, and add the epoch: 1288834974657 for Twitter / X, 1420070400000 for Discord. The decoder above does it for any epoch and layout.
What is the Discord epoch?
1420070400000 ms, the first second of 2015 (UTC). IdGen uses the same default epoch.
What is the Twitter / X Snowflake epoch?
1288834974657 ms, which is 2010-11-04T01:42:54.657Z.
How do I generate Snowflake IDs in C#?
Install IdGen, create one IdGenerator per process with a unique generator ID (new IdGenerator(1)), and call CreateId(). Use IdGeneratorOptions for a custom epoch or bit layout.
Why does my Snowflake ID change in JavaScript?
It is larger than 2^53, so a JavaScript number cannot hold it exactly and JSON.parse rounds it. Send IDs as strings.
How many IDs can a Snowflake generator make per second?
With 12 sequence bits, 4,096 per millisecond per generator, so about 4 million per second, and 1,024 generators with 10 machine bits.