現代のWebアプリケーションにおけるプライマリキーの選択を巡る議論
Lukas Schneider
DevOps Engineer · Leapcell

はじめに
現代のWebアプリケーション開発の進化し続ける状況において、アーキテクトや開発者の間でしばしば大きな議論を巻き起こす基本的な決定事項が一つあります。それはプライマリキー戦略の選択です。ユニバーサル一意識別子(UUID)、大きな整数(BIGINT)、あるいはメールアドレスのような意味のある自然キーを使用するという、この一見単純な選択は、アプリケーションのスケーラビリティ、パフォーマンス、データ整合性、さらにはチームのワークフローにさえ profondamente 影響を与える可能性があります。アプリケーションが複雑化し、分散システム全体にスケールし、堅牢なデータモデルを要求するにつれて、各アプローチのニュアンスを理解することがcriticalになります。本稿では、UUID、BIGINT、自然キーの長所と短所を分析し、開発者が最新のWebプロジェクトのために情報に基づいた意思決定を行えるよう、この議論の中心に迫ります。
主要な用語
比較分析に入る前に、この議論の中心となる主要な用語を明確に理解しておきましょう。
- プライマリキー(PK): データベーステーブル内の、そのテーブルの各行を一意に識別する1つ以上の列。プライマリキーはエンティティ整合性を強制し、テーブル間の関係を確立するためにcriticalです。
 - UUID(Universally Unique Identifier): コンピュータシステム内の情報を一意に識別するために使用される128ビットの数値。UUID は中央機関なしで生成されるため、衝突は非常に起こりにくいです。しばしば36文字の16進文字列として表現されます。例:
a1b2c3d4-e5f6-7890-1234-567890abcdef。 - BIGINT: 大きな整数を表すデータ型で、通常は64ビットです。プライマリキーの文脈では、BIGINT はしばしば自動インクリメントされ、データベースが各新しいレコードに自動的にシーケンシャルで一意な番号を割り当てることを意味します。
 - 自然キー: エンティティの不可欠な部分であり、それを一意に記述する1つ以上の既存の属性を使用して形成されるプライマリキー。例としては、ユーザーのメールアドレス、本のISBN、社会保障番号などがあります。
 - サロゲートキー: データベースの外部では意味を持たない、システムによって生成された人工的なプライマリキー。UUIDや自動インクリメントされたBIGINTは、サロゲートキーの一般的な例です。
 - 分散システム: コンポーネントが異なるネットワーク化されたコンピュータ上に配置され、メッセージをやり取りすることによって通信および協調するシステム。この環境は、一意性と整合性の維持に課題を課すことがよくあります。
 - インデックスの断片化: ディスク上のデータの物理的な格納が時間の経過とともに整理されなくなり、データ取得が遅くなること。これは、行が挿入、更新、または削除されたときに発生する可能性があり、特に非シーケンシャルなプライマリキーの場合に顕著です。
 
