Snowflake
Discord API が返す場所では、GameVox も Discord の 64 ビット Snowflake 形式で ID を返します。ビット構成、並び順、タイムスタンプ計算はすべて同一なので、既存の Snowflake 解析コードはそのまま動きます。
形式
Discord と同一です。42 ビットのタイムスタンプ + 5 ビットのワーカー + 5 ビットのプロセス + 12 ビットのインクリメントを、符号なし 64 ビット整数にビッグエンディアンで格納します。
┌─ 42 bits ─────────────────┬─ 5 ─┬─ 5 ─┬─ 12 ────────┐
│ GameVox エポックからの ms │ wkr │ prc │ increment │
└───────────────────────────┴─────┴─────┴─────────────┘
63 22 21 17 16 12 11 0 各セグメントは Discord と一致します。
- タイムスタンプ(42 ビット) — GameVox エポックからのミリ秒。下記参照。
- ワーカー ID(5 ビット) — 起動時に Redis のアトミックカウンターで、実行中の api タスクごとに割り当てられます。
- プロセス ID(5 ビット) — ワーカーと組み合わせて一意の発行プールを作ります。
- インクリメント(12 ビット) — (ワーカー, プロセス)ごと、1 ミリ秒あたりの連番。
エポック
GameVox は Discord と同じエポックを使います: 1420070400000(2015-01-01 00:00:00 UTC)。Discord 向けに書かれた Snowflake パーサーは、定数を差し替えることなく GameVox の ID でも動きます。
const SNOWFLAKE_EPOCH = 1420070400000n; ワイヤーフォーマット
REST / ゲートウェイ上のすべての Snowflake は、数値ではなく JSON 文字列としてシリアライズされます。JavaScript は Number.MAX_SAFE_INTEGER(253-1)を超えると精度を失い、Snowflake はそれを日常的に超えます。計算が必要なときは必ず BigInt で解析し、それ以外では文字列のまま保存してください。
// 正しい例
const id = BigInt(message.id);
// 誤った例 — 精度が静かに失われます
const id = Number(message.id); タイムスタンプを取り出す
JavaScript
function snowflakeToDate(snowflake) {
const EPOCH = 1420070400000n; // Discord と同じ
const ms = Number((BigInt(snowflake) >> 22n) + EPOCH);
return new Date(ms);
}
console.log(snowflakeToDate('182955831900192769'));
// → Date オブジェクト — この Snowflake が発行された時刻 Python
from datetime import datetime, timezone
SNOWFLAKE_EPOCH = 1420070400000
def snowflake_to_dt(snowflake):
ms = (int(snowflake) >> 22) + SNOWFLAKE_EPOCH
return datetime.fromtimestamp(ms / 1000, tz=timezone.utc) Java
long SNOWFLAKE_EPOCH = 1420070400000L;
long ms = (Long.parseUnsignedLong(snowflake) >>> 22) + SNOWFLAKE_EPOCH;
Instant when = Instant.ofEpochMilli(ms); 並び順
タイムスタンプが上位ビットにあるため、2 つの Snowflake を文字列として辞書順に比較すると時系列に並びます。Discord と同じです。だからこそ、メッセージエンドポイントの before / after / around によるページングが、明示的なタイムスタンプ項目なしで機能します。
// メッセージ配列で、古い順 → 新しい順:
messages.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0); ネイティブデータとエイリアステーブル
内部では、GameVox のネイティブなオブジェクト(サーバー、チャンネル、ユーザー、メッセージなど)は UUIDv4 を使います。ボット互換レイヤーは、オブジェクトが初めてボットに公開されたときに安定した Snowflake を発行し、その対応を snowflake_aliases テーブルに保存します。以後そのオブジェクトを参照するとき(REST でもゲートウェイでも)は、同じ Snowflake が使われます。
メッセージについては、メッセージの created_at を元にした Snowflake を遡って埋めているため、ボットが初めてチャンネルに参加したときも過去のメッセージが正しく並びます。それ以外は初回公開時に NOW() を使って遅延生成されるので、Snowflake はボットがそのオブジェクトを見た時刻を表し、必ずしも作成時刻ではありません。
シャーディングのハッシュ
Discord の標準的なシャード計算式がそのまま使えます。
shardId = (BigInt(guildId) >> 22n) % BigInt(numShards); サーバー数 2,500 未満のボットは単一シャード([0, 1])で動くため、この点を気にする必要はありません。より大きなボットでも、上位 42 ビットがランダムではなく時間的に単調増加であるため、Discord と同程度に均等なハッシュ分布が得られます。
注意点
- Snowflake を数値リテラルと
==で比較しないでください。 ワイヤー上では文字列です。文字列と比較するか、両方をBigIntに変換してください。 - 42 ビットのタイムスタンプはエポックから約 139 年で一巡します。 GameVox の Snowflake は 2165 年まで有効です。
- サーバー / チャンネル / ロールの作成時刻を Snowflake から推測しないでください。 ボットプラットフォームより前から存在するネイティブオブジェクトは、作成時ではなく初めてボットに公開された時点で Snowflake を得ています。メッセージだけは例外です(
created_atで遡って設定)。