代表的な面接トピック

分散型ユニークIDジェネレータをどのように設計しますか?

システム設計難しい
Offer.cc 編集チーム公開日 更新日

質問

4つのリージョンにまたがる分散型ユニークIDジェネレータを設計してください。IDは正の64ビット整数であり、グローバルにユニークで、概ね時間順にソート可能である必要があります。システムは800台のジェネレータワーカーと毎秒200万件のID生成をサポートし、クロックロールバックやワーカーの入れ替え時にも重複を発生させてはなりません。

問題の定義と適用場面

4つのリージョンにまたがる分散型ユニークIDジェネレータを設計します。各リージョンには最大200台のジェネレータワーカーが存在します。クラスタ全体でピーク時に毎秒200万件のIDを処理し、任意の1台のワーカーは単一のミリ秒内に最大3,000件のIDを生成する可能性があります。各IDは正の64ビット整数であり、グローバルにユニークで、生成時刻順に概ね並び、少なくとも60年間使用可能である必要があります。ホットパスではIDごとにネットワークリクエストを行うことはできません。レイテンシ目標はp99で5ミリ秒以下、可用性目標は99.99%ですが、重複のリスクを冒すくらいならジェネレータは停止しなければなりません。

IDの欠番(ギャップ)は許容され、リージョン間にまたがる厳密な単調増加順序は不要です。IDは内部メッセージ識別子およびデータベースの主キーであるため、現時点では予測可能性があっても許容されます。識別子が信頼できないクライアントに公開される場合は、別途対処が必要です。これらのキャパシティとSLOは面接用の制約条件であり、実企業のプロダクショントラフィックに関する主張ではありません。

この設問は、シニアバックエンド、インフラストラクチャ、システムデザインの面接に適しています。真の課題は、耐用年数、ワーカー数、ミリ秒あたりのバースト要件をビット配分に落とし込み、クロック、並行性、ワーカーアイデンティティの障害によって重複が発生しないことを証明することです。単に「Snowflakeを使う」と答えるだけでは設計とは言えません。

面接官が評価しているポイント

第1に、候補者がユニーク性、順序性、連続性、予測不可能性を明確に区別できるかです。Snowflakeはユニークで概ね時間順に並んだ64ビット整数を提供できますが、欠番のないシーケンス、リージョン間の因果順序、セキュリティトークンを自動的に提供するわけではありません。要件が異なれば設計も変わります。

第2に、候補者が制約からレイアウトを導き出せるかです。優れた回答では、41ビットのタイムスタンプ、10ビットのワーカーID、12ビットのシーケンスがカバーする範囲を算出し、60年の耐用年数、800ワーカー、ワーカーあたりミリ秒間3,000 IDという条件を満たしていることを確認します。定番のビット配分をそのままコピーして後付けで理由付けをするようなことはしません。

第3に、候補者がIDが重複しないことを証明できるかです。正しさを保証するには、重複する任意の時間範囲内でワーカーコードのアクティブな所有者が常に1つであること、そのワーカーがミリ秒内でシーケンスを再利用しないこと、以前使用したタイムスタンプとシーケンスの範囲に再突入するようなクロックロールバックや再起動が発生しないことが求められます。

第4に、候補者が障害時の設計を網羅できているかです。シーケンスの枯渇、クロックの後退、リースの喪失、プロセスのゾンビ化(予期せぬ復活)、リージョン分離、タイムスタンプの枯渇は、いずれもユニーク性や可用性を脅かす可能性があります。回答には明確な停止条件、アラート、復旧手順が必要です。

第5に、候補者が制約に基づいてSnowflake、UUIDv7、ランダムUUID、集中型またはセグメント割り当ての中から適切なものを選択できるかです。本問では64ビット要件とIDごとの調停(coordination)を行わないという条件からSnowflakeが有利になります。128ビットが許容され、ワーカー登録を避けたいのであれば、UUIDv7の方がシンプルな候補となります。

