nuid
is an OTP library to generate unique identifiers.
Add nuid
to your project dependencies.
%%% e.g., rebar.config
{deps, [
{nuid, {git, "[email protected]:nomasystems/nuid.git", {branch, "main"}}}
]}.
Traditionally we've been using uuid1 as defined on rfc4122 and later we transitioned to uuid6 as defined on draft-peabody-dispatch-new-uuid-format.
The latest is preferred since it can be ordered by the time of creation.
Recently we had the necessity of having identifiers with more entropy. Specifically, after a security revision, we've been requested to use at least the same random bits as uuid4 (122). With that in mind, we started searching for standards covering these requirements:
Must be able to order identifiers by the time of creation. Identifiers must have at least 122 bits of cryptographically strong random numbers.
- Some optional requirements would make the identifiers more appealing:
- Being unique (at least having a low collision probability).
- Carry information about where the identifier has been created.
We found these two great de-facto standards:
But they lack the second requirement, the one about 122 bits of cryptographically strong random numbers. So we came up with nuid2.
nuid2 has these properties:
- Lexicographically sortable.
- Sixteen cryptographically strong random bytes. (128bits)
- It is unique (at least has a low collision probability).
- It carries 3 bytes of information reserved for origin information.
- No longer than uuid (36 bytes)
- URL safe
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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| POSIX time in seconds |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Unique sortable integer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Origin information (node) | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
| |
+ +
| Cryptographically strong random bits (128) |
+ +
| |
+ +-+-+-+-+-+-+-+
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 1: nuid2 Field and bit Layout
We encode this information on what we'll call base64'. That is a url safe, sortable base64 representation.
This is base64' alphabet in order:
-, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
_,
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z
We took Erlang/OTP base64 module and modified it to meet these properties. It's a pretty amazing code. Thanks.
We also came up with nuid1, having just these properties
- Lexicographically sortable. It can be used on systems that uuid6 were used. All nuid1 identifiers generated after a uuid6 has been generated will be lexicographically greater.
- 16 cryptographically strong random bytes. (128bits)
- It is unique (at least has a low collision probability).
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 2 3 4 5 6
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Hex unique time in us |-| base64' 16 random bytes |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Figure 2: nuid1 Field and Byte Layout
(*) where dash at byte 14 is the 45 ASCII character
nuid
exposes utilities via its API that allows you to:
Function | Description |
---|---|
nuid:uuid1/0 |
Generates RFC 4122 uuid v1 |
nuid:uuid3/2 |
Generates RFC 4122 uuid v3 |
nuid:uuid4/0 |
Generates RFC 4122 uuid v4 |
nuid:uuid5/2 |
Generates RFC 4122 uuid v5 |
nuid:uuid6/0 |
Generates draft-ietf-uuidrev-rfc4122bis uuid v6 |
nuid:uuid7/0 |
Generates draft-ietf-uuidrev-rfc4122bis uuid v7 |
nuid:uuid8/1 |
Generates draft-ietf-uuidrev-rfc4122bis uuid v8 |
nuid:uuid8/3 |
Generates draft-ietf-uuidrev-rfc4122bis uuid v8 |
nuid:nil_uuid/0 |
Generates RFC 4122 nil uuid |
nuid:max_uuid/0 |
Generates draft-ietf-uuidrev-rfc4122bis max uuid |
nuid:uuid1_info/1 |
Gets info from uuid1 (generation time, node, unique) |
nuid:uuid6_info/1 |
Gets info from uuid6 (generation time, node, unique) |
nuid:uuid7_info/1 |
Gets info from uuid7 (generation time) |
nuid:nuid1/0 |
Generates Noma nuid1 |
nuid:nuid2/0 |
Generates Noma nuid2 |
nuid:nuid1_info/1 |
Gets info from nuid1 (generation time) |
nuid:nuid2_info/1 |
Gets info from nuid2 (generation time, node) |
1> nuid:uuid1().
<<"07e826fe-ed86-1060-8000-00001430cc44">>
2> nuid:uuid3(url, <<"https://www.nomasystems.com">>).
<<"67805345-d49e-3058-9ad3-160686e8ee2a">>
3> nuid:uuid4().
<<"37a9e737-f680-44a9-b83d-a517ec758b75">>
4> nuid:uuid5(dns, <<"nomasystems.com">>).
<<"cefe05b2-95ca-5b0a-ad06-9b3f2b38e532">>
5> nuid:uuid6().
<<"0607e826-ff71-6410-8000-00002430cc44">>
6> nuid:uuid7().
<<"018b3d7a-9f9a-7577-adb2-08761e3d87f7">>
7> nuid:uuid8(<<16#f, 16#e, 16#d, 16#c, 16#b, 16#a, 16#9, 16#8, 16#7, 16#6, 16#5, 16#4, 16#3, 16#2, 16#1, 16#0>>).
<<"0f0e0d0c-0b0a-8908-8706-050403020100">>
8> nuid:nuid1().
<<"607e826ff7388-4WX7g2peZpWw9QAIpkRp-F">>
9> nuid:nuid2().
<<"OHtpP-----Fkn3F6JaT5Kxnm_NAiDzFgGMzc">>
10> nuid:uuid1_info(nuid:uuid1()).
{uuidInfo,{{2023,10,17},{11,52,8}},5,nonode@nohost}
11> nuid:uuid6_info(nuid:uuid6()).
{uuidInfo,{{2023,10,17},{11,52,8}},6,nonode@nohost}
12> nuid:nuid1_info(nuid:nuid1()).
{{2023,10,17},{11,52,8}}
13> nuid:nuid2_info(nuid:nuid2()).
{nonode@nohost,{{2023,10,17},{11,52,8}}}
14> nuid:nil_uuid(nuid:nuid2()).
<<"00000000-0000-0000-0000-000000000000">>
15> nuid:max_uuid(nuid:nuid2()).
<<"FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF">>.
Run a rebar3 shell using the bench
profile:
rebar3 as bench shell
Run the following command:
1> nuid_bench:bench().
This benchmark compares different unique identifiers generated by nuid
.
nuid1 creation time: 1.83 (us)
nuid2 creation time: 1.55 (us)
uuid1 creation time: 0.92 (us)
uuid4 creation time: 1.69 (us)
uuid6 creation time: 0.91 (us)
uuid7 creation time: 1.82 (us)
Any doubt or suggestion? Please, check out our issue tracker.