21 ポイント 投稿者 GN⁺ 2024-07-06 | 4件のコメント | WhatsAppで共有
  • UUIDはデータベーステーブルの主キーとしてよく使われる
    • 生成が簡単で、分散システム間で共有しやすく、一意性を保証できる
    • UUIDのサイズを考えるとこれが正しい選択なのか疑問に思うが、多くの場合こちらで決められない
  • この記事は「UUIDがキーに適した形式か」に焦点を当てるのではなく、PostgreSQLでUUIDを主キーとして効率的に使う方法を説明する

PostgreSQLでUUIDを主キーとして使う

  • UUIDとは?
    • UUIDはデータベーステーブルの主キーとしてよく使われる
    • 分散システム間で簡単に共有でき、一意性を保証する
    • UUIDのサイズのため適切か疑問に思うこともあるが、選択の余地がないことも多い

PostgreSQLのUUIDデータ型

  • UUIDを文字列として保存

    • PostgreSQLは文字列を保存するための text データ型を提供している
    • しかし text 型はUUIDの保存には適していない
    • PostgreSQLはUUID専用のデータ型 uuid を提供している
    • uuid 型は128ビットのデータ型で、1つの値を保存するのに16バイト必要
    • text 型には1バイトまたは4バイトのオーバーヘッドが追加される
  • 実験結果

    • 2つのテーブルを作成して比較: 片方は text 型、もう片方は uuid
    • 10,000,000行を挿入した後、テーブルサイズとインデックスサイズを比較
    • text 型を使うテーブルは54%大きく、インデックスサイズは85%大きい

UUIDとB-Treeインデックス

  • B-TreeインデックスとUUID

    • ランダムUUIDはB-Treeインデックスに向いていない
    • B-Treeインデックスは順序のある値とうまく機能する
    • Javaの UUID.randomUUID() はUUID v4を返し、これは疑似ランダム値である
    • UUID v7は時系列順に並ぶ値を生成するため、B-Treeインデックスに適している
  • UUID v7の使用

    • JavaでUUID v7を使うには java-uuid-generator ライブラリが必要
    • UUID v7を生成すると挿入性能が向上する可能性がある

UUID v7がINSERT性能に与える影響

  • 実験
    • UUID v7を使うテーブルを作成し、10,000行を10回挿入して性能を測定
    • 結果はややランダムだが、UUID v7の挿入はおよそ2倍高速

追加の読み物

  • PostgreSQL 17でUUID v7がネイティブサポートされる可能性がある
  • UUID v7形式に関する情報
  • UUIDがデータベース主キーとしての性能に与える影響

まとめ

  • UUIDの長さの問題

    • 最適化しても、UUIDは主キーとして最適な型ではない
    • 選択の余地があるなら、TSIDのような別の選択肢を検討すること
  • 最適化の必要性

    • 大規模データセットや高トラフィックが見込まれるなら、最適化を検討すべき
    • 主キーの変更は難しい作業なので、最初から正しく設定することが重要
  • 注意事項

    • 筆者はPostgreSQLの専門家ではなく、学んだ内容を共有しているだけ
    • 役に立ったなら、コメントやTwitterでフィードバックを送ってほしい

GN⁺のまとめ

  • この記事は、PostgreSQLでUUIDを主キーとして使う際の効率的な方法を扱っている
  • 実験を通じて、UUID v7を使うと挿入性能が向上する可能性を示している
  • 大規模データセットや高トラフィックが予想される場合は最適化が必要
  • TSIDのような別の選択肢も検討する価値がある

4件のコメント

 
savvykang 2024-07-09

UUID に標準形式(16進数 + ハイフン)ではなく base62 エンコーディングを望むのは無理でしょうか?

 
qurare 2024-07-08

uuidv7 は無敵だ
uuidv8+ は「神」だ

 
bbulbum 2024-07-08

一番大きなハードルは、、人に優しくないこと…。自分はまだ多くの場面でこの点が必要ですね…。

 
GN⁺ 2024-07-06
Hacker Newsの意見
  • B-tree に適した主キーとして bigserial を使い、外部レコードロケータの選択肢として文字列エンコードされた UUID を検討することを推奨

    • 技術に詳しくないユーザーが引用する場合は、まず PNR スタイルのロケータのような簡単な選択肢を検討すること
    • サービスやアプリケーションのスキーマ内で PK の型を混在させないこと
    • 一意識別子として UUIDv7 を使う場合は、タイムコードが内在するデータにのみ使うこと
    • hashids は使わないこと。暗号学的な品質がなく、一般の人にもなじみが薄い
    • エンコード時に base64 やハイフンを含むアルファベットを使わないこと
  • データベーススキーマ設計では、関心の分離と機械的共感の原則を念頭に置くこと

  • Stripe の型付きランダム ID は、実際にはランダムではない

    • メタデータ、埋め込まれたタイムスタンプ、シャードや参照キー、バージョン情報などが含まれている
    • 個人的には base58 でエンコードした AES 暗号化 bigserial+HMAC ロケータを好む
  • Postgres ではランダム UUID は大きな問題ではない

    • UUID(16バイト)は serial(4バイト)や bigserial(8バイト)より大きいが、テーブル全体のレベルでは大きな問題ではない
  • Postgres で serial vs. random UUID vs. ordered UUID を考える前に、心配すべきことは他にもたくさんある

  • 最近 Postgres の PK として ULID を選び、この記事がとても参考になった: https://brandur.org/nanoglyphs/026-ids

  • ULID を好む理由は、UUID 型と互換性があり、タイムスタンプが組み込まれているため、ID でソートするとタイムスタンプ順に並ぶから

  • 比較に int64 も含まれていれば、UUID と従来のアプローチのオーバーヘッドを比較できてよいはず

  • 挿入性能は性能評価の方法としては適切ではない

    • B-Tree は挿入時の性能は高いが、大規模トランザクションではどうなのか疑問
  • SQLite で UUID4 が好まれる理由は、トランザクションロック中にページキャッシュの競合が起きる可能性が低いため

    • Postgres システムでも同様に当てはまる可能性がある
  • 整数の自動増分主キーを好む

    • 理解しやすく、並べ替えも簡単
    • 大規模なバッチプロジェクトでは、最後の主キーを保存して、それより大きいものをすべて取得できる
  • UUIDv7 の挿入時間ベンチマークには UUID 生成時間が含まれている

    • 単純にインデックス更新コストだけを切り分けて見たい
  • PostgreSQL 17 に UUIDv7 サポートが含まれる可能性は低い

    • 最近の作業でコミッターが外れ、バージョン 17 はすでに機能凍結状態にある
  • python-ulid を使い始めたが、ULID は UUID より優れている

  • UUID v7 の標準リンクは古くなっているので、RFC 9562 を参照すること: https://datatracker.ietf.org/doc/html/rfc9562