回答前に確認すべき明確化のための質問

  • ユニーク性は決定論的である必要がありますか、それとも極めて微小な衝突確率は許容されますか? 決定論的なユニーク性を担保するには、分離されたワーカー名前空間が必要です。確率的なユニーク性で十分な場合は、UUIDv4やUUIDv7を採用することでワーカーの割り当て管理を排除できます。
  • 識別子は64ビットでなければなりませんか? データベース、プロトコル、インデックスが128ビットを許容できる場合、標準のUUIDv7を使用すれば、タイムスタンプのプレフィックスとランダム空間を組み合わせることで、よりシンプルなコントロールプレーンで済みます。本問題では正の64ビット整数に固定されています。
  • 「順序付け」の要件はどの程度厳密ですか? 大まかな時間順であればローカルクロックで十分です。リージョン間にまたがる厳密な全順序を実現するには、集中型シーケンサー、コンセンサスログ、またはビジネスパーティション単位のシーケンスが必要となり、コストが大きく異なります。
  • 欠番(ギャップ)は許容されますか? セグメントの事前割り当て、クラッシュ、リトライによって欠番が発生する可能性があります。欠番なしと厳密なグローバル単調増加を両立させるには、割り当てをシリアルなトランザクションパスに組み込む必要があります。本問では欠番は許容されます。
  • IDは一般公開されますか? タイムスタンプとワーカービットにより生成時刻やデプロイ情報が露呈する可能性があり、また単調増加する値は列挙(エニュメレーション)されるリスクがあります。内部キーであれば許容されるかもしれませんが、パブリックな識別子には別途不透明な(opaque)値を使用すべきです。
  • ワーカーはどのように作成および置換されますか? 固定マシン、オーケストレーションされたコンテナ、オートスケーリングでは、ワーカーアイデンティティの再利用に関するリスクが異なります。動的なワーカーにはリース、フェンシング、安全な再利用ルールが必要です。
  • ネットワーク分断時は何を優先しますか? 既存のワーカーは、リージョナルコントロールプレーンが正常でリースが安全に維持されている限り処理を継続できます。排他的な所有権を証明できない場合、可用性を多少犠牲にしてでもワーカーは停止しなければなりません。

30秒での回答

「64ビットを、固定の符号ビット1ビット、ミリ秒時刻41ビット、リージョンおよびワーカー用10ビット、ミリ秒内シーケンス12ビットに分割します。これにより約69.7年間、1,024個のワーカーコード、ワーカーあたりミリ秒間4,096 IDをカバーでき、制約を満たします。各ワーカーはローカルのクリティカルセクション内で(timestamp, worker, sequence)を生成し、ホットパス上でのネットワーク呼び出しは行いません。リージョナルコントロールプレーンがワーカーのリースを割り当ておよび更新します。ワーカーはシーケンス枯渇時にラップアラウンド(ゼロクリア)せず、リースの喪失や危険なクロックロールバック時には停止します。結果として大まかな時間順序は保たれますが、リージョン間にまたがる厳密な順序付けはされません。128ビットが許容されワーカー登録を廃止したい場合は、UUIDv7を検討します。」

ステップごとの詳細解説

ステップ 1: 制約を利用して不適切なデフォルト案を排除する

集中型データベースシーケンスは明確な順序を提供しますが、割り当てのたびに共有書き込みパスを経由することになります。これは、ホットパスでネットワークを使用しないという要件やリージョン分離の要件と矛盾します。レンジをバッチで割り当てる方式は調停コストを償却できますが、ワーカーがクラッシュした際に欠番が生じます。欠番は許容されているためセグメント割り当ては有効な代替案ですが、依然としてレンジ管理サービスとプリフェッチポリシーが必要です。

UUIDv4とUUIDv7はいずれも128ビット形式です。UUIDv4は完全ランダムであり、時間順序を提供しません。RFC 9562に基づくUUIDv7は、最上位部分に48ビットのUnixミリ秒タイムスタンプを配置し、残りのスペースをバージョン、バリアント、ランダムまたは単調増加フィールドに使用します。ワーカー登録なしで概ね順序付けられた値を生成できますが、本問の64ビット要件に違反します。

したがって、Snowflake型の64ビットレイアウトが残ります。「調停不要(Coordination-free)」というのはホットパス上の各割り当てについてのみ当てはまる表現であり、ワーカーのアイデンティティ管理には依然としてコントロールプレーンが必要です。そのコストを隠蔽しても、システムから調停が完全になくなるわけではありません。

ステップ 2: キャパシティから 1 + 41 + 10 + 12 レイアウトを導出する

