Webエンジニア向けプログラミング解説動画をYouTubeで配信中!
▶ チャンネル登録はこちら

【ITニュース解説】Your UUID Column Is 9x Bigger Than It Needs to Be

2026年10月26日に「Dev.to」が公開したITニュース「Your UUID Column Is 9x Bigger Than It Needs to Be」について初心者にもわかりやすく解説しています。

作成日: 更新日:

ITニュース概要

UUIDは16バイトだが、DBで文字列として保存すると144バイトになり、インデックスが9倍に肥大化し処理が遅くなる。MySQLではBINARY(16)で保存すべきだ。v4 UUIDは書き込みを遅くするが、v7 UUIDは時間情報が漏れる点に注意。安易な短縮IDは衝突しやすい。

ITニュース解説

UUID(Universally Unique Identifier)は、システム全体で重複しない識別子を生成するための標準的な方法であり、データベースの主キーなどで広く利用される。その名の通り、世界中で唯一無二のIDを生成することを目的としている。この記事は、このUUIDをデータベースで扱う際に起こりがちな、しかし重大なパフォーマンス問題とその解決策について解説する。

まず、UUIDのサイズに注目する。UUIDそのものは16バイトのバイナリデータだ。これは例えば、F8C7F00F-8C7F-4F00-8C7F-0F8C7F00F8C7のような文字列として表現されるが、このハイフンを含む文字列はあくまで「表示形式」であり、実際のUUIDデータは16バイトの固定長データなのである。しかし、多くのデータベーススキーマでは、このUUIDを文字列型、特にVARCHAR(36)やCHAR(36)として保存するケースが非常に多い。APIなどで文字列形式のUUIDがやり取りされるため、そのままデータベースに格納するのが手軽だと考えられるからだ。

ここで問題が発生する。特にMySQLのようなデータベースでVARCHAR(36)やCHAR(36)を使い、さらにutf8mb4のようなマルチバイト文字セットを設定している場合、36文字の文字列は1文字あたり最大4バイトを消費するため、36文字 × 4バイト = 144バイトもの領域を占有してしまう。本来16バイトで済むはずのデータが、9倍の144バイトに膨れ上がるのだ。この過大なデータサイズは、単にストレージ容量を無駄にするだけでなく、データベースのパフォーマンスに深刻な影響を与える。

データベースのインデックスも同様に影響を受ける。インデックスはデータを効率的に検索するための仕組みだが、UUIDを文字列型で保存していると、インデックスも各エントリごとに144バイトを格納することになる。例えば、100万行のデータを持つテーブルでは、UUIDを16バイトのバイナリで保存した場合のインデックスサイズは約16MBで済む。しかし、文字列形式で保存すると、インデックスは約144MBにもなり、これは9倍もの差が生じる。インデックスがメモリに収まらないと、データベースは頻繁にディスクからデータを読み書きする必要が出てくる。ディスクI/Oはメモリアクセスに比べて非常に遅いため、これがシステムのボトルネックとなる。また、UUIDを文字列として比較する処理は、16バイトのバイナリデータとして比較するよりもCPUコストも高くなる。

この問題の解決策はデータベースの種類によって異なる。PostgreSQLにはネイティブなUUID型があり、これを使えばデータは正しく16バイトで保存され、この問題は回避できる。ただし、習慣的にTEXT型などを使ってしまうと、同様の問題が発生する可能性がある。MySQLの場合は、BINARY(16)型を使うのが適切な解決策となる。これにより、UUIDは16バイトのバイナリデータとして効率的に保存される。さらに、UUIDのバイナリ変換にはUUID_TO_BIN(?, 1)やBIN_TO_UUID(id, 1)といった関数を利用する。引数の1は、UUIDの時間フィールドを入れ替えてインデックスの局所性を高め、検索や挿入のパフォーマンスを向上させる効果が期待できる。

