Airframe

Airframe

  • Docs
  • Blog
  • Release Notes
  • GitHub

›Utilities

Resources

  • Overview
  • Articles
  • Release Notes
  • Logos

Framework

  • airframe-di: Dependency Injection
  • Airframe RPC
  • airframe-http: Creating REST Service
  • airframe-rx: ReactiveX interface
  • AirSpec: Testing Framework

Core Modules

  • airframe-codec: Schema-On-Read Object Serializer
  • airframe-config: Application Config Flow
  • airframe-control: Retry/Rate Control
  • airframe-log: Application Logger
  • airframe-metrics: Human-Friendly Measures for Time and Data Size
  • airframe-surface: Object Shape Inspector

Utilities

  • airframe-benchmark: JMH Benchmark
  • airframe-canvas: Off-Heap Memory Manager
  • airframe-fluentd: Fluentd Logger
  • airframe-http-recorder: Web Request/Response Recorder
  • airframe-jdbc: JDBC Connection Pool
  • airframe-jmx: JMX Application Monitor
  • airframe-json: Pure-Scala JSON Parser
  • airframe-launcher: Command-Line Program Launcher
  • airframe-msgpack: Pure-Scala MessagePack Parser
  • airframe-parquet: Parquet Columnar File Reader and Writer
  • airframe-sql: SQL Parser
  • airframe-ulid: ULID Generator

airframe-ulid: ULID Generator

airframe-ulid is an ULID generator for Scala and Scala.js.

ULID (Universally Unique Lexicographically Sortable Identifier) has the following good characteristics:

  • 128-bit compatibility with UUID
  • 1.21e+24 unique ULIDs per millisecond
  • Lexicographically sortable
  • Canonically encoded as a 26 character string, as opposed to the 36 character UUID Uses Crockford's base32 for better efficiency and readability (5 bits per character)
  • Case insensitive
  • No special characters (URL safe)
  • Monotonic sort order (correctly detects and handles the same millisecond)

Source code of airframe-ulid at GitHub

ULID Format

The ULID is a 26-character string encoded with Crockford's Base32, which excludes letters like I, L, O, and U to avoid confusion.

 01AN4Z07BY      79KA1307SR9X4MV3

|----------|    |----------------|
 Timestamp          Randomness
   48bits             80bits

Timestamp

  • 48-bit integer
  • UNIX-time in milliseconds
  • Won't run out of space until the year 10889 AD.

Randomness

  • 80 bits
  • Cryptographically secure random value.

Binary Format

ULIDs can be encoded as 128-bit values, using network-byte order (big-endian, MSB first). In JVM, it can be two 64-bit Long values, or a byte array of size 16. Technically, 26 characters of Crockford's Base32 can represent 130-bit values, but ULID strictly uses 128 bits, so the largest ULID string is 7ZZZZZZZZZZZZZZZZZZZZZZZZZ, the unix time of which is 281474976710655.

Usage

Maven Central

build.sbt

libraryDependencies += "org.wvlet.airframe" %% "airframe-ulid" % "(version)"

// For Scala.js
libraryDependencies ++= "org.wvlet.airframe" %%% "airframe-ulid" % "(version)"

ULID can be generated with ULID.newULID method:

import wvlet.airframe.ulid.ULID

// Generate a new ULID
val ulid: ULID = ULID.newULID

// ULID.toString produces the String representation of ULID
println(ulid)             // 01F3J0G1M4WQRBHGZ6HCF6JA0K
println(ulid.epochMillis) // 1618733434500

// You can generate a ULID string at ease with newULIDString
ULID.newULIDString

// Parse the string representation of ULIDs 
val ulid2 = ULID.fromString("01F3J0G1MD4QX7Z5QB148QNH4R")
ulid2.epochMillis // 1618733434509

// Parse the binary representation of ULIDs
ULID.fromBytes(...)


// Create an ULID from a given unixtime (milliseconds)
val unixTimeMillis = System.currentTimeMillis() - 1000
ULID.ofMillis(unixTimeMillis)

// Create an ULID from a given unixtime (48-bits, milliseconds) and a random value (80-bits) 
ULID.of(unixTimeMillis, randHi, randLow)

ULID.newULID will produce monotonically increasing ULIDs in a thread-safe manner. If ULIDs are generated within the same millisecond, airframe-ulid will increment the random part of ULID by 1 to enforce the ordering:

01F3HZ9V4BHVHJMMETE0MFBQKH
01F3HZ9V4BHVHJMMETE0MFBQKJ // <- random part was incremented by 1
01F3HZ9V4BHVHJMMETE0MFBQKK // <- random part was incremented by 1
01F3HZ9V4BHVHJMMETE0MFBQKM // <- random part was incremented by 1
01F3HZ9V4BHVHJMMETE0MFBQKN // <- random part was incremented by 1
01F3HZ9V4C8SWD21A4SCM4NMD8 // <- millisecond is changed
...

Empirically, generating monotonically increasing ULIDs will reduce the probability of having ULID conflicts than using completely random ULIDs, according to this report.

If you use multiple machines, however, there is no guarantee that generated ULIDs will be monotonically increasing values. We recommend having a single coordinator to generate ULIDs if you need strict ordering.

Serialization

ULID can be serialized and deserialized with airframe-codec:

case class Transaction(id:ULID, status:String)

val codec = MessageCodec.of[Transaction]

val json = codec.toJson(Transaction(id = ULID.newULID, status = "committed")) 
// {"id":"01F3HZ9V454BC466FPDSBTZ64G", "status":"committed"}

val txx = codec.fromJson(json)
// Transaction(01F3HZ9V454BC466FPDSBTZ64G,committed)

Performance

airframe-ulid can produce 5 million ULIDs / sec. As of April 2021, airframe-ulid is the fastest ULID generator in Scala:

$ ./sbt
> benchmark/run bench ulid

Benchmark                    Mode  Cnt        Score        Error  Units
Airframe.generateMonotonic  thrpt   10  5514410.915 ± 534378.141  ops/s
Chatwork.generate           thrpt   10  2063919.975 ±  62641.190  ops/s
Chatwork.generateMonotonic  thrpt   10  4715112.355 ± 680190.587  ops/s
UUID.generate               thrpt   10  3056452.879 ± 121768.161  ops/s

Comparison targets:

  • scala-ulid (by Chatwork)
  • Java's UUID.randomUUID()

Benchmark code using JMH.

Scala.js-specific Note

For Scala.js 1.10.0 or later, ULID generator uses scalajs-java-securerandom library for generating ULIDs using a Cryptographically-Secure-Pseudo-Random-Number-Generator (CSPRNG). If none of Node.js crypto module or Web Crypto API in Web browsers is available, airframe-ulid falls back to a non-secure random generator, which is useful only for testing purpose at JSDOM environment, which doesn't support crypto module.

← airframe-sql: SQL Parser
  • ULID Format
    • Timestamp
    • Randomness
    • Binary Format
  • Usage
    • Serialization
  • Performance
    • Scala.js-specific Note
Airframe
Docs
Documentation
Community
Gitter Chat
More
GitHubStar
airframe logo
Copyright © 2024 wvlet.org