-
Notifications
You must be signed in to change notification settings - Fork 47
/
flakes.clj
129 lines (93 loc) · 3.99 KB
/
flakes.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
(ns ^{:author "Bruno Bonacci (@BrunoBonacci)"
:doc
"
Flakes are like snowflakes, no two are the same.
------------------------------------------------
This is an implementation of a 192-bits unique ids which has the
following characteristics:
- **Monotonic IDs**
Every new ID is larger than the previous one. The idea is that
the 'happens before' relationship applies to these IDs. If you
observe a Flake and you create a new one in the same thread, the
new Flake is going to be larger than the previous one. There is
no synchronisation and it uses a wall clock as well as a
monotonic clock as part of the generation, therefore Flakes
created across processes/machines might suffer from clock skew
and hard reset. Generally the following condition should apply
for all Flakes `flake0 < flake1 < flake2 < ... < flakeN`
- **Two components: one time-based (64 bits), one random (128 bits)**
The most significant bits are time based and they use a monotonic
clock with nanosecond resolution. The next 128 bits are randomly
generated.
- **Random-based**
The Random component is built with a PRNG for speed.
It uses 128 full bits, more bits than `java.util.UUID/randomUUID`
which has 6 bits reserved for versioning and type, therefore
effectively only using 122 bits.
- **Homomorphic representation**
Whether you choose to have a bytes representation or string representation
it uses an encoding which maintain the ordering.
Which it means that:
if `flake1 < flake2` then `flake1.toString() < flake2.toString()`
Internally it uses a NON STANDARD base64 encoding to preserve the ordering.
Unfortunately, the standard Base64 encoding doesn't preserve this property
as defined in https://en.wikipedia.org/wiki/Base64.
- **Web-safe string representation**.
The string representation uses only characters which are web-safe and can
be put in a URL without the need of URL encoding.
- **Fast**, speed is important so we target under 50 nanosecond for 1 id.
These are the performances measured with Java 1.8.0_232 for the creation
of a new Flake.
Evaluation count : 1570017720 in 60 samples of 26166962 calls.
Execution time mean : 36.429623 ns
Execution time std-deviation : 0.519843 ns
Execution time lower quantile : 35.684111 ns ( 2.5%)
Execution time upper quantile : 37.706974 ns (97.5%)
Overhead used : 2.090673 ns
Found 4 outliers in 60 samples (6.6667 %)
low-severe 4 (6.6667 %)
Variance from outliers : 1.6389 % Variance is slightly inflated by outliers
These are the performances for creating an new Flake and turn it into a string
Evaluation count : 755435400 in 60 samples of 12590590 calls.
Execution time mean : 78.861983 ns
Execution time std-deviation : 1.006261 ns
Execution time lower quantile : 77.402593 ns ( 2.5%)
Execution time upper quantile : 81.006901 ns (97.5%)
Overhead used : 1.881732 ns
"}
com.brunobonacci.mulog.flakes
(:import com.brunobonacci.mulog.core.Flake))
(defn flake
"A time-ordered, pseudo-random, Universal ID of 192 bits"
[]
(Flake/flake))
(defn snowflake
"A time-ordered, pseudo-random, Universal ID of 192 bits represented as a 32bytes strings"
[]
(str (Flake/flake)))
(defn flake-time
"Returns the timestamp in nanoseconds"
[^Flake flake]
(.getTimestampNanos flake))
(defn flake-hex
"Hexadecimal representation"
[^Flake flake]
(Flake/formatFlakeHex flake))
;; Flake representation is just a string base64 homomorphic
(defmethod print-method Flake
[f ^java.io.Writer w]
(.write w "#mulog/flake ")
(print-method (str f) w))
(defn flake?
"returns true if `f` is an instance of a Flake."
[f]
(instance? Flake f))
(defmethod print-dup Flake
[f ^java.io.Writer w]
(print-method f w))
;; Reader macro data reader
(defn read-method
"Reader method"
[flake]
{:pre [(string? flake)]}
(Flake/parseFlake flake))