プライマリキーの戦い
各プライマリキー戦略について、その原則、実装、および理想的なユースケースを検討しながら、詳細に見ていきましょう。
自動インクリメントBIGINTs
原則: BIGINTは通常、シーケンシャルに自動インクリメントされる整数です。各新しいレコードには、次の利用可能な番号が割り当てられます。これは最も伝統的で、しばしば最もシンプルなアプローチです。
実装:
CREATE TABLE users ( id BIGINT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE, email VARCHAR(255) NOT NULL UNIQUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
長所:
- コンパクトなストレージ: BIGINT(8バイト)はUUID(16バイト)よりも小さく、ストレージオーバーヘッドが少なく、より多くのレコードをキャッシュに格納できる可能性があります。
 - Bツリーインデックスの優れたパフォーマンス: シーケンシャルな挿入は、新しいデータを末尾に追加することでBツリーインデックスのパフォーマンスを最適化し、ページ分割や断片化を最小限に抑えます。これにより、高速なルックアップと効率的なキャッシュ利用が可能になります。
 - 可読性: シンプルでシーケンシャルな番号は、人間が読みやすく、デバッグしやすく、参照しやすいです。
 - 自然な順序付け: IDに基づいて、挿入時間によるデータの自然な順序付けが可能です。
 
短所:
- スケーラビリティの課題(分散システム): 分散システム内の複数の独立したデータベースインスタンス全体で、一意でシーケンシャルなIDを生成することは複雑です。中央集権的なID生成サービス(例:Snowflake、TwitterのIDジェネレータ)が必要になることが多く、単一障害点や遅延を引き起こす可能性があります。
 - 予測可能性/セキュリティ上の懸念: IDのシーケ ンスを知っていると、攻撃者がレコードを推測したりイテレーションしたりできるようになります。これは主要なセキュリティ対策ではありませんが、考慮事項です。
 - データ移行の問題: 自動インクリメントIDを持つ異なるデータベースからのデータをマージすると、IDの衝突が発生する可能性があり、複雑なマッピングや再生成が必要になります。
 - ベンダーロックイン(暗黙的): 厳密なベンダーロックインではありませんが、特定の 
AUTO_INCREMENT構文はデータベース間でわずかに異なる場合があります。 
ユースケース: ID生成を処理する単一の中央データベースがあるモノリシックアプリケーションやシステム、または分散ID生成が外部サービスを通じて明示的に管理される場合に理想的です。シーケンシャルな書き込みが有利な高ボリュームの挿入シナリオに最適です。
UUIDs
原則: UUIDは、グローバルに一意であるように設計された128ビットの数値です。さまざまなバージョン(v1、v4、v7)があり、それぞれ異なる生成メカニズムを持っています。v4は純粋にランダム、v1はMACアドレスとタイムスタンプを組み込み、v7はタイムスタンプとランダムビットを組み合わせ、v4よりもデータベースパフォーマンスが向上します。
実装:
-- PostgreSQLの場合(uuid-ossp拡張を使用) CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; CREATE TABLE products ( id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, name VARCHAR(255) NOT NULL, description TEXT, price NUMERIC(10, 2), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- MySQLの場合(UUID()関数またはアプリケーション生成) CREATE TABLE orders ( id BINARY(16) DEFAULT (UUID_TO_BIN(UUID(), 1)) PRIMARY KEY, -- 効率のためにBINARY(16)として格納 user_id UUID, total_amount NUMERIC(10, 2), order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
長所:
- グローバルな一意性: 調整なしで、すべてのデータベース、サーバー、さらには地理的な場所全体で一意性を保証します。これは、分散システムやマイクロサービスアーキテクチャにおいて significantな利点です。
 - スケーラビリティ(分散システム): IDは、競合なしに任意のサービスまたはデータベースインスタンスによって独立して生成できるため、マルチマスター、マルチテナント、またはフェデレーテッドデータベースセットアップに最適です。
 - クライアントサイド生成: IDは、データベースに保存する前にクライアントまたはアプリケーションレイヤーで生成できるため、オフラインデータ入力または楽観的ロック戦略が簡素化されます。
 - 隠蔽によるセキュリティ: UUIDは推測したり列挙したりすることが難しいため、 minorな隠蔽層が追加されます。
 - 簡単なデータマージ: IDの衝突なしで、異なるソースからのデータセットを結合できます。
 
短所:
- ストレージオーバーヘッド: UUIDは16バイトであり、BIGINTの2倍のサイズであるため、インデックスとデータサイズが大きくなります。
 - Bツリーインデックスの断片化(ランダムUUID): ランダムなUUID(v4など)は、非シーケンシャルな挿入につながります。これにより、Bツリーインデックスで頻繁なページ分割と再均衡が発生し、 significantなインデックス断片化、I/Oの増加、および時間の経過とともに書き込み/読み込みパフォーマンスの低下につながります。タイムオーダーのID(v1またはv7など)では、これは lessな問題です。
 - キャッシュ局所性の悪さ: ランダムなUUIDは、関連データがディスク全体に散らばっている可能性があり、キャッシュパフォーマンスを低下させます。
 - 人間による読みにくさ: 長い16進文字列は、読み取り、記憶、デバッグが面倒です。
 - 結合パフォーマンスへの影響: キーサイズが大きいと、比較する必要のあるデータが増えるため、結合パフォーマンスに minor な影響を与える可能性があります。
 
ユースケース: 分散システム、マイクロサービスアーキテクチャ、マルチマスターデータベースレプリケーション、またはIDをオフラインまたは独立したサービスによって生成する必要があるシナリオに不可欠です。インデックス断片化の問題を軽減するために、タイムオーダーのUUIDバージョン(例:v1、v7、またはMySQLの UUID_TO_BIN(UUID(), 1) のようなデータベース固有の実装)の使用が強く推奨されます。
自然キー
原則: 自然キーは、ユーザーのメールアドレスや本のISBNなど、レコードを固有に識別する属性(または属性のセット)を使用します。
実装:
CREATE TABLE customers ( email VARCHAR(255) PRIMARY KEY, -- メールを自然キーとして使用 first_name VARCHAR(255), last_name VARCHAR(255), registration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- 複合自然キーの例 CREATE TABLE course_enrollments ( student_id BIGINT, course_code VARCHAR(10), enrollment_date DATE, PRIMARY KEY (student_id, course_code) );
長所:
- ビジネスの意味: キーは、ユーザーやビジネスドメインにとって意味があります。
 - 冗長性の排除(可能性あり): 自然キーが既に一意な識別子として格納されている場合、それを使用すると追加のサロゲートキー列を作成せずに済みます。
 - 結合のシンプルさ: 自然キーがテーブル間で直接共有されている場合、結合がより直感的になることがあります。
 
短所:
- 変更可能性の懸念: 自然キーは変更される可能性があります(例:ユーザーのメールアドレス)。プライマリキーが変更されると、関連するすべての外部キーテーブル全体でのカスケード更新が必要になり、計算コストが高く、管理が複雑になり、データの一貫性が失われる可能性があります。
 - データ整合性の課題: 自然キーは、常に一意性を保証したり、すべての可能なシナリオで一定のままであったりするとは限りません。今日ユニークに見えるものが、明日そうでないかもしれません。
 - ストレージオーバーヘッド: 自然キーが長い文字列(メールアドレスなど)の場合、BIGINTよりも大きくなる可能性があり、ストレージを消費し、インデックスパフォーマンスに影響を与えます。
 - プライバシーの問題: 自然キーには、しばしば機密情報(メールアドレス、国民IDなど)が含まれており、広く公開される識別子として使用することが望ましくない場合があります。
 - 複雑な複合キー: 自然キーが、一意性を確保するために複数の列(複合キー)を必要とする場合があり、外部キーの関係やインデックスを複雑にします。
 - 開発オーバーヘッド: プライマリキーの更新を処理し、システム全体で参照整合性を確保することは、 significantな開発および保守オーバーヘッドを追加します。
 
ユースケース: ほとんどの最新のWebアプリケーションでは、特に変更がまれなエンティティでは、強く推奨されません。真に不変で、固有で安定した識別子を持ち、かつ簡潔なエンティティ(例:国コード、固定参照データ)には検討できます。サロゲートキーは、ほとんどの場合、より堅牢で保守可能な基盤を提供します。
結論
プライマリキーの選択は、現代のWebアプリケーションに広範囲に影響を与える基本的な決定です。ほとんどのシナリオでは、自動インクリメントBIGINTsは、中央集権的なID生成メカニズムが実行可能なシステムに対して、優れたパフォーマンスとシンプルさを提供します。ストレージを最小限に抑え、Bツリーインデックスを最適化し、人間にとって親しみやすいものです。しかし、それらのアキレス腱は、調整なしでグローバルな一意性を維持することが significantな課題となる分散システムにあります。
ここで UUIDs が輝きます。それらのグローバルな一意性、独立した生成、および分散アーキテクチャへの適合性は、マイクロサービス、マルチリージョンデプロイメント、およびマルチテナントアプリケーションにとって不可欠な選択肢となります。それらの主な欠点であるインデックス断片化を緩和するために、開発者は、グローバルな一意性の利点と改善されたデータベースパフォーマンスを組み合わせるために、タイムオーダーのUUIDバージョン(例:v1、v7、またはMySQLの UUID_TO_BIN(UUID(), 1) のような類似のデータベース固有の実装)を優先すべきです。
自然キーは、概念的にはそのビジネス上の意味合いから魅力的ですが、一般的には、現代的で動的なWebアプリケーションにおけるプライマリキーの選択肢としては、変更可能性、整合性、プライバシーに関連する実用的な課題が多すぎます。サロゲートキー(BIGINTまたはUUID)は、ほとんどの場合、より堅牢で保守可能な基盤を提供します。
最終的に、決定要因はアプリケーションの特定のアーキテクチャとスケーラビリティ要件です。単一のデータベースを持つシンプルでモノリシックなアプリケーションの場合、BIGINTで十分なことが多いです。複雑で分散され、高度にスケーラブルなシステムの場合、UUID(特にタイムオーダーのバリアント)は、必要な柔軟性と回復力を提供し、プライマリキーの武器庫の中で好ましい武器となります。