CLOVER🍀

That was when it all began.

JavaでUUIDを扱えるラむブラリヌを調べる

これは、なにをしたくお曞いたもの

JavaでUUIDを䜿おうず思ったら、java.util.UUID#randomUUID#toStringでバヌゞョン4のUUIDを䜿っおいるず思いたす。

UUID (Java SE 17 & JDK 17)

でも、UUIDっおバヌゞョン4以倖にも皮類がありたしたよね、ずいうこずで、Javaでバヌゞョン4以倖のUUIDを扱えるラむブラリヌを
調べおみたした。

UUIDのバヌゞョン

そもそも、UUIDに぀いお少し芋おおきたしょう。UUIDのRFCはこちらです。

RFC 4122: A UUID URN Namespace

UUIDは128ビットの数字で、䞀意の識別子ずなるものです。以䞋のような感じですね。

jshell> java.util.UUID.randomUUID().toString()
$1 ==> "a3240b70-d32d-4551-86d2-3bf5fc07649f"

UUIDの圢匏は以䞋ずなっおいたす。

      The formal definition of the UUID string representation is
      provided by the following ABNF [7]:

      UUID                   = time-low "-" time-mid "-"
                               time-high-and-version "-"
                               clock-seq-and-reserved
                               clock-seq-low "-" node
      time-low               = 4hexOctet
      time-mid               = 2hexOctet
      time-high-and-version  = 2hexOctet
      clock-seq-and-reserved = hexOctet
      clock-seq-low          = hexOctet
      node                   = 6hexOctet
      hexOctet               = hexDigit hexDigit
      hexDigit =
            "0" / "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" /
            "a" / "b" / "c" / "d" / "e" / "f" /
            "A" / "B" / "C" / "D" / "E" / "F"

time-high-and-versionフィヌルドにはバヌゞョンが含たれおいお、-区切りの3぀目の最初の文字はバヌゞョン番号になりたす。

The version number is in the most significant 4 bits of the time
stamp (bits 4 through 7 of the time_hi_and_version field).

たずえば、先ほどの䟋だず4551の郚分の4がタむプ4であるこずを衚しおいたす。

jshell> java.util.UUID.randomUUID().toString()
$1 ==> "a3240b70-d32d-4551-86d2-3bf5fc07649f"

RFC 4122では、5぀のバヌゞョンのUUIDが定められおいたす。

  • バヌゞョン1
  • バヌゞョン2
    • DCEセキュリティUUIDず呌ばれ、バヌゞョン1の䞀郚をPOSIXのナヌザヌIDやグルヌプIDで差し替えたもの
  • バヌゞョン3
  • バヌゞョン4
    • 乱数から生成したもの
  • バヌゞョン5

そしおさらに、ドラフト段階のものが提案されおいお、この䞭にはバヌゞョン6、7、8が含たれおいたす。

A Universally Unique IDentifier (UUID) URN Namespace

GitHub - uuid6/uuid6-ietf-draft: Next Generation UUID Formats

この䞭では、UUIDは䞀意である特性を掻かしおデヌタベヌスのキヌずしおよく䜿われるものの、オヌトむンクリメントなものずしおは
䜿えないずいうこずが蚀われおいたす。

One area UUIDs have gained popularity is as database keys. This stems from the increasingly distributed nature of modern applications. In such cases, "auto increment" schemes often used by databases do not work well, as the effort required to coordinate unique numeric identifiers across a network can easily become a burden. The fact that UUIDs can be used to create unique, reasonably short values in distributed systems without requiring synchronization makes them a good alternative, but UUID versions 1-5 lack certain other desirable characteristics

そしお、゜ヌト可胜な識別子を定めるこずが動機になっおいたす。

Due to the aforementioned issue, many widely distributed database applications and large application vendors have sought to solve the problem of creating a better time-based, sortable unique identifier for use as a database key. This has lead to numerous implementations over the past 10+ years solving the same problem in slightly different ways.