値が正の符号付き64ビット整数となるよう最上位ビットを0として予約し、残りの63ビットを次のように割り当てます。

フィールドビット数範囲要件への適合性
符号10固定BIGINT を正の値に維持
カスタムエポックからの経過ミリ秒412^41 ミリ秒(約69.7年)60年の耐用年数を超える
リージョン + ワーカー101,024 コードリージョン2ビット × ローカルワーカー8ビットで 4 × 256 ワーカーをサポート
ミリ秒内のシーケンス120〜4,095(4,096値)ワーカーあたりミリ秒間3,000 IDの要件を超える

合計は 1 + 41 + 10 + 12 = 64 ビットとなります。クラスタ全体で最大 4 × 200 = 800 台のワーカーが存在しますが、これは1,024未満であり、リージョンあたり200台のワーカーは8ビットが提供する256値の中に収まります。41ビットの時間範囲は約 2^41 ÷ 1000 ÷ 60 ÷ 60 ÷ 24 ÷ 365.2425 = 69.7 年です。「正の整数」という要件を厳密に満たすため、オールゼロのIDを予約し、最初の割り当てより前の時刻をカスタムエポックとして設定します。これにより、最初のワーカーのシーケンス0であっても 0 が返されることはなくなります。

クラスタ全体で毎秒200万IDというレートは、全体のキャパシティ確認用です。シーケンスフィールドは、より厳しい「ワーカーごと、ミリ秒ごと」のバースト要件を満たす必要があります。秒あたりの平均計算だけでは12ビットが十分であることの証明にはなりません。全体のQPSが低くても、単一のワーカーがシーケンスを枯渇させる可能性があるからです。

ステップ 3: ローカル生成の実装とユニーク性の証明

各ワーカーは64ビット整数演算を使用し、ロックまたはアトミックなクリティカルセクションで lastMssequence を保護します。

text
nextId():
  lock
  now = wallClockMs() - customEpochMs
  if now < lastMs:
    fail("clock_moved_back")
  if now == lastMs:
    if sequence == 4095:
      now = waitUntilAfter(lastMs)
      sequence = 0
    else:
      sequence = sequence + 1
  else:
    sequence = 0
  lastMs = now
  return (now << 22) | (workerCode << 12) | sequence

クロックが同一ミリ秒内にある間、シーケンスが4,095に達した後に警告なくゼロへラップアラウンドしてはなりません。それを行うと過去の値と即座に重複します。シーケンスはゼロから始まるため、4,096番目のIDまでは有効です。同一ミリ秒内の4,097番目のリクエストは、次のミリ秒まで待機するか、過負荷エラーを受け取る必要があります。

ユニーク性の証明には3つのケースがあります。異なるワーカーは異なる10ビットのワーカーコードを持ちます。同一ワーカーの異なるミリ秒では41ビットのタイムスタンプが異なります。同一ワーカーの同一ミリ秒では12ビットのシーケンス値が異なります。各フィールドが範囲内に収まり、ワーカーの所有権が重複せず、クロックが使用済み状態に再突入しない限り、63ビットのペイロード全体が重複することはありません。

ステップ 4: コントロールプレーンによるワーカーアイデンティティの管理

デプロイ設定によってリージョンの2ビットコードを固定した上で、各リージョン内で独立したワーカーリースアロケータを実行します。コントロールプレーンは以下を保持します。

フィールド目的
region_id, worker_id10ビットのグローバルワーカーコードを構成
owner_id現在のプロセスまたはデプロイインスタンスを識別
fencing_token1つのワーカーコードの新旧の所有者を区別
lease_expires_at所有権が有効である期間の境界を定義
timestamp_ceiling_ms安全な再利用時に旧所有者が使用可能なタイムスタンプ範囲の上限を定義

起動時、ノードはワーカーIDとリースを取得します。ID生成のホットパスでは、所有者状態、フェンシング状態、インメモリのリース安全期限、および now <= timestamp_ceiling_ms のみをチェックします。更新は非同期で行われるため、割り当てごとにコントロールプレーンへアクセスすることはありません。ノードは、リースが安全でなくなった時点、またはタイムスタンプ上限に達した時点で即座に生成を停止します。そのワーカーIDを置換ノードに再割り当てする前に、コントロールプレーンは旧所有者をフェンシングし、新ノードのクロックが旧リースの timestamp_ceiling_ms を超えるまで待機しなければなりません。これにより、応答が遅延した旧ノードとその置換ノードが同じタイムスタンプ範囲を使用することを防止できます。

