3月4日、LogRocket Blogが「Understanding UUIDs in Node.js」と題した記事を公開した。
この記事では、Node.jsにおけるUUID(Universally Unique Identifier)について詳しく紹介されている。以下に、その内容を紹介する。
UUIDとは何か
UUID(Universally Unique Identifier)は、非常に低い重複確率で生成される一意の識別子だ。IETF (Internet Engineering Task Force) がRFC 4122で提案したUUIDの仕様によると、「 空間と時間にわたって一意であることを保証できる128ビット長の識別子 」と定義されている。
今日では、UUIDはデータを一意にラベル付けする際に信頼性が高く便利であるため、多くの開発者に利用されている。IETFはUUIDのさまざまなバージョンを定義しており、現在はバージョン8(v8)が最新の仕様となっている。
UUIDは128ビットの中に複数のセグメントを持っており、それぞれ役割が異なる。例えばv1、v2、v6のUUIDにはMACアドレスを表す48ビットのセグメントがあり、タイムスタンプを表す60ビットのセグメント、そして「クロックシーケンス」と呼ばれる13または14ビットのセグメントが含まれる。一方でv4は乱数値に基づく122ビットのセグメントを持つなど、バージョンによって構造が異なる。
一般的にUUIDは xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx という形式(8-4-4-4-12の区切り)で表現される。ここでxは16進数(4ビット)を指す。例えば以下の例のようになる。
b592d358-adf0-4782-b90e-d8ee4118ddcd
多くのOSにはUUIDを生成するためのCLIツールが用意されている。
uuidgen              // UUIDを生成する
uuidgen --help       // UUIDコマンドのヘルプを表示する
uuidgenコマンドはWindows、Linux、macOSの各環境でv4(ランダム生成)のUUIDを簡単に生成できる。
UUIDの長さと衝突
128ビットのUUIDは、ハイフン(-)を含めれば36文字、含めなければ32文字の文字列として表現される。標準的な形式では32個の16進数を5つのセクションに区切ってハイフンで分割する。
- ハイフンあり: 550e8400-e29b-41d4-a716-446655440000(36文字)
- ハイフンなし: 550e8400e29b41d4a716446655440000(32文字)
UUIDの長さはそのまま一意性にも関係している。128ビットの場合、理論上は約3.4×10^38通り存在するため、衝突する可能性は極めて低い。もし1秒間に10億個のUUIDを生成し続けても、衝突確率が50%に達するまでには86年かかるとされる。
ただし、もし衝突が起きると、データベースのプライマリキーなど「重複があってはならない」場面において深刻な問題を引き起こす。そのため、必要性やアプリケーションの要件に応じて別のID生成手法を検討することも重要だ。
基本的には128ビットで生成されるUUIDだが、用途によっては短く圧縮する手法もある。ただし、短縮化すると一意性や安全性への影響など、トレードオフが発生する点に注意が必要である。
Node.jsでUUIDを生成する方法
Node.jsでUUIDを生成する方法はいくつかある。分散システムやデータベース、あるいは高い一意性が求められるアプリケーションで特によく利用される。本記事では、主に以下の4つの手法を比較している。
- uuidパッケージを使う
- Node.jsに組み込まれているcrypto.randomUUID()メソッドを使う
- Math.random()を使ったカスタムUUID生成
- ハッシュベースのカスタムUUIDなどの代替アプローチ
どの手法がユースケースに合うかを理解しやすくするため、それぞれの特徴を整理している。
1. uuidパッケージを使う
uuidはNode.jsでUUIDを生成する際によく使われる人気のライブラリで、v1からv6まで複数のUUIDバージョンをサポートしている。使い方はシンプルで、暗号学的に安全なUUIDを生成可能だ。
npm install uuid
import { v4 as uuidv4 } from 'uuid';
const uuid = uuidv4();
console.log(uuid); // 例: '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'
uuidパッケージはv1、v3、v4、v5、v6など、さまざまなバージョンに対応しており、UUIDのバリデーションやフォーマットのカスタマイズにも対応しているため、柔軟性が必要な場面で便利である。
- 利点: - v1、v3、v4、v5、v6など複数バージョンをサポート
- Node.jsだけでなくブラウザやReact Nativeなどでも利用可能
- UUIDのバリデーションやバッファへの変換など追加機能も豊富
 