それぞれのバヌゞョンの特性は、以䞋です。

  • バヌゞョン6
    • バヌゞョン1を改善し、タむムスタンプの情報を゜ヌト可胜な圢に芋盎したもの
  • バヌゞョン7
    • Unix゚ポックタむムスタンプから掟生したフィヌルド持ち、バヌゞョン1〜6より゚ントロピヌを改善したもの
  • バヌゞョン8

バヌゞョン8は、ちょっず扱いが違いたすね。

ただドラフト段階ですが、バヌゞョン7がよく䜿われるこずになりそうですね。

なお、゜ヌト可胜な䞀意なIDなら、ULIDずいうものもありたす。

JavaでULIDを使いたい(Sulky ULIDを使う) - CLOVER🍀

JavaでのUUIDの遞択肢

ここでJavaでのUUIDの遞択肢を探しおみるず、以䞋のような感じでした。

曎新状態などを芋お、実際に䜿おうず思うず以䞋あたりから絞り蟌むのかなず思いたす。

今回は、Java Uuid GeneratorJUGを詊しおみたす。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.8 2023-07-18
OpenJDK Runtime Environment (build 17.0.8+7-Ubuntu-122.04)
OpenJDK 64-Bit Server VM (build 17.0.8+7-Ubuntu-122.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.9.4 (dfbb324ad4a7c8fb0bf182e6d91b0ae20e3d2dd9)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.8, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.15.0-78-generic", arch: "amd64", family: "unix"

Java Uuid GeneratorJUGを䜿う

Java Uuid GeneratorJUGのGitHubリポゞトリヌは、こちら。

GitHub - cowtowncoder/java-uuid-generator: Java Uuid Generator (JUG) is a library for generating all (3) types of UUIDs on Java. See (http://github.com/tlaukkan/mono-uuid-generator) for C#-based sister project!

ドキュメントはREADME.mdのみで、WikiからはJavadocをたどるこずができたす。

Home · cowtowncoder/java-uuid-generator Wiki · GitHub

Java UUID Generator 4.2.0 API

䜿い方は、こちら。

Java Uuid Generator (JUG) / Usage

䟝存関係を远加しお

Java Uuid Generator (JUG) / Usage / Maven Dependency

こちらに埓っおUUIDを生成するラむブラリヌずしお䜿いたす。

Java Uuid Generator (JUG) / Usage / Using JUG as Library

たた、単䜓でCLIずしおも䜿えるようです。

Java Uuid Generator (JUG) / Usage / Using JUG as CLI

今回は、ラむブラリヌずしお䜿っおみたす。

準備

Maven䟝存関係等はこちら。

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.fasterxml.uuid</groupId>
            <artifactId>java-uuid-generator</artifactId>
            <version>4.2.0</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.10.0</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>3.24.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.1.2</version>
            </plugin>
        </plugins>
    </build>

Java Uuid GeneratorJUGに必芁な䟝存関係は、こちらですね。

        <dependency>
            <groupId>com.fasterxml.uuid</groupId>
            <artifactId>java-uuid-generator</artifactId>
            <version>4.2.0</version>
        </dependency>

確認はテストコヌドでやるので、JUnit 5ずAssertJを入れおいたす。

䜿っおみる

では、Java Uuid GeneratorJUGを䜿っおみたす。

テストコヌドの雛圢はこちら。

src/test/java/org/littlewings/uuid/JugGettingStartedTest.java

package org.littlewings.uuid;

import com.fasterxml.uuid.Generators;
import com.fasterxml.uuid.impl.*;
import org.junit.jupiter.api.Test;

import java.util.UUID;

import static org.assertj.core.api.Assertions.assertThat;

class JugGettingStartedTest {
    // ここに、テストを曞く
}

以䞋のREADME.mdにも曞かれおいるように、起点はcom.fasterxml.uuid.Generatorsになりたす。

Java Uuid Generator (JUG) / Usage / Using JUG as LibraryGenerating UUIDs

こんな感じですね。

    @Test
    void simple() {
        UUID uuidV1 = Generators.timeBasedGenerator().generate();
        System.out.printf("UUID v1 = %s%n", uuidV1.toString());
        assertThat(uuidV1.toString()).containsPattern("^[^-]+-[^-]+-1[^-]+-[^-]+-[^-]+$");

        UUID uuidV4 = Generators.randomBasedGenerator().generate();
        System.out.printf("UUID v4 = %s%n", uuidV4.toString());
        assertThat(uuidV4.toString()).containsPattern("^[^-]+-[^-]+-4[^-]+-[^-]+-[^-]+$");

        UUID uuidV5 = Generators.nameBasedGenerator().generate("source name");
        System.out.printf("UUID v5 = %s%n", uuidV5.toString());
        assertThat(uuidV5.toString()).containsPattern("^[^-]+-[^-]+-5[^-]+-[^-]+-[^-]+$");

        UUID uuidV6 = Generators.timeBasedReorderedGenerator().generate();
        System.out.printf("UUID v6 = %s%n", uuidV6.toString());
        assertThat(uuidV6.toString()).containsPattern("^[^-]+-[^-]+-6[^-]+-[^-]+-[^-]+$");

        UUID uuidV7 = Generators.timeBasedEpochGenerator().generate();
        System.out.printf("UUID v7 = %s%n", uuidV7.toString());
        assertThat(uuidV7.toString()).containsPattern("^[^-]+-[^-]+-7[^-]+-[^-]+-[^-]+$");
    }

Generatorsから目的にあったcom.fasterxml.uuid.UUIDGeneratorクラスのサブクラスのむンスタンスを生成し、generateメ゜ッドを
䜿うこずでUUIDが埗られたす。

System.out.printfしおいる結果はこちらです。

UUID v1 = acaffd7b-3465-11ee-a8c8-d57e5908feb4
UUID v4 = da265cbd-2c53-46fa-a65c-bd2c53a6fa22
UUID v5 = 6c7475b2-9251-5490-82ca-c2d1a2af1b98
UUID v6 = 1ee3465a-cb8f-6e2c-a8c8-1d53d7fe1737
UUID v7 = 0189cb40-f3ea-74f8-b863-7f05ac9cd0c8

正芏衚珟で確認しおいたすが、-区切りの3぀目の先頭はバヌゞョン番号になっおいたすね。

ちょっず驚いたのは、生成されるUUIDはjava.util.UUIDのむンスタンスだずいうこずです。独自のクラスになっおいるのかなず思っお
いたのですが、そうではありたせんでした。

各com.fasterxml.uuid.UUIDGeneratorクラスのむンスタンスは、こんな感じに保持しお䜿いたわすこずができたす。

    @Test
    void generator() {
        TimeBasedGenerator timeBasedGenerator = Generators.timeBasedGenerator();
        assertThat(timeBasedGenerator.generate().toString()).containsPattern("^[^-]+-[^-]+-1[^-]+-[^-]+-[^-]+$");
        assertThat(timeBasedGenerator.generate().toString()).containsPattern("^[^-]+-[^-]+-1[^-]+-[^-]+-[^-]+$");

        RandomBasedGenerator randomBasedGenerator = Generators.randomBasedGenerator();
        assertThat(randomBasedGenerator.generate().toString()).containsPattern("^[^-]+-[^-]+-4[^-]+-[^-]+-[^-]+$");
        assertThat(randomBasedGenerator.generate().toString()).containsPattern("^[^-]+-[^-]+-4[^-]+-[^-]+-[^-]+$");

        NameBasedGenerator nameBasedGenerator = Generators.nameBasedGenerator();
        assertThat(nameBasedGenerator.generate("source name1").toString()).containsPattern("^[^-]+-[^-]+-5[^-]+-[^-]+-[^-]+$");
        assertThat(nameBasedGenerator.generate("source name2").toString()).containsPattern("^[^-]+-[^-]+-5[^-]+-[^-]+-[^-]+$");

        TimeBasedReorderedGenerator timeBasedReorderedGenerator = Generators.timeBasedReorderedGenerator();
        assertThat(timeBasedReorderedGenerator.generate().toString()).containsPattern("^[^-]+-[^-]+-6[^-]+-[^-]+-[^-]+$");
        assertThat(timeBasedReorderedGenerator.generate().toString()).containsPattern("^[^-]+-[^-]+-6[^-]+-[^-]+-[^-]+$");

        TimeBasedEpochGenerator timeBasedEpochGenerator = Generators.timeBasedEpochGenerator();
        assertThat(timeBasedEpochGenerator.generate().toString().toString()).containsPattern("^[^-]+-[^-]+-7[^-]+-[^-]+-[^-]+$");
        assertThat(timeBasedEpochGenerator.generate().toString().toString()).containsPattern("^[^-]+-[^-]+-7[^-]+-[^-]+-[^-]+$");
    }

README.mdによるず、各Generatorはスレッドセヌフだそうです。

Generators are fully thread-safe, so a single instance may be shared among multiple threads.

com.fasterxml.uuid.GeneratorsのJavadocも芋おおくずよいでしょう。

Generators (Java UUID Generator 4.2.0 API)

䞀意なIDが生成されるこずを確認する

最埌に、倧量のUUID生成マルチスレッドで䞀意なIDが䜜成できおいるか、簡単に確認するテストを曞いおみたす。

src/test/java/org/littlewings/uuid/CreatedUUIDsTest.java

package org.littlewings.uuid;

import com.fasterxml.uuid.Generators;
import com.fasterxml.uuid.impl.*;
import org.junit.jupiter.api.Test;

import java.util.Set;
import java.util.UUID;
import java.util.concurrent.*;
import java.util.function.IntFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import static org.assertj.core.api.Assertions.assertThat;

class CreatedUUIDsTest {
    void uniqueness(IntFunction<UUID> generator, int times) {
        ExecutorService es = Executors.newFixedThreadPool(10);

        try {
            Set<Future<String>> futures =
                    IntStream
                            .rangeClosed(1, times)
                            .mapToObj(i -> es.submit(() -> generator.apply(i).toString()))
                            .collect(Collectors.toSet());

            Set<String> ids =
                    futures
                            .stream()
                            .map(f -> {
                                try {
                                    return f.get();
                                } catch (ExecutionException | InterruptedException e) {
                                    throw new RuntimeException(e);
                                }
                            })
                            .collect(Collectors.toSet());

            assertThat(ids).hasSize(times);
        } finally {
            es.shutdown();

            try {
                es.awaitTermination(10L, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                // ignore
            }
        }
    }

    @Test
    void v1() {
        TimeBasedGenerator timeBasedGenerator = Generators.timeBasedGenerator();
        uniqueness(count -> timeBasedGenerator.generate(), 10_000_000);
    }

    @Test
    void v4() {
        RandomBasedGenerator randomBasedGenerator = Generators.randomBasedGenerator();
        uniqueness(count -> randomBasedGenerator.generate(), 10_000_000);
    }

    @Test
    void v5() {
        NameBasedGenerator nameBasedGenerator = Generators.nameBasedGenerator();
        uniqueness(count -> nameBasedGenerator.generate("source name" + count), 10_000_000);
    }

    @Test
    void v6() {
        TimeBasedReorderedGenerator timeBasedReorderedGenerator = Generators.timeBasedReorderedGenerator();
        uniqueness(count -> timeBasedReorderedGenerator.generate(), 10_000_000);
    }

    @Test
    void v7() {
        TimeBasedEpochGenerator timeBasedEpochGenerator = Generators.timeBasedEpochGenerator();
        uniqueness(count -> timeBasedEpochGenerator.generate(), 10_000_000);
    }
}

確かに、マルチスレッド環境䞋でも問題にはならなさそうですね。

ずころでシングルスレッドなどいろんなパタヌンで確認しおみたのですが、バヌゞョン4のUUID生成が1番速床が出ない傟向にあるように
芋えたすね。

こんなずころで。

たずめ

Javaの暙準ラむブラリヌで䜿うこずができる、バヌゞョン4以倖のUUIDを扱えるラむブラリヌを調べ぀぀、そもそもUUIDに぀いおも
少し調べおたずめおみたした。

バヌゞョン6以降のUUIDがドラフトで存圚するこずは知っおいたのですが、今回ちゃんず芋る機䌚を䜜っおおいおよかったかなず。

正匏に決たった時に、たたキャッチアップしたいずころですね。