次に、UUIDの「衝突」、つまり重複が発生する可能性について考える。一般的なv4 UUIDは122ビットのランダムな値から構成されており、衝突の確率は極めて低い。例えば、10億個のUUIDを生成したとしても、重複が発生する確率は非常に小さい。記事が指摘するように、多くの人がUUIDの衝突を過度に恐れているが、実際に発生する重複は、開発者自身のコーディングミスに起因することがほとんどである。具体的には、予測可能な乱数ジェネレータを使ったり、データベースのカラムに空の文字列をデフォルト値として設定したり、あるいはUUIDを短縮して使ったりする場合に、意図しない重複が起こりうる。

特にURLなどで短いIDを表示するためにUUIDの一部を切り取って使う「短縮ID」は非常に危険だ。例えば、UUIDの最初の8文字(16進数)だけを使うと、これは32ビットのデータに相当する。この場合、約77,000行のデータで50%の確率で衝突が発生する可能性がある。12文字(48ビット)に増やしても、約2,000万行で50%の衝突確率となる。短縮IDが必要な場合は、UUIDを単純にスライスするのではなく、Base62エンコーディングなどを用いて、必要な行数に合わせて意図的に生成するべきである。v7 UUIDのような時間ベースのUUIDを短縮すると、同じ時間帯に生成されたIDは先頭部分が共通するため、衝突の可能性がさらに高まる点にも注意が必要である。

UUIDの利用には、もう一つパフォーマンス上の落とし穴がある。それは「ランダム挿入のコスト」だ。標準的なv4 UUIDは、その最初のバイトがランダムな値である。データベースのインデックスはB-treeという構造でデータを管理することが多いが、この構造ではデータが順序通りに挿入されると効率が良い。しかし、v4 UUIDのようにランダムな値が挿入されると、データがインデックスのさまざまな場所に散らばって挿入されることになる。

テーブルのデータ量が少ないうちは、インデックス全体がメモリ(キャッシュ)に収まるため、この問題は表面化しにくい。しかし、データ量が数百万行を超え、インデックスがメモリに収まらなくなると、各挿入処理でインデックスツリーの奥深くにある「冷たいページ」(メモリにキャッシュされていないディスク上のページ)をディスクから読み込み、さらに新しいデータを挿入するためにページの分割が発生する。これにより、書き込みスループットが劇的に低下する。「UUIDはたった16バイトだから大丈夫」という認識は、このランダム挿入によるI/Oコストを見落としている。

このランダム挿入のコストを解決するために登場したのが、v7 UUIDやULID(Universally Unique Lexicographically Sortable Identifier)といった新しいUUIDのバージョンだ。これらの識別子は、タイムスタンプを上位ビット(先頭部分)に配置するように設計されている。これにより、生成されるUUIDは時間順に並び、インデックスへの挿入が常にB-treeの「右端」に近い位置で行われるようになる。結果として、ランダム挿入によるディスクI/Oやページ分割が減少し、書き込み性能が大幅に改善される。

ただし、v7 UUIDにはトレードオフも存在する。タイムスタンプが識別子の先頭に埋め込まれているため、そのIDがいつ作成されたかという情報が外部に漏洩してしまう。そのため、作成時間を秘匿する必要があるIDにはv7 UUIDは適さない場合がある。

以上の点を踏まえると、UUIDを扱う上でのベストプラクティスは次のようになる。第一に、UUIDは常に16バイトのバイナリデータとしてデータベースに保存するべきだ。これは、ストレージ効率とI/O性能の両面で最も優れている。第二に、データベースのインデックスには、v7 UUIDのように時間順に並ぶ値を利用することが推奨される。これにより、特に大量のデータが書き込まれる際のパフォーマンス低下を防ぐことができる。最後に、UUIDを短縮した文字列として比較することは絶対に避けるべきである。これは衝突の危険性を高めるだけでなく、文字列比較による性能低下も招くからだ。

UUIDは強力なツールだが、その特性を理解し、適切に利用しなければ、意図しないパフォーマンス問題やデータの信頼性に関わる問題を引き起こす可能性がある。システムエンジニアとしてデータベースを設計する際には、これらの点を十分に考慮することが重要となる。

関連コンテンツ

関連IT用語

関連ITニュース