仕組みから学ぶ Web
応用読了目安 15#ID#UUID#ULID#主キー#連番

ID の設計 — 連番を外に出した日から、数えられてしまう

注文番号を連番にしていた。競合が毎日1件注文して番号の差を見れば、1日の注文数が分かる。URL に出す ID は、内部の事情を外に漏らす。

この記事の進み方

What — ID に求めるものは、場所によって違う

ID は1種類ではありません。同じ値を「主キー」と「URL に出すもの」の両方に使うと、要件が衝突します。

CompareID の方式

1, 2, 3 と増える。DB が採番する。

1
2
3
...
48213   ← いま何件目かが分かる
大きさ
8 バイト
並び順
作成順になる
推測されにくさ
推測できる
件数の秘匿
できない
事前に決められるか
できない(DB 任せ)
  • インデックスが最も効率的。末尾に追記されるので断片化しにくい。
  • 外に出すと件数と成長速度が漏れる。冒頭の事故。
  • DB を分割すると採番が衝突する。

どれが優れているかではなく、何に使うかで選ぶ。主キーに向く性質と、外に出すのに向く性質は別物。

主キー
行を一意に指す値。DB の内部でインデックスの中心になる。
サロゲートキー
業務上の意味を持たない、採番だけのための ID。連番や UUID。
ナチュラルキー
メールアドレスや商品コードのように、業務上の意味を持つ値を鍵にすること。
公開 ID
URL や画面に出す ID。内部の主キーとは別に持つことがある。
ULID
先頭に時刻、後ろに乱数を置いた 128 ビットの ID。生成順に並ぶ。

「主キー」と「外に出す ID」は分けられる

同じ値を両方に使う必要はありません。

orders
  id          BIGINT      主キー(連番、内部だけで使う)
  public_id   CHAR(26)    URL に出す(ULID)

こうすると、インデックスの効率(連番)と推測されにくさ(ULID)を両方取れます。

代償は、テーブルに1列増えることと、外向きの処理で public_id を引くためのインデックスが要ることです。

小さいシステムでは過剰になることもあります。最初から ULID を主キーにしてしまうのも、十分に現実的な選択です。

Why — ID が漏らすもの、ID が縛るもの

連番は「順序」と「量」を漏らす

連番には、値そのもの以外の情報が入っています。

  • その時点で何件あるか(値そのもの)
  • どれくらいの速度で増えているか(2回観測すれば分かる)
  • 誰が先か(顧客 ID が小さければ古参)

冒頭の事故は1つ目と2つ目です。外に出した瞬間に、隠していない情報になります。

URL に /invoices/1 と出れば、「このサービスの請求書は1件目」と分かります。創業直後であることが、見せたくない相手にも分かります。

推測できる ID は、権限の不備を増幅する

連番そのものは脆弱性ではありません。 冒頭の事故の本当の原因は、権限チェックの不足です(security/authorization)。

しかし連番は、その不備を「誰でも簡単に突ける」形に変えます

  • UUID なら、他人の ID を当てるのは実質不可能
  • 連番なら、1ずつ減らすだけで全件たどれる

権限を正しく実装するのが本筋です。そのうえで、推測しにくい ID は間違えたときの被害を小さくします。どちらか一方ではなく、両方です。

ランダムな主キーは、インデックスを断片化させる

UUID v4 を主キーにすると、挿入位置がばらばらになります。

Figure挿入位置の違い
  1. 1連番
  2. 2UUID v4
  3. 3ULID / v7
1/3
連番新しい行は常にインデックスの末尾に追加される。既存のページを動かさない

連番は常に末尾。ランダムはどこにでも入る。どこにでも入ると、ページの分割が頻繁に起きる。

影響の大きさは DB と規模によります。数万行なら誤差ですが、数千万行で書き込みが多いテーブルでは効いてきます。

「UUID は遅い」と単純化しないこと。 遅くなるのは「ランダムな値を、クラスタ化されたインデックスの主キーにしたとき」です。条件が揃わなければ問題になりません。

演習 — まず自分で判断する

確認 — ここまで読めたか

請求書の URL が `/invoices/1024` です。この ID から外部の人が推測できることは何ですか?

まず選ぶ(解答例は a〜d の記号で説明します)

確認 — ここまで読めたか

新規サービスで、注文テーブルの主キーを決めます。書き込みは1日数千件、URL にも ID を出します。何を選びますか?

まず選ぶ(解答例は a〜d の記号で説明します)

読み終わりましたか?

読了にすると、これを前提とする記事がロードマップで開放されます。