- 欠点: - 外部ライブラリへの依存が発生する
- 組み込みメソッドよりバンドルサイズがやや大きい
 
2. Node.js組み込みのcrypto.randomUUID()を使う
Node.js v14.17.0以降では、crypto.randomUUID()メソッドが追加されており、外部依存なしでUUID(v4)を生成できる。これは暗号学的に安全な乱数に基づいたUUID v4を返す。
import crypto from 'node:crypto';
const uuid = crypto.randomUUID();
console.log(uuid); // 例: '550e8400-e29b-41d4-a716-446655440000'
v4 UUIDのみ生成できるが、依存パッケージが不要でシンプルに利用できるため、「v4で十分」という用途には適している。
- 利点: - 外部ライブラリが不要
- シンプルでわかりやすいAPI
- 暗号学的に安全
 
- 欠点: - v4以外のUUIDを生成できない
- uuidパッケージほどの拡張性はない
 
3. Math.random()によるカスタムUUID生成
乱数によってUUIDを生成するためにMath.random()を利用する方法もあるが、暗号学的に安全ではないため、セキュリティが重要な場面には推奨されない。サンプルコードは以下のとおり。
function generateCustomUUID() {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    const r = Math.random() * 16 | 0;
    const v = c === 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}
const uuid = generateCustomUUID();
console.log(uuid); // 例: 'e4e3f8d0-4b2d-4b5d-9b5d-ab8dfbbd4bed'
衝突リスクが高まるため、本格的な利用には適さない。テストや学習目的など、厳密な安全性が不要な場合のみ選択肢となる。
- 利点: - 外部ライブラリが不要
- 実装がシンプル
 
- 欠点: - 暗号学的に安全ではない
- 他の手法に比べ衝突リスクが高い
 
4. その他のアプローチ
標準的なUUID生成以外にも、より短い文字列が必要な場合や、より高いセキュリティを求める場合など、さまざまなライブラリが利用できる。
a. short-uuidパッケージを使う
short-uuidは、RFC 4122に準拠したv4 UUIDを短い形式に変換できるライブラリだ。URLなどに埋め込みやすい短いIDが必要な場合に便利である。
npm install short-uuid
import short from 'short-uuid';
const translator = short();
const shortId = translator.generate();
console.log(shortId); // 例: '2gYx5fZb'
const originalUUID = translator.toUUID(shortId);
console.log(originalUUID); // 元のUUIDに戻せる
- 利点: - 短く、URLフレンドリーなIDを生成できる
- 短いIDと標準UUIDの相互変換が可能
- カスタムのアルファベット設定などにも対応
 
- 欠点: - v4のUUIDにのみ対応
- 外部依存が増える
 
b. cuid2パッケージを使う
cuid2は、URLフレンドリーかつ安全性の高い一意のIDを生成するライブラリだ。オリジナルのcuidの改良版であり、より強固なセキュリティが特徴となっている。
npm install @paralleldrive/cuid2
import { createId } from '@paralleldrive/cuid2';
const id = createId();
console.log(id); // 例: 'hgyoie0b9qb1fyso09hjyoef'
- 利点: - 安全性が高く衝突リスクが低い
- URLに埋め込みやすい
- ID長や生成オプションをある程度カスタマイズ可能
 
- 欠点: - 連番ではないため、大規模DBでのインデックス性能に影響が出る場合がある
- 外部依存が増える
 
