jonnifyfsharp

Dive C4.07.1

The Snowflake

A 64-bit integer that carries its own clock. Read its high bits and you have the time it was made — to the millisecond.

A Snowflake packs three fields into one int64: a millisecond timestamp in the high bits, a node id in the middle, and a within-millisecond sequence in the low bits. EchoMQ uses the exact layout in echo_fs/EchoMQ/Snowflake.fs: (ts - Epoch) << 22 | node << 12 | seq, against the epoch 1704067200000 (2024-01-01). Because the timestamp is the high field, a later mint is always a larger integer — the property the queue turns into "byte order is mint order". This dive composes one, then reads it back with shifts and masks; every value was produced by dotnet fsi.

C4.07.1 · part 1

The layout — three fields in one integer

The 64 bits split into one unused sign bit, then a 42-bit timestamp, a 10-bit node id, and a 12-bit sequence. NodeBits = 10 and SeqBits = 12, so the timestamp is shifted left by 22 and the node by 12. The constants and the compose function below are quoted verbatim from echo_fs/EchoMQ/Snowflake.fs.

module Snowflake =
    [<Literal>]
    let Epoch = 1704067200000L   // 2024-01-01T00:00:00Z

    [<Literal>]
    let NodeBits = 10
    [<Literal>]
    let SeqBits = 12

    let private nodeMax = (1L <<< NodeBits) - 1L
    let private seqMax = (1L <<< SeqBits) - 1L

    /// Compose a snowflake from a wall-clock millisecond, a node id, and a sequence.
    let compose (tsMs: int64) (node: int64) (seq: int64) : int64 =
        ((tsMs - Epoch) <<< (NodeBits + SeqBits))
        ||| ((node &&& nodeMax) <<< SeqBits)
        ||| (seq &&& seqMax)
Epoch
the custom zero point, 1704067200000L ms (2024-01-01). The stored timestamp is tsMs - Epoch, which keeps the value small and within 42 bits for decades.
NodeBits / SeqBits
10 and 12, so node ranges 0–1023 and seq ranges 0–4095 within one millisecond.
nodeMax / seqMax
the masks (1L <<< bits) - 1L — all-ones for that field — used to clamp each input before it is OR-ed in.
<<< · ||| · &&&
F#'s left-shift, bitwise-OR, and bitwise-AND on int64. The shift places each field; the OR merges them; the AND keeps each field within its width.
C4.07.1 · part 2

Reading it back — shifts and masks

Each accessor undoes one part of the composition. timestamp shifts the high bits back down and re-adds the epoch; node shifts past the sequence and masks; seq masks the low 12 bits. at turns the timestamp into a DateTimeOffset. All four are quoted verbatim.

/// The wall-clock millisecond the id was minted at.
let timestamp (id: int64) : int64 = (id >>> (NodeBits + SeqBits)) + Epoch

/// The minting node id.
let node (id: int64) : int64 = (id >>> SeqBits) &&& nodeMax

/// The within-millisecond sequence.
let seq (id: int64) : int64 = id &&& seqMax

/// The mint instant as a DateTimeOffset.
let at (id: int64) : System.DateTimeOffset =
    System.DateTimeOffset.FromUnixTimeMilliseconds(timestamp id)

Composed and read back in FSI, for a fixed example — minted at 2024-06-01T12:00:00Z, node 7, sequence 0:

let id = Snowflake.compose 1717243200000L 7L 0L
// val id : int64 = 55264149504028672L
Snowflake.timestamp id     // val it : int64 = 1717243200000L
Snowflake.node id          // val it : int64 = 7L
Snowflake.seq id           // val it : int64 = 0L
Snowflake.at id            // val it : DateTimeOffset = 01/06/2024 12:00:00 +00:00
The round trip is exact: timestamp (compose tsMs node seq) = tsMs whenever the inputs fit their fields. The unused sign bit keeps the integer non-negative, so it compares and sorts as an ordinary positive int64.

Decode the bits

Pick a field. The diagram marks it; the readout shows that field's value for a fixed example id, computed by the same shift-and-mask the F# accessors use. The id is 55264149504028672 — Snowflake.compose 1717243200000L 7L 0L, verified in FSI. The decode runs in this page's JavaScript with the identical layout (id >>> 22, (id >>> 12) & 0x3FF, id & 0xFFF).

A 64-bit Snowflake · select a field

64-bit integer, high bits first 0 1b · unused timestamp (id >>> 22) + Epoch 42 bits node (id >>> 12) & 0x3FF 10 bits seq id & 0xFFF 12 bits decimal id 55264149504028672
timestamp · (id >>> 22) + Epoch = 1717243200000 ms = 2024-06-01 12:00:00 UTC · the high field, so a later mint is a larger integer.
timestamp
1717243200000
2024-06-01 12:00:00 UTC
node
7
the minting node
seq
0
first within the millisecond
Without JavaScript: the three fields of this id

The clock lives inside the number. No created_at column, no central counter — sort the integers and you have sorted by time.

C4.07.1 · part 3

Why the timestamp is the high field

Field order decides sort order. With the timestamp in the high bits, two ids minted in different milliseconds compare in time order regardless of node or sequence — a later millisecond is a strictly larger integer. Within one millisecond, the sequence breaks ties; across nodes, the node id breaks the remaining ties. The result is a total order that needs no coordination: every node mints on its own and the integers still line up by time.

// three mints; A and B share a millisecond, C is one ms later (FSI)
let a = Snowflake.compose 1717243200000L 7L 0L  // 55264149504028672L
let b = Snowflake.compose 1717243200000L 7L 1L  // 55264149504028673L
let c = Snowflake.compose 1717243200001L 7L 0L  // 55264149508222976L
a < b && b < c    // val it : bool = true

The layout gives

Timestamp high, node middle, sequence low — a single int64 whose numeric order is mint order.

The next dive asks

How does this integer travel as a string and keep that order? Base62, fixed width, ascending alphabet — C4.07.2.

EchoMQ in F#

EchoMQ gives each job a Snowflake.compose id, so the pending set is held in mint order with no second index — the bus serves the oldest job first by reading the smallest id. The single-writer broker advances the sequence within a millisecond and lets the timestamp tick when it exhausts, exactly as the layout intends.

The F# EchoMQ mints these ids today — Snowflake is built and tested (SnowflakeTests covers the compose/decode round trip). The Valkey transport EchoMQ.Connector lands next.

The cost model on .NET

An int64 is a value type: 8 bytes, stored inline in a record or on the stack, with no heap allocation and no boxing in the integer path. compose and every accessor are constant-time bit operations — a handful of shifts, ORs, and ANDs that the JIT lowers to single machine instructions. The Elixir course (E4.07) builds the same Snowflake on the BEAM, where an integer is a tagged term; on the CLR it is a machine word, so F#'s integer path allocates nothing.

Status — echo_fs is an in-memory model. The EchoMQ protocol — the Lua transitions executing on a Valkey wire — is not implemented (there is no Lua and no Valkey client in the code); that is the coming-soon transport (EchoMQ.Connector). This block shows how the model uses the structure, not a running bus.

References

Sources

Related in this course