フェンシングトークンは最終的なIDにはエンコードされないため、衝突が発生した後にそれを修復することはできません。フェンシングトークンの役割は、古い所有者をID生成パスから締め出すことです。プラットフォームが古いプロセスを確実にフェンシングできない場合は、リースだけでプロセスのゾンビ化問題を解決できると思い込まず、デプロイスロットに長寿命で再利用されないワーカーIDを静的に割り当てるか、UUIDv7を採用すべきです。

ステップ 5: クロック後退、オーバーフロー、ネットワーク分断時の停止条件を定義する

now < lastMs に対するポリシーは有界(bounded)でなければなりません。一例として、リクエストのレイテンシ予算に十分な余裕がある場合に限り、最大5ミリ秒までのロールバックを待機します。この待機時間は5ミリ秒のp99予算としてカウントされます。ロールバック幅が大きい場合や予算が不足している場合は、リトライ可能なエラーを返し、ノードをサービスから切り離してアラートを発報します。プロセス起動時には、現在時刻とそのワーカーの永続化された高水位標(high-water mark)を比較し、クロックが遅れている場合は起動を拒否します。lastMs をメモリ内のみに保持していると、再起動時の問題をカバーできません。

シーケンスが枯渇した場合は、次のミリ秒まで待機し、sequence_exhausted メトリクスをインクリメントします。枯渇が頻発する場合は、負荷が偏っているかビット配分が不適切であることを意味します。トラフィックを分散させるか、ワーカーを追加するか、新しいフォーマットでシーケンスビットを増やしてください。決してラップアラウンドさせてはなりません。

リージョン間の分断時やグローバルネットワークからの隔離時でも、2ビットのリージョンフィールドによって名前空間は分離されたままです。既存のワーカーは、リージョナルリースが安全である限りローカルで処理を継続できます。リージョナルリースサービスが利用できなくなり、安全期限が切れた場合、それらのワーカーは停止します。99.99%の可用性目標よりもユニーク性が優先され、この例外動作はSLOおよびアラートに明記される必要があります。

41ビットのタイムスタンプはいずれ枯渇します。エポックの残り寿命をメトリクスとして公開し、数年前から移行を開始してください。オーバーフロー後にタイムスタンプをリセットしたり、同じ64ビットカラムを新しいレイアウトで暗黙的に再解釈したりしてはなりません。リージョンの追加やワーカー数の拡張にも、同様のフォーマット移行が必要です。

ステップ 6: 「順序付け」と「セキュア」が実際に意味するものを明記する

上位ビットに時刻を配置することでIDは概ね時間順になりますが、クロックスキューによって後のレコードの方が小さなIDを持つ場合があります。また、ワーカービットも同一ミリ秒内の順序に影響します。Snowflake IDはリージョン間の因果関係を証明するものではなく、決済台帳のコミットシーケンスの代用にはなりません。

APIが id > cursor を用いた増分同期を行う場合、遅れたクロックを持つノードから遅れて到着したIDは保存されたカーソルよりも小さくなり、永久にスキップされてしまう可能性があります。完全な順序付けが重要な場合は、データベースのコミットシーケンス、コンセンサスログの位置、または会話やアカウントなどのビジネスパーティションにスコープされたシーケンサーを使用してください。Snowflakeは識別子であり、順序の決定権を持つものではありません。

また、生のIDは生成時刻を大まかに露呈し、リージョンやワーカーのビットを明らかにする可能性があります。これは認可クレデンシャルではありません。公開リソースでは64ビットの内部キーを保持しつつ、独立した不透明な識別子を外部に公開すべきです。データを保護するためにIDの推測困難性に依存してはなりません。

ステップ 7: 同一の制約下で代替案を比較する

