データベースを扱う場合、テーブルの各行に対して一意の識別子を与えるために、ある種のid
フィールドを使用するのが一般的な習慣です。
例えば、customers
テーブルを想像してください。複数の顧客が同じ名前を持っていたり、同じ住所を共有していたりする可能性があるため、name
やaddress
といったフィールドを一意の識別子として使用することはありません。
その代わりに、各行には何らかのユニークな識別子を割り当てるのがよいでしょう。その1つがUUIDの使用です。
UUIDとは何でしょうか?
UUIDとは、Universally Unique IDentifierの略で、36文字の英数字で情報(テーブルの行など)を識別するために使用される文字列です。
以下はUUIDの一例です: acde070d-8c4c-4f0d-9d8a-162843c10333
UUIDが広く使われているのは世界的に一意である可能性が高いからです。つまり、私たちの行のUUIDは私たちのデータベーステーブルで一意であるだけでなく、おそらくどこのシステムでもそのUUIDを持つ唯一の行であるということです。
技術的には、私たちが生成したUUIDが他の場所で使用される可能性もないわけではありませんが、340,282,366,920,938,463,463,374,607,431,768,211,456通りのUUIDの可能性があるので、その限りではないのです...。
UUIDは何に使うのか?
この質問に答えるために私たちがeコマースの書店を運営していると仮定しましょう。注文が入るとその注文にID番号を付けて、その番号を使ってorders
テーブルに格納したいと思います。
最初に来た注文は1
、2
、...というように、連続したIDを設定することができます:
id | item | buyer | price |
---|---|---|---|
1 | The Years of Rice and Salt | Sue | $14 |
2 | A Darkling Sea | Al | $20 |
3 | Too Like the Lightning | Mei | $25 |
そしてこの方法は私たちの規模が小さければ、少なくともしばらくの間はうまくいくかもしれません。しかし、この方法にはいくつかの大きな欠点があります:
第一に、テーブルの結合や新しいデータのインポートを行う際に、上記のid
値がユニークでないため、混乱が生じやすくなります。これは複数のテーブルで同じIDシステムを使用している場合、内部でも問題が生じますが、外部のあらゆるデータを扱うようになると本当に厄介なことになります。
例えば、私たちの小さな本屋が大きくなり、別のオンライン書店を買収したとします。orders
テーブルを統合しようとしたところ、同じシステムを使用していることがわかりました。この問題を解決するには、統合する2つのデータベースのうち少なくとも1つのデータベースのIDをすべて更新する必要があります。最良のシナリオであっても、これは非常に面倒なことです。
第二に、シーケンシャルなアプローチはINSERT
コマンドを1つずつ実行しなければならないため、あらゆる種類の分散システムでうまく機能しないことです。この制限により規模が大きくなるとパフォーマンスに大きな問題が生じる可能性があります。アプリケーションで厳密なID順序が必要な場合でも、CockroachDBのChange Data Captureのような機能を使用すれば、UUIDを使用しながらも要件を満たすことができ、順次順序付けられたIDに起因するパフォーマンスの打撃を受けずに済むかもしれません。
また、SERIAL
でランダムなIDを生成するような従来のユニークIDのアプローチも、分散システムでは同じ時期に生成された値は似たような値を持つため、テーブルのストレージで互いに近い場所に配置されるため、ホットスポットにつながることがあります。
UUIDはこれらの問題をすべて解決します:
- グローバルに一意であるため外部データであっても重複するIDに遭遇する可能性が非常に低いです。
- UUIDは中央のノードと照合することなく生成できるため、分散システムでは各ノードが自律的にUUIDを生成でき、重複や整合性の問題を心配する必要がありません。
理由1だけでもほとんどすべてのデータベースシステムでUUIDを使用するための十分な論拠となります。また、理由2は分散型データベースはスケーラビリティと耐障害性に優れているため、大規模な運用を目指すビジネスとして、私たちブックショップに非常に適しています。
UUIDのデメリット
UUIDの唯一の重大な欠点はメモリに128ビット(メタデータを含めるともう少し多い)を占有することです。ストレージスペースを最小限に抑えることが絶対に必要な場合、36文字の英数字を保存するよりも、シーケンシャルID(おそらく1~10文字の間のどこかであろう)を保存する方が効率的であることは明らかです。
しかし、ほとんどの場合でシーケンシャルIDのようなものを使用することのデメリットは、UUIDを使用することで生じるストレージコストの最小限の増加を大幅に上回ります。UUIDは非常に人気があり、さまざまな異なる識別目的で広く使用されています。私たちはかなり素晴らしいデータベースを作っているので、この記事ではデータベースの例に焦点を当てましたが、UUIDは分析システム、ウェブやモバイルアプリケーションなどでも使用されています。
UUIDの例
UUIDにはいくつかの種類があります:
バージョン1とバージョン2です。 タイムベースUUIDと呼ばれることもあるこれらのIDはUUIDが生成される時間を反映したdatetime値、ランダム値、UUIDを生成するデバイスのMACアドレスの一部の組み合わせを使って生成されます。
その内訳を視覚的に説明すると次のようになります:
このようにUUIDを生成することで、同一のUUIDを持つことはほとんど不可能になります。同じデバイスによって全く同じ時間に生成され、全く同じランダムな16ビットシーケンスを生成していなければならないのです。
UUID v1およびUUID v2のIDは、生成デバイスのMACアドレスの一部を含んでいるため、(例えば)どのデータベースノードがそのIDを生成したかを識別するために使用することができます。これは一般的には問題ではなく、分散システムでは利点となることもあります。
UUID v1とv2の違いはUUID v2にはローカルドメイン番号のセグメントも含まれていることです。多くの理由から、これはほとんどのアプリケーションに最適ではないので、UUID v2は広く使用されていません。
バージョン3およびバージョン5。 この2つのバージョンのUUIDは、名前空間識別子と名前をハッシュ化することで生成されます。完全にランダムではなく既存のデータを使って生成されるという点ではタイムベースのUUIDと似ていますが、日付データとデバイスのMACアドレスではなく、名前空間データと名前データを使用します。
名前空間データはそれ自体がUUIDであり、名前データは実際には任意の文字列を使用することができますが、通常はUUIDの使用方法(例えばアカウント名や製品IDなど)に関連します。しかし、使用される2つの値が何であれ、ハッシュ化されて36文字の英数字の文字列が最終的なUUIDとなります。
UUIDのバージョン3と5は、主に異なるハッシュアルゴリズムを使用している点で異なります。UUID v3はMD5
を使用し、UUID v5はSHA-1
を使用しています。
バージョン4。 これらのUUIDは単にランダムに生成された36文字の文字列です。v4 UUIDであることを示す1つの4
を除けば、各文字は単にa~zのランダムな文字、または0~9の整数です。
生成は完全にランダムであるため、ユニークである可能性は極めて高いです。また、日付、MACアドレス、名前データなどの識別情報は含まれていません(これは特定のユースケースによって利点にも欠点にもなり得ます)。
バージョン6、7、8。 この記事を書いている時点ではこれらのバージョンは存在しませんが、提案されており、今後数年のうちにUUID標準に追加される可能性があります。これらの提案されている新しいUUIDについては、こちらで詳細をご覧いただけます。
UUIDの生成方法
UUIDは複雑そうに見えるかもしれませんが、最新のアプリケーション開発の文脈ではUUIDの生成は実はとても簡単なのです。一般的なプログラミング言語には関数を呼び出すだけで簡単にUUIDを生成できるライブラリが用意されています。
例えばJavaScriptではuuidパッケージをインポートしてuuid.v1()
を呼び出すだけでUUID v1が生成されます。Pythonも非常に似ていてuuidをインポートしてuuid.uuid1()
でUUID v1が生成されます。
データベース、特に分散型データベースではUUIDの生成が組み込まれている場合があります。例えばCockroachDBでは行の識別子としてUUIDを使うことを推奨していますが、これを行うにはgen_random_uuid()
関数を使うだけと簡単です。
そのため、例えばSQLを使ってテーブルを作成する際に各行に対してUUID v4を自動生成するようにすることができます:
CREATE TABLE users ( id UUID NOT NULL DEFAULT gen_random_uuid(), city STRING NOT NULL, name STRING NULL, address STRING NULL, credit_card STRING NULL, CONSTRAINT "primary" PRIMARY KEY (city ASC, id ASC), FAMILY "primary" (id, city, name, address, credit_card) );
このテーブルに行が挿入されるたびにid
値は自動生成されたUUIDとなります。
UUIDの生成と使用は、一般的に非常に簡単です。具体的な実装の詳細は、使用するデータベース技術やプログラミング言語によって多少異なりますが、ほとんどの場合、generate_uuid()関数のようなものを呼び出すだけです。
分散データベースにおけるUUID
分散型SQLデータベースはNoSQLデータベースの弾力的なスケールと回復力、SQLデータベースのトランザクションの一貫性と親しみやすさを組み合わせた強力な組み合わせを提供します。
しかし、分散システムを扱うということは、いくつかの点で異なるアプローチが必要であることを意味します。例えば行IDを生成する際に整数に1を足して2
、3
、4
と識別するような従来のやり方は分散システムではうまく機能しません。これらのアプローチは「ホットスポット」ノードにつながり、パフォーマンスのボトルネックになります。
UUIDはデータベースの各ノードが他のノードと照合することなく、自律的に完全にユニークなUUIDを生成できるため、多くの分散ワークロードに優れた選択肢を提供します。
しかし、これはUUIDが常に最良の選択であることを意味するものではありません。CockroachDBではUUIDを使うとうまくいくことが多いですが、マルチカラムの主キーを使うと、状況によっては優れたパフォーマンスを発揮することがあります(ただし、セットアップとテストがより複雑になります)。
ハンズオンに行く
分散型データベースでUUIDを扱うことを自分で試してみたいと思いませんか?CockroachDB Serverlessは無料で、新しいクラスタをスピンアップして、5分以内にテーブルの作成とデータの挿入を行うことができます。ぜひ試してみてください!