開発者ツール · UUID ジェネレーター
ランダム UUID 主キーと B ツリーの断片化: 実際に何が起こるか
· なぜそれが重要なのか
uuid 暗号化 ブラウザ API
ランダムな v4 キーはランダムなインデックス ページに挿入され、クラスター化インデックスがその費用を支払います。この投稿では、メカニズム、テキストとバイナリのストレージ コスト、および時間順 UUID が状況を変える場所について説明します。
テーブルが大きくなるにつれて挿入が遅くなる - DBA がキーの選択を検討する症状
クラスター化インデックスを持つデータベースに主キーとしてランダムな UUID を持つ行を挿入すると、データベースは B ツリー構造内のランダムな位置に新しい行を挿入します。シーケンシャル キーは右端のリーフ ページに追加され、すべての挿入が小さなキャッシュ常駐領域内に保持されます。ランダム キーはインデックス全体に挿入を分散させ、データベースが物理ストレージ内で遠く離れたページを横断して変更することを強制します。テーブルが成長し、ツリーが深くなるにつれて、挿入ごとにより多くのページにアクセスし、より多くの I/O 操作が発生します。 1,000 行では安く見える操作が、100 万行では高価になります。これは理論的な問題ではありません。これは、挿入スループットの目に見える低下として現れます。
クラスター化された B ツリーがどのように満たされるか — 連続キーが最後のページに追加されます。ランダムなキーがインデックス全体のページにアクセスします
このメカニズムは、B ツリーがリーフ ページ内でソートされたキーの順序を維持するため、B ツリーの動作の基礎となります。すでにキー 1 から 10,000 が格納されているテーブルにキー 10,001 を持つ行を挿入すると、データベースはその行がどこに属しているかを認識します。最後に、スペースがある場合は既存の右端のページに、または右側に追加された新しいページに属します。 7524fae2-7dec-11d0-a765-00a0c91e6bf6 のようなランダムな UUID を持つ行を同じテーブルに挿入する場合、データベースはツリーをナビゲートして、その UUID 範囲内のキーを含むリーフ ページを見つけ、そのページ内の正確な位置を見つけて行を挿入する必要があります。そのページがいっぱいの場合、ページは分割され、コンテンツの半分が新しいページに移動され、親ノードが更新されます。
ページ分割とキャッシュ負荷 — ランダム挿入のコストが高くなる理由 I/O と、その効果がテーブル サイズに応じて大きくなる理由
ランダム キーは、すべての挿入が右端のページに追加されるのではなく、ツリー内のランダムな位置に配置されるため、最悪の場合の挿入パターンを作成します。データベースは正しいページを検索する必要があり、これには通常、ツリーのレベルごとに 1 つずつ、複数のページの読み取りがかかります。次に、そのページを変更する必要があります。これにより、ツリーの上に伝播する分割が引き起こされる可能性があります。より多くのページが変更され、より多くの書き込みが発生し、メモリ バッファ プールは、アクティブな挿入ポイントに焦点を当て続けるのではなく、ツリーのさまざまな領域からのページでいっぱいになります。キャッシュ ミスが増加し、I/O がボトルネックになり、テーブルの規模が大きくなるにつれて挿入率が頭打ちになります。
テキストまたはバイナリ — 36 文字列と 16 バイトのネイティブ UUID またはバイナリ (16) 列、およびインデックス サイズへの影響
システムが異なればページ管理の最適化方法も異なるため、コストはデータベース エンジン間で均一ではありません。強力な圧縮、小さいページ サイズ、またはインメモリ操作を行うデータベースでは、シーケンシャル キーとランダム キーの間でパフォーマンスの差が小さくなる可能性があります。大きなページ、機械ディスク、または厳しいメモリ制限を備えたデータベースでは、劇的な劣化が見られます。この問題は観察可能かつ測定可能です。1,000 行、100,000 行、および 1,000,000 行で挿入率を測定します。スケールが大きくなると 1 秒あたりの速度が急激に低下する場合は、特定のハードウェアおよびデータベース構成でランダム挿入ペナルティが発生することになります。
時間順の代替案 — UUIDv7 と ULID がインデックスの最後に挿入する際に一意性を維持する方法
UUID は、選択したストレージ形式に応じて、テキスト形式で 36 文字、またはバイナリ UUID 型として 16 バイトを消費します。 UTF-8 または ASCII 列の 36 文字列は、64 ビット整数の 8 バイトと比較して、36 バイトです。圧縮技術が適用されていないと仮定すると、string-UUID 列のインデックスは、整数列のインデックスより 3 倍大きくなります。インデックスが大きくなると、バッファ プールに収まるインデックス ページが少なくなり、ツリーを走査する際のキャッシュ ヒットが少なくなります。 RAM に収まる小さなインデックスは、挿入順序やワークロード パターンに関係なく、クエリごとにディスクから読み取る必要がある大きなインデックスよりもパフォーマンスが高くなります。
作業例 - ランダム キー テーブルと時間順キー テーブルについて説明したのと同じ挿入ワークロードを、定性的に、ベンチマークを作成せずに実行します。
主キーを含むすべてのセカンダリ インデックスは、完全な 36 文字の UUID または 16 バイトのバイナリ UUID 値を格納する必要があるため、ストレージの違いはプライマリ インデックスとセカンダリ インデックスの両方にとって非常に重要です。これにより、セカンダリ インデックスは、主キーの検索に整数キーを使用したインデックスよりも大幅に大きくなります。レプリケーション、バックアップ、クエリ結果セットはすべて、インデックス サイズが大きくなるにつれて比例して増大します。 ToolAcre UUID ジェネレーターは、バイナリ互換の値を生成します。データベースに応じてそれらを binary(16) または GUID タイプとして保存すると、varchar(36) と比較してスペースが節約され、全体的なキャッシュ効率が向上します。このストレージの最適化は、大規模システムにとって重要です。
これでカバーされないもの — 主キーがクラスター化されておらず、効果が小さいヒープ構成テーブルおよびデータベース
一般的な最適化は、UUID を内部的にバイナリとして保存し、API またはユーザー インターフェイスで必要な場合にのみ文字列として表示することです。インデックスと結合の操作は、コンパクトなバイナリ形式で機能します。 API 応答またはアプリケーション コードは文字列表現に変換されます。一部のデータベースは、この変換を自動的に処理する組み込みの GUID または UUID タイプを提供します。明示的なキャスト操作が必要な場合もあります。 36 バイトの列と 16 バイトの列のパフォーマンスの違いは実際にあります。100 万行のテーブルと 36 バイトのテーブルと、16 バイトの UUID 列キーは、インデックス レベルごとに 20 MB だけ異なります。これは、L3 キャッシュにインデックスが適合するかメモリを必要とするかの差になる可能性があります。取ってくる。
要点: バージョンを選択する前にインデックスを知っておいてください。ToolAcre ジェネレーターはランダムな UUID を生成します。投稿を使用して、それがストレージ エンジンに適合するかどうかを判断してください
挿入パフォーマンス、インデックス サイズ、クエリ特性の間のトレードオフには、ワークロード パターンに基づいたアーキテクチャ上の決定が必要です。タイムスタンプベースの文字列など、文字列が昇順の場合、シーケンシャル文字列キーは高速に挿入できますが、ランダムな UUID と同じスペースを消費し、一時的な情報が漏洩します。ランダムな UUID は意味的にクリーンで、リークするタイムスタンプ コンポーネントがありませんが、クラスター化インデックスへの挿入が遅く、全体的なストレージ容量が大きくなります。 UUIDv7 のような時間順の代替手段は、バージョン 4 のランダム識別子でのタイムスタンプのリークを回避しながら、挿入の局所性を維持することで利点を兼ね備えています。