アプローチビット幅と順序性調停(Coordination)最適なユースケース主なコスト
Snowflake型64ビット、概ね時間順起動時および更新時に調停。ホットパスはローカル64ビットID、極めて高いスループット、大まかな順序クロックおよびワーカーの管理
UUIDv7128ビット、時間プレフィックス付きワーカー登録不要128ビットが許容され、ワーカーコントロールプレーンのない標準形式が望ましい場合値のサイズ増大。ユニーク性は乱数の品質と実装に依存
UUIDv4128ビット、ランダム順なし順序性が不要で不透明性が重要な場合インデックスの局所性悪化、IDからの時間推測不可
集中型シーケンサー / セグメント通常64ビット、厳密または概ね単調増加サービスまたはDB経由で割り当て。セグメントはバッチ取得可能中央での順序付けが必須、または既にDBに依存しているシステムネットワーク/DBへの依存。バッチ処理により欠番が発生

厳密な64ビット制約とIDごとのネットワーク呼び出し禁止という条件から、本問題ではSnowflakeが最適です。面接官が64ビット制約を外した場合は、UUIDv7を再検討してください。厳密な全順序が必要な場合は、Snowflakeでは対応できないことを認め、間違ったプリミティブを改変するのではなくシリアライズされたシーケンサーへ移行してください。

ステップ 8: 障害注入による検証

検証は、重複のない大規模な通常サンプルを生成するだけでは不十分です。少なくとも以下のケースを網羅する必要があります。

  1. 時刻を固定し、単一のワーカーから1ミリ秒内に4,096個のIDを生成します。すべてがユニークであり、4,097番目は待機するか失敗することを確認します。
  2. 1台のジェネレータを並行して呼び出し、ローカルのクリティカルセクションによって2つのスレッドが同一シーケンスを再利用しないことを検証します。
  3. クロックを1ミリ秒、5ミリ秒、2,000ミリ秒巻き戻し、待機、タイムアウト、サービス除外、およびアラートのパスを検証します。
  4. 旧所有者を一時停止させ、リースを失効させ、新所有者をアクティブにした後、旧所有者を再開します。フェンシングによって古いプロセスからの生成が阻止されることを検証します。
  5. 4つのリージョンと800台の全ワーカーを同一ミリ秒で固定し、異なるシーケンスを生成して、デコードされたフィールド範囲とグローバルなユニーク性を検証します。
  6. リージョナルリースサービスを分断・隔離します。既存のワーカーは安全なリース期間内であれば処理を継続でき、その後はフェイルクローズ(安全に停止)することを検証します。
  7. 時刻を 2^41 - 1 まで進め、次のミリ秒が拒否され移行アラートがトリガーされることを検証します。

プロダクション監視では、最低でも clock_rollback_mssequence_exhausted、リース更新失敗、利用可能なワーカーコードの空き容量、生成レイテンシ、エラーレート、エポックの残り寿命をカバーすべきです。データベースのユニーク制約は有用な最終防衛ラインおよびアラートソースになりますが、衝突をキャッチしてリトライすることはジェネレータ自体の正しさの代用にはなりません。

質の高い模範解答

「まず、要件が決定論的にユニークな正の64ビットIDであることを確認します。欠番は許容され、順序付けは概ね時間順であれば問題ありません。4つのリージョン、リージョンあたり200ワーカー、ワーカーあたりミリ秒間最大3,000 IDという条件から、128ビットのUUIDv7やIDごとに集中型シーケンサーを呼び出す方式ではなく、Snowflake型の設計を選択します。

最上位ビットは0のまま固定します。カスタムエポックを用いた41ビットのミリ秒タイムスタンプは約69.7年間有効です。10ビットのワーカービットは2ビットのリージョンコードと8ビットのローカルワーカーコードに分割し、4 × 256ノードをカバーします。12ビットのシーケンスにより、ワーカーあたりミリ秒間に4,096個の値を生成できます。これら3つのディメンションはすべて提示された制約を満たしており、63ビットのペイロードを過不足なく使い切っています。

各ワーカーはローカルのクリティカルセクション内で lastMssequence を保持します。時間が進むとシーケンスをリセットし、同一ミリ秒内ではインクリメントし、シーケンス4,095に達した後は待機します。クロックの後退時にそのまま処理を継続することは決してありません。5ミリ秒以内のロールバックであればレイテンシ予算内で待機しますが、それを超えるロールバックの場合はワーカーを切り離してアラートを発報します。異なるワーカーコードによりワーカー間が分離され、タイムスタンプにより同一ワーカーの異なるミリ秒が分離され、シーケンスにより同一ミリ秒内のIDが分離されます。