c. nanoidパッケージを使う
nanoidは、軽量かつURLフレンドリーなIDを生成するライブラリだ。IDが短く衝突リスクが低いため、URLや認証コード、DBの主キーなどに利用されることが多い。
npm install nanoid
import { nanoid } from 'nanoid';
const id = nanoid();
console.log(id); // 例: 'e4D3gvwajLcsYhdgOXK6B'
- 利点: - URLフレンドリーかつ短いID
- 衝突が起きにくい
- 非常に軽量
 
- 欠点: - 連番ではないため、大規模DBでのインデックスに影響が出る可能性がある
- 外部依存が増える
 
d. ハッシュベースのカスタムUUID
SHA-256などのハッシュ関数を用いて、入力データに基づき独自にUUIDを作る方法もある。決定論的に一意なIDを生成したい場合に有用だ。
import crypto from 'node:crypto';
function generateHashBasedUUID(input) {
  const hash = crypto.createHash('sha256').update(input).digest('hex');
  return (
    hash.slice(0, 8) + '-' +
    hash.slice(8, 12) + '-' +
    hash.slice(12, 16) + '-' +
    hash.slice(16, 20) + '-' +
    hash.slice(20, 32)
  );
}
const uuid = generateHashBasedUUID('custom-input-data');
console.log(uuid); // 例: '5a105e8b-9d5c-4b2d-9b5d-ab8dfbbd4bed'
- 利点: - 入力データに応じた決定論的なID生成が可能
- 生成プロセスを細かく制御できる
 
- 欠点: - 実装が複雑になる場合がある
- 一意性を担保するために入力データの取り扱いに注意が必要
 
UUID生成方法の比較
| 方法 | 対応バージョン | 暗号学的安全性 | 外部依存 | カスタマイズ性 | 主な用途 | 
|---|---|---|---|---|---|
| uuid npmパッケージ | v1, v3, v4, v5, v6 | あり | あり | 高 | 複数バージョンへの対応、バリデーション | 
| crypto.randomUUID() | v4 | あり | なし | 低 | 組み込み機能、依存なし | 
| Math.random() | カスタム | なし | なし | 中 | 簡易的な用途や学習目的 | 
| short-uuid | v4 | あり | あり | 中 | 短いID、URLフレンドリー | 
| cuid2 | カスタム | あり | あり | 高 | 安全性が高く、URLフレンドリー | 
| nanoid | カスタム | あり | あり | 高 | 短いID、軽量 | 
| ハッシュベースのUUID | カスタム | 実装次第 | なし | 高 | 入力データに基づく決定論的な生成 | 
UUIDをデータベースの主キーに使う際のデメリット
多くの開発者がデータベースのレコードIDとしてUUIDを採用している。しかし、データベースエンジンの動作やUUIDの構造上、全アプリケーションにとって最適とは限らない。以下では、代表的なデメリットについて触れている。
データベースのインデックス
PostgreSQL、MySQL、SQLiteなど、多くのデータベースはB+ツリーインデックスを用いている。UUIDはランダム生成であるため、連番IDと比べるとインデックスの再構築や再配置が頻繁に起きる可能性が高い。大規模な書き込みがある環境では、パフォーマンスの低下につながりやすい。
セキュリティ面の懸念
UUIDにはMACアドレスやタイムスタンプが含まれるバージョンもあり、エンコードされているだけで簡単にデコードできる。この情報が漏れると、レコードの生成時刻やMACアドレスなどを推測される可能性がある。ウェブアプリケーションでUUIDを外部に公開する場合は、cuid2やnanoidのように機密情報が含まれない手法を使うか、そもそもクライアント側から見えない運用をする必要がある。
まとめ
UUIDはNode.jsアプリケーションにおいて広く使われているが、常に最適解であるとは限らない。グローバルに一意なIDを生成できる一方で、大規模な環境でのパフォーマンス面やセキュリティ上のリスクを考慮すると、cuid2やnanoidなど、より安全で衝突しにくいライブラリを検討することも有効だ。
詳細は Understanding UUIDs in Node.js を参照していただきたい。