Module: DiscordStore::Snowflake
- Defined in:
- lib/discord_store/snowflake.rb
Overview
Discord snowflake IDs, treated as what they actually are: a k-sortable primary key with a millisecond timestamp baked into the high bits.
63 22 17 12 0
+--------------------------------------+------+------+------------+
| milliseconds since 2015-01-01 (42b) | wrkr | proc | incr (12b) |
+--------------------------------------+------+------+------------+
Two properties matter for using Discord as a store:
-
IDs issued later sort after IDs issued earlier, so the natural message order in a channel is insertion order, and no separate sequence column is needed. The log sequence number is free.
-
Because the timestamp is recoverable, a wall-clock range maps onto an ID range. Discord's +before+/+after+ pagination parameters take snowflakes, so "every record written between 09:00 and 10:00" is a server-side range scan rather than a client-side filter over the whole channel.
Constant Summary collapse
- EPOCH_MS =
Discord's epoch: 2015-01-01T00:00:00Z, in milliseconds.
1_420_070_400_000- TIMESTAMP_SHIFT =
22- WORKER_SHIFT =
17- PROCESS_SHIFT =
12- WORKER_MASK =
0x1F
- PROCESS_MASK =
0x1F
- INCREMENT_MASK =
0xFFF
- MAX_TIMESTAMP_MS =
The largest value the 42-bit timestamp field can hold.
(1 << 42) - 1
Class Method Summary collapse
-
.at(id) ⇒ Time
UTC time at which
idwas issued. -
.from_time(time) ⇒ Integer
Build a synthetic snowflake for the given time, with all low bits zeroed.
-
.from_time_inclusive(time) ⇒ Integer
The same instant as Snowflake.from_time, but with every low bit set, so that it sorts after any real ID issued during that millisecond.
-
.parts(id) ⇒ Hash
The worker/process/increment fields.
-
.timestamp_ms(id) ⇒ Integer
Milliseconds since the Unix epoch at which
idwas issued. -
.valid?(value) ⇒ Boolean
Whether
valuecould plausibly be a snowflake this library issued or read.
Class Method Details
.at(id) ⇒ Time
Returns UTC time at which id was issued.
49 50 51 |
# File 'lib/discord_store/snowflake.rb', line 49 def at(id) Time.at((id) / 1000.0).utc end |
.from_time(time) ⇒ Integer
Build a synthetic snowflake for the given time, with all low bits zeroed.
The result is not a real message ID and will never collide with one, but it sorts exactly where a real ID issued at that instant would sort. That makes it the correct bound for +before+/+after+ pagination when you want a time range rather than a message range.
78 79 80 81 82 83 84 85 86 |
# File 'lib/discord_store/snowflake.rb', line 78 def from_time(time) ms = time.is_a?(Time) ? (time.to_f * 1000).floor : Integer(time) offset = ms - EPOCH_MS raise ArgumentError, "time predates the Discord epoch (2015-01-01)" if offset.negative? raise ArgumentError, "time overflows the 42-bit snowflake timestamp" if offset > MAX_TIMESTAMP_MS offset << TIMESTAMP_SHIFT end |
.from_time_inclusive(time) ⇒ Integer
The same instant as from_time, but with every low bit set, so that it sorts after any real ID issued during that millisecond. Use this as an inclusive upper bound.
94 95 96 |
# File 'lib/discord_store/snowflake.rb', line 94 def from_time_inclusive(time) from_time(time) | ((1 << TIMESTAMP_SHIFT) - 1) end |
.parts(id) ⇒ Hash
The worker/process/increment fields. Present for completeness and for debugging odd ordering; nothing in this library depends on them.
58 59 60 61 62 63 64 65 66 |
# File 'lib/discord_store/snowflake.rb', line 58 def parts(id) id = Integer(id) { timestamp_ms: (id), worker: (id >> WORKER_SHIFT) & WORKER_MASK, process: (id >> PROCESS_SHIFT) & PROCESS_MASK, increment: id & INCREMENT_MASK } end |
.timestamp_ms(id) ⇒ Integer
Milliseconds since the Unix epoch at which id was issued.
43 44 45 |
# File 'lib/discord_store/snowflake.rb', line 43 def (id) (Integer(id) >> TIMESTAMP_SHIFT) + EPOCH_MS end |
.valid?(value) ⇒ Boolean
Whether value could plausibly be a snowflake this library issued or read.
102 103 104 105 106 107 |
# File 'lib/discord_store/snowflake.rb', line 102 def valid?(value) id = Integer(value) id.positive? && id < (1 << 64) && (id) >= EPOCH_MS rescue ArgumentError, TypeError false end |