リージョンごとにワーカーリースのコントロールプレーンを運用します。ノードは起動時および非同期更新時にのみこれにアクセスし、ID生成自体はローカルで完結します。古い所有者はリースを失うと停止します。そのワーカーIDを再利用する前に、アロケータは旧所有者をフェンシングし、置換ノードのクロックが旧リースのタイムスタンプ上限を超えていることを確認します。排他的な所有権を証明できない場合、ワーカーは停止します。

最後に、これが提供するのは大まかな順序のみである点を明記します。リージョン間にまたがる厳密な順序にはコンセンサスログやビジネスパーティションごとのシーケンサーが必要であり、公開用の推測不可能なIDには別途不透明な値が必要です。テストにおいては正常系だけでなく、同一ミリ秒内の4,097番目のリクエスト、並行シーケンスアクセス、2秒のクロックロールバック、リースの引き継ぎ、エポック枯渇の検証も含めます。」

よくある落とし穴

  • 即座に「UUIDを使う」と答える → ビット幅、順序性、衝突セマンティクスが曖昧なままになる → まずUUIDv4、UUIDv7、決定論的ワーカー名前空間を明確に区別すること。
  • 41-10-12のビット構成を盲目的にコピーする → 耐用年数、ワーカー数、バーストキャパシティを満たす証明がなされていない → 2^41 ミリ秒、2^10 個のワーカーコード、2^12 個のシーケンス値をそれぞれ算出すること。
  • クラスタ全体の平均QPSのみで計算する → ワーカー単位・ミリ秒単位のバーストでシーケンスが枯渇する可能性がある → 最小時間単位かつ最も負荷の高いワーカーでキャパシティを検証すること。
  • マスク演算でシーケンスをラップアラウンドさせる → 同一ワーカーが同一ミリ秒内に同じIDを再度生成してしまう → 次のミリ秒まで待機するか、バックプレッシャーをかけるか、エラーを返すこと。
  • ワーカーIDを設定ファイルに固定して終わる → オートスケーリング、設定の複製、プロセスのゾンビ化により所有者が重複する → リース、フェンシング、安全な再利用、停止条件を設計すること。
  • ロールバック後に現在のウォールクロックで処理を継続する → タイムスタンプとシーケンスの組み合わせが重複する恐れがある → 制限時間内で待機するかフェイルクローズし、再起動後は永続化された高水位標を確認すること。
  • 大まかな順序を「グローバルに単調増加」と呼ぶ → クロックスキューや並行ワーカーによって順序が前後する → 厳密な順序が必要な場合はシリアライズされたログやパーティション化されたシーケンサーを使用すること。
  • IDをアクセス制御の手段として扱う → デコード可能または推測可能なIDはリクエストの認可を保証しない → 独立した不透明な識別子を公開し、サーバーサイドでの認可を必ず実施すること。
  • 何百万件もの正常なID生成しかテストしない → 通常のランダム負荷では危険な境界条件に達することは稀である → 時刻を固定し、ロールバック、リースの奪取、フィールド枯渇を注入してテストすること。

フォローアップ質問と回答例

フォローアップ 1: IDが欠番なしでリージョン間にまたがり厳密に単調増加しなければならない場合、何が変わりますか?

Snowflakeは適用外となります。厳密な全順序を実現するには、コンセンサスログ内の単一シーケンス状態など、すべての割り当てが単一の線形化ポイント(linearization point)を通過する必要があります。また欠番を許容しないということは、ビジネス処理のトランザクションが成功してコミットするのと同時に番号が確定しなければならないため、未使用のプリフェッチされたセグメントは許容されません。ネットワーク分断が発生した場合、少数派(マイノリティ)側は停止せざるを得ず、スループットと可用性が低下し、レイテンシが増加します。ビジネス上、本当に欠番なしの性質が必要なのかを確認してください。多くの監査システムで必要なのはイミュータブルなビジネス参照コードであり、欠番のないDB主キーではありません。

フォローアップ 2: 5つ目のリージョンを追加する場合や、リージョンあたり300台のワーカーをサポートする場合はどうしますか?

