17
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

グロースエクスパートナーズAdvent Calendar 2023

Day 15

Javaで実現するUUID v7:時刻順序を考慮したユニーク識別子

Last updated at Posted at 2023-12-14

この記事はグロースエクスパートナーズ アドベントカレンダー2023の13日目の記事です。
今年は2023年新卒入社の方などの投稿で盛り上がり、シリーズ2もつくられました。シリーズ2にふさわしい、簡単に読めて役立つ記事をお届けしたい所存です。

はじめに

UUID v7を試してみよう

この記事では時間順序を持ち、ランダム性も備えたUUID v7を紹介するとともに、JavaでのUUID v7の生成とUUID v7からタイムスタンプを取得する実装も紹介します。

要約

  • UUID v4をRDBのプリマリーキーとして用いると、B-Treeインデックスの断片化や挿入処理遅延の懸念がある
  • 解決策としてUUID v7が提案されている
  • JavaにもUUID v7を生成するライブラリがあり利用可能になっている
  • 生成したUUID v7の値からタイムスタンプが取得できる

UUID v4の課題

ユニークな値が欲しいとき、UUIDが真っ先に浮かぶという方は多いと思います。
下記のような文字列を見たことがあるかもしれませんが、実際は128bitの数値です。

4e390b5a-c22f-4702-a16e-2921f5c75c44

UUIDとして広く使われているのはUUID v4というものです。
v4がどんなものかというと、RFCの仕様をみれば、ほぼランダムな値から構成されていることがわかります。

4.4.  Algorithms for Creating a UUID from Truly Random or
     Pseudo-Random Numbers

  The version 4 UUID is meant for generating UUIDs from truly-random or
  pseudo-random numbers.

  The algorithm is as follows:

  o  Set the two most significant bits (bits 6 and 7) of the
     clock_seq_hi_and_reserved to zero and one, respectively.

  o  Set the four most significant bits (bits 12 through 15) of the
     time_hi_and_version field to the 4-bit version number from
     Section 4.1.3.

  o  Set all the other bits to randomly (or pseudo-randomly) chosen
     values.

引用元:https://www.ietf.org/rfc/rfc4122.txt から

UUID v4は予測困難な一意の値にできるという意味では非常に有効ですが、 UUID v4をテーブルのキーにするとデータの断片化がおきやすく、性能問題につながる可能性がある というデメリットがあります。

対策としてのUUID v7

そんな課題を解決するためUUID v7がIETFに提案されています。現在はドラフトで正式に発行される前の状態のようです。

UUID 新フォーマットに関するドラフト

資料によると、先頭48bitにUUID生成時エポックミリ秒を持つことがわかります。これによって、順序とランダム性も持ち合わせるということが可能になっています。

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           unix_ts_ms                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          unix_ts_ms           |  ver  |       rand_a          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|                        rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

引用元:https://www.ietf.org/archive/id/draft-peabody-dispatch-new-uuid-format-04.html#name-uuidv7-field-and-bit-layout

JavaでUUID v7を扱う

Javaの標準クラスライブラリに存在するjava.util.UUIDクラスはJDK21の時点でまだv7の生成は実装されていません。Javaでのuuid v7を利用可能にするライブラリとしては以下が存在します。

cowtowncoder/java-uuid-generator

このライブラリを使うサンプルコードを掲載します。
まずはgradle等のビルドツールで依存関係を追加します。

build.gradle
dependencies {
    implementation 'com.fasterxml.uuid:java-uuid-generator:4.3.0'
}

UUID v7を生成する

com.fasterxml.uuid.impl.TimeBasedEpochGeneratorを用いることでUUID v7を生成することができます。

package jp.co.gxp.adcal_2023;

import java.util.UUID;

import com.fasterxml.uuid.Generators;
import com.fasterxml.uuid.impl.TimeBasedEpochGenerator;

/**
 * UUID v7形式のIDを生成します.
 */
public class IdGenerator {

    // 生成の度にロックを取得する方式なので、
    // 生成時間が気になる場合には、
    // 例えばキーの種類ごとにgeneratorインスタンスを持った方がよさそう[※1]
    private static final TimeBasedEpochGenerator GENERATOR;
    static {
        GENERATOR = Generators.timeBasedEpochGenerator();
    }

    public static UUID generateId() {
        return GENERATOR.generate();
    }
}

※1: com.fasterxml.uuid.impl.TimeBasedEpochGenerator#construct 参照

UUID v7からタイムスタンプを取得する

先頭48bitがUnix timestamp milli secondsです。
uuidは128bitなので、先頭64bitをlongとして取得し、16bitシフトライトすることでタイムスタンプ値を得られます。

package jp.co.gxp.adcal_2023;

import java.time.Instant;
import java.util.UUID;

public final class UUIDTimeUtil {

    /**
     * UUID v7からタイムスタンプを取得します.
     * nullチェックとかuuidバージョンチェックは割愛.
     */
    public static Instant timestampFrom(UUID v7) {
        long epochMilli = v7.getMostSignificantBits() >> 16; 
        return Instant.ofEpochMilli(epochMilli);
    }

}

java.time.Instantのインスタンスが得られるので、例えば以下のコードでOffsetDateTimeに変換できます。

// 2023-12-12T09:16:13.039+09:00 などが得られる
OffsetDateTime timestamp = timestampFrom(IdGenerator.generateId())
  .atZone(ZoneId.systemDefault())
  .toOffsetDateTime()

まとめ

JavaでもUUID v7を扱う準備ができているよ、という紹介でした。

UUIDクラスはJava 5.0で2004年に追加されたものです。それが20年たってもそのまま新しい仕様にも対応できているという点に驚きました。JavaのUUIDクラスのAPI設計も、UUIDの仕様も、よく考えられた仕組みであることに感動しました。
このような発展性、後方互換性を持った仕様やAPIを作れるというのは素晴らしいですね。

おまけ

UUIDの新フォーマットに関するRFCドラフト にはDBのキーとして使う場合は文字列ではなく128bitの数値として保持せよ、とあります。

For many applications, such as databases, storing UUIDs as text is unnecessarily verbose, requiring 288 bits to represent 128 bit UUID values. Thus, where feasible, UUIDs SHOULD be stored within database applications as the underlying 128 bit binary value.

postgresにはUUID型がありますが、MySQLではBINARY型でBINARY(16)を用いるとよさそうです。
こういうヒントがあるのもRFCのよいところですね。

17
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?