現在のリージョン2ビット・ローカルワーカー8ビットの構成ではどちらも表現できません。システム稼働前であれば、リージョンに1ビット追加してローカルワーカーの容量を減らすなど、10ビットの配分を調整できます。しかし一度IDが生成されて運用が始まると、オンライン上で古い値を新しいビット境界で解釈し直すことはできません。明示的な新フォーマットを導入するか、128ビット識別子へ移行し、読み取り側と書き込み側の双方が両方のバージョンを識別できるようにします。ビット境界を暗黙的に変更すると、デコード、順序性、ユニーク性の前提が崩壊します。

フォローアップ 3: ノードのクロックが2秒巻き戻りました。シーケンスビットを使って可用性を維持できますか?

状態を永続化しないアドホックな回避策では安全に維持できません。再起動後、プロセスはどの論理タイムスタンプを前借りしたかを忘れてしまう可能性があります。定義されたポリシーに従い、即座にノードを切り離し、アラートを発報してトラフィックを他へ逃がします。ウォールタイムが lastMs に追いつくか、永続化された高水位標から検証済みの論理時刻を復旧した後にのみノードを復帰させます。ロールバック中も可用性を維持することが必須要件である場合は、永続化された論理クロックの設計を明示的に行い、オーバーフロー、再起動、順序性の挙動を再証明する必要があります。「論理クロックを使う」と一言で済ませるのは、困難な状態復旧の問題を無視しています。

フォローアップ 4: IDが公開の注文URLに表示されます。列挙や売上件数の漏洩をどのように防ぎますか?

Snowflake IDは内部結合用のキーとして保持し、外部向けには独立したランダムな識別子を生成します。十分なエントロピーと不透明性が求められる場合はUUIDv4が適しており、時間プレフィックスの漏洩が許容される場合はUUIDv7も選択肢になります。そのマッピングを注文レコードに保存します。識別子の形式にかかわらず、注文データの読み取り時には常に現在のユーザーに対する認可チェックを実施しなければなりません。予測不可能性は列挙リスクを低減しますが、認可の代わりにはなりません。

フォローアップ 5: リージョンがグローバルコントロールプレーンとの通信を失った場合でもID生成を継続できますか?

はい、リージョンビットによって名前空間が分離されており、割り当て処理にグローバルコントロールプレーンは不要だからです。既存のワーカーは、リージョナルリースが安全な期間内にある限りローカルで処理を継続します。リージョナルリースアロケータまでもが利用不能になり、更新期限が切れた場合、ワーカーは停止します。リージョナルリースストア自体は可用性のためにリージョン内コンセンサスクラスタを使用できますが、分断された2つのパーティションが同一のワーカーコードを並行して更新することは絶対に防がなければなりません。

フォローアップ 6: 1台のワーカーが突発的にミリ秒あたり5,000件のIDを必要とした場合はどうなりますか?

12ビットのシーケンスは4,096個の値しか提供できません。即時の対応策はバックプレッシャーをかけ、より多くのワーカーへ負荷を分散することです(ラップアラウンドは禁止です)。長期的には、タイムスタンプの耐用年数やワーカー容量を削ってシーケンスビットを増やした新フォーマットを導入するか、64ビット制約を撤廃してUUIDv7を採用します。レイアウトを変更する前に、実際の1ミリ秒あたりのバースト分布を測定してください。秒あたりの平均値はこの問題の解決には役立ちません。

フォローアップ 7: 厳密な順序が同一会話(conversation)内でのみ求められる場合、グローバルシーケンサーは必要ですか?

不要です。各会話を固定のパーティションにマッピングし、そのパーティション内でコミットシーケンスを維持します。Snowflakeはグローバルな識別子として機能し、パーティションシーケンスが会話内の順序を担います。これにより、すべてのリージョンをシリアライズする場合に比べて調停コストと障害ドメインを大幅に削減できます。読み取り側は (conversation_id, sequence) でソートを行い、Snowflake IDが会話のコミット順序と一致していると見なしてはなりません。

公開情報ソース

関連する質問

関連面接ツール

システム設計の回答には「回答する」を使用

まず要件を明確にし、スケール、アーキテクチャ、コンポーネント選定、トレードオフの順に進めます。

ツールを見る