mirror of
https://github.com/penpot/penpot.git
synced 2025-01-22 14:39:45 -05:00
🎉 Replace current uuidv1 with custom v8
This commit is contained in:
parent
abfca5c89a
commit
fbfcb827ed
11 changed files with 212 additions and 105 deletions
|
@ -33,4 +33,4 @@
|
|||
{:src-dirs ["dev/java"]
|
||||
:class-dir class-dir
|
||||
:basis basis
|
||||
:javac-opts ["-source" "11" "-target" "11"]}))
|
||||
:javac-opts ["-source" "17" "-target" "17"]}))
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
com.github.luben/zstd-jni {:mvn/version "1.5.2-3"}
|
||||
org.clojure/data.fressian {:mvn/version "1.0.0"}
|
||||
|
||||
|
||||
io.prometheus/simpleclient {:mvn/version "0.15.0"}
|
||||
io.prometheus/simpleclient_hotspot {:mvn/version "0.15.0"}
|
||||
io.prometheus/simpleclient_jetty {:mvn/version "0.15.0"
|
||||
|
|
|
@ -34,7 +34,9 @@
|
|||
[mockery.core :as mk]
|
||||
[promesa.core :as p]
|
||||
[yetti.request :as yrq])
|
||||
(:import org.postgresql.ds.PGSimpleDataSource))
|
||||
(:import
|
||||
java.util.UUID
|
||||
org.postgresql.ds.PGSimpleDataSource))
|
||||
|
||||
(def ^:dynamic *system* nil)
|
||||
(def ^:dynamic *pool* nil)
|
||||
|
@ -128,8 +130,8 @@
|
|||
|
||||
(defn mk-uuid
|
||||
[prefix & args]
|
||||
(uuid/namespaced uuid/zero (apply str prefix args)))
|
||||
|
||||
(UUID/nameUUIDFromBytes (-> (apply str prefix args)
|
||||
(.getBytes "UTF-8"))))
|
||||
;; --- FACTORIES
|
||||
|
||||
(defn create-profile*
|
||||
|
|
15
common/build.clj
Normal file
15
common/build.clj
Normal file
|
@ -0,0 +1,15 @@
|
|||
(ns build
|
||||
(:refer-clojure :exclude [compile])
|
||||
(:require [clojure.tools.build.api :as b]))
|
||||
|
||||
(def class-dir "target/classes")
|
||||
(def basis (b/create-basis {:project "deps.edn"}))
|
||||
|
||||
(defn clean [_]
|
||||
(b/delete {:path "target"}))
|
||||
|
||||
(defn compile [_]
|
||||
(b/javac {:src-dirs ["src"]
|
||||
:class-dir class-dir
|
||||
:basis basis
|
||||
:javac-opts ["-source" "17" "-target" "17"]}))
|
|
@ -28,7 +28,6 @@
|
|||
:exclusions [org.clojure/data.json]}
|
||||
|
||||
frankiesardo/linked {:mvn/version "1.3.0"}
|
||||
danlentz/clj-uuid {:mvn/version "0.1.9"}
|
||||
commons-io/commons-io {:mvn/version "2.11.0"}
|
||||
com.sun.mail/jakarta.mail {:mvn/version "2.0.1"}
|
||||
|
||||
|
@ -36,7 +35,7 @@
|
|||
fipp/fipp {:mvn/version "0.6.26"}
|
||||
io.aviso/pretty {:mvn/version "1.1.1"}
|
||||
environ/environ {:mvn/version "1.2.0"}}
|
||||
:paths ["src"]
|
||||
:paths ["src" "target/classes"]
|
||||
:aliases
|
||||
{:dev
|
||||
{:extra-deps
|
||||
|
@ -48,6 +47,10 @@
|
|||
mockery/mockery {:mvn/version "RELEASE"}}
|
||||
:extra-paths ["test" "dev"]}
|
||||
|
||||
:build
|
||||
{:extra-deps {io.github.clojure/tools.build {:git/tag "v0.8.1" :git/sha "7d40500"}}
|
||||
:ns-default build}
|
||||
|
||||
:test
|
||||
{:extra-paths ["test"]
|
||||
:extra-deps
|
||||
|
|
81
common/src/app/common/UUIDv8.java
Normal file
81
common/src/app/common/UUIDv8.java
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
Copyright (c) UXBOX Labs SL
|
||||
|
||||
This file contains a UUIDv8 with conformance with
|
||||
https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format
|
||||
|
||||
It has the following characteristics:
|
||||
- time ordered
|
||||
- 48bits timestamp
|
||||
- custom epoch: milliseconds since 2022-01-01T00:00:00
|
||||
- 14bits monotonic clockseq (allows generate 16k uuids/ms)
|
||||
- mostly static random 60 bits (initialized at class load or clock regression)
|
||||
|
||||
This is results in a constantly increasing, sortable, very fast uuid impl.
|
||||
*/
|
||||
|
||||
package app.common;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import java.time.Clock;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UUIDv8 {
|
||||
public static final long timeRef = 1640991600L * 1000L; // ms since 2022-01-01T00:00:00
|
||||
public static final long clockSeqMax = 16384L; // 14 bits space
|
||||
public static final Clock clock = Clock.systemUTC();
|
||||
|
||||
public static long baseMsb;
|
||||
public static long baseLsb;
|
||||
public static long clockSeq = 0L;
|
||||
public static long lastTs = 0L;
|
||||
|
||||
public static SecureRandom srandom = new java.security.SecureRandom();
|
||||
|
||||
public static synchronized void initializeSeed() {
|
||||
baseMsb = 0x0000_0000_0000_8000L; // Version 8
|
||||
baseLsb = srandom.nextLong() & 0x0fff_ffff_ffff_ffffL | 0x8000_0000_0000_0000L; // Variant 2
|
||||
}
|
||||
|
||||
static {
|
||||
initializeSeed();
|
||||
}
|
||||
|
||||
public static synchronized UUID create(final long ts, final long clockSeq) {
|
||||
long msb = (baseMsb
|
||||
| ((ts << 16) & 0xffff_ffff_ffff_0000L)
|
||||
| ((clockSeq >>> 2) & 0x0000_0000_0000_0fffL));
|
||||
long lsb = baseLsb | ((clockSeq << 60) & 0x3000_0000_0000_0000L);
|
||||
return new UUID(msb, lsb);
|
||||
}
|
||||
|
||||
public static synchronized UUID create() {
|
||||
while (true) {
|
||||
long ts = clock.millis() - timeRef;
|
||||
|
||||
// Protect from clock regression
|
||||
if ((ts - lastTs) < 0) {
|
||||
initializeSeed();
|
||||
clockSeq = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastTs == ts) {
|
||||
if (clockSeq < clockSeqMax) {
|
||||
clockSeq++;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
lastTs = ts;
|
||||
clockSeq = 0;
|
||||
}
|
||||
|
||||
return create(ts, clockSeq);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,12 +7,12 @@
|
|||
(ns app.common.uuid
|
||||
(:refer-clojure :exclude [next uuid zero?])
|
||||
(:require
|
||||
#?(:clj [app.common.data.macros :as dm])
|
||||
#?(:clj [clj-uuid :as impl])
|
||||
#?(:clj [clojure.core :as c])
|
||||
#?(:cljs [app.common.uuid-impl :as impl])
|
||||
#?(:cljs [cljs.core :as c]))
|
||||
#?(:clj (:import java.util.UUID)))
|
||||
#?(:clj (:import
|
||||
java.util.UUID
|
||||
app.common.UUIDv8)))
|
||||
|
||||
(def zero #uuid "00000000-0000-0000-0000-000000000000")
|
||||
|
||||
|
@ -22,32 +22,31 @@
|
|||
|
||||
(defn next
|
||||
[]
|
||||
#?(:clj (impl/v1)
|
||||
:cljs (impl/v1)))
|
||||
#?(:clj (UUIDv8/create)
|
||||
:cljs (impl/v8)))
|
||||
|
||||
(defn random
|
||||
"Alias for clj-uuid/v4."
|
||||
[]
|
||||
#?(:clj (impl/v4)
|
||||
#?(:clj (UUID/randomUUID)
|
||||
:cljs (impl/v4)))
|
||||
|
||||
#?(:clj
|
||||
(defn namespaced
|
||||
[ns data]
|
||||
(impl/v5 ns data)))
|
||||
|
||||
(defn uuid
|
||||
"Parse string uuid representation into proper UUID instance."
|
||||
[s]
|
||||
#?(:clj (UUID/fromString s)
|
||||
:cljs (c/uuid s)))
|
||||
:cljs (c/parse-uuid s)))
|
||||
|
||||
(defn custom
|
||||
([a] #?(:clj (UUID. 0 a) :cljs (c/uuid (impl/custom 0 a))))
|
||||
([b a] #?(:clj (UUID. b a) :cljs (c/uuid (impl/custom b a)))))
|
||||
([a] #?(:clj (UUID. 0 a) :cljs (c/parse-uuid (impl/custom 0 a))))
|
||||
([b a] #?(:clj (UUID. b a) :cljs (c/parse-uuid (impl/custom b a)))))
|
||||
|
||||
#?(:clj
|
||||
(dm/export impl/get-word-high))
|
||||
(defn get-word-high
|
||||
[id]
|
||||
(.getMostSignificantBits ^UUID id)))
|
||||
|
||||
#?(:clj
|
||||
(dm/export impl/get-word-low))
|
||||
(defn get-word-low
|
||||
[id]
|
||||
(.getLeastSignificantBits ^UUID id)))
|
||||
|
|
|
@ -92,104 +92,82 @@ goog.scope(function() {
|
|||
hexMap[buf[i++]]);
|
||||
}
|
||||
|
||||
const buff = new Uint8Array(16);
|
||||
self.v4 = (function () {
|
||||
const buff8 = new Uint8Array(16);
|
||||
|
||||
function v4() {
|
||||
fill(buff);
|
||||
buff[6] = (buff[6] & 0x0f) | 0x40;
|
||||
buff[8] = (buff[8] & 0x3f) | 0x80;
|
||||
return core.uuid(toHexString(buff));
|
||||
}
|
||||
return function v4() {
|
||||
fill(buff8);
|
||||
buff8[6] = (buff8[6] & 0x0f) | 0x40;
|
||||
buff8[8] = (buff8[8] & 0x3f) | 0x80;
|
||||
return core.uuid(toHexString(buff8));
|
||||
};
|
||||
})();
|
||||
|
||||
let initialized = false;
|
||||
let node;
|
||||
let clockseq;
|
||||
let lastms = 0;
|
||||
let lastns = 0;
|
||||
self.v8 = (function () {
|
||||
const buff = new ArrayBuffer(16);
|
||||
const buff8 = new Uint8Array(buff);
|
||||
const view = new DataView(buff);
|
||||
|
||||
function v1() {
|
||||
let cs = clockseq;
|
||||
const timeRef = 1640991600 * 1000; // ms since 2022-01-01T00:00:00
|
||||
const maxClockSeq = 16384n; // 14 bits space
|
||||
|
||||
if (!initialized) {
|
||||
const seed = new Uint8Array(8)
|
||||
fill(seed);
|
||||
let clockSeq = 0n;
|
||||
let lastTs = 0n;
|
||||
let baseMsb;
|
||||
let baseLsb;
|
||||
|
||||
// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
|
||||
node = [
|
||||
seed[0] | 0x01,
|
||||
seed[1],
|
||||
seed[2],
|
||||
seed[3],
|
||||
seed[4],
|
||||
seed[5]
|
||||
];
|
||||
|
||||
// Per 4.2.2, randomize (14 bit) clockseq
|
||||
cs = clockseq = (seed[6] << 8 | seed[7]) & 0x3fff;
|
||||
initialized = true;
|
||||
function initializeSeed() {
|
||||
fill(buff8);
|
||||
baseMsb = 0x0000_0000_0000_8000n; // Version 8;
|
||||
baseLsb = view.getBigUint64(8, false) & 0x0fff_ffff_ffff_ffffn | 0x8000_0000_0000_0000n; // Variant 2;
|
||||
}
|
||||
|
||||
let ms = Date.now();
|
||||
let ns = lastns + 1;
|
||||
let dt = (ms - lastms) + (ns - lastns) / 10000;
|
||||
|
||||
// Per 4.2.1.2, Bump clockseq on clock regression
|
||||
if (dt < 0) {
|
||||
cs = cs + 1 & 0x3fff;
|
||||
function currentTimestamp() {
|
||||
return BigInt.asUintN(64, "" + (Date.now() - timeRef));
|
||||
}
|
||||
|
||||
// Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
|
||||
// time interval
|
||||
if (dt < 0 || ms > lastms) {
|
||||
ns = 0;
|
||||
initializeSeed();
|
||||
|
||||
const create = function create(ts, clockSeq) {
|
||||
let msb = (baseMsb
|
||||
| ((ts << 16n) & 0xffff_ffff_ffff_0000n)
|
||||
| ((clockSeq >> 2n) & 0x0000_0000_0000_0fffn));
|
||||
let lsb = baseLsb | ((clockSeq << 60n) & 0x3000_0000_0000_0000n);
|
||||
view.setBigUint64(0, msb, false);
|
||||
view.setBigUint64(8, lsb, false);
|
||||
return core.uuid(toHexString(buff8));
|
||||
}
|
||||
|
||||
// Per 4.2.1.2 Throw error if too many uuids are requested
|
||||
if (ns >= 10000) {
|
||||
throw new Error("uuid v1 can't create more than 10M uuids/s")
|
||||
}
|
||||
const factory = function v8() {
|
||||
while (true) {
|
||||
let ts = currentTimestamp();
|
||||
|
||||
lastms = ms;
|
||||
lastns = ns;
|
||||
clockseq = cs;
|
||||
// Protect from clock regression
|
||||
if ((ts-lastTs) < 0) {
|
||||
initializeSeed();
|
||||
clockSeq = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Per 4.1.4 - Convert from unix epoch to Gregorian epoch
|
||||
ms += 12219292800000;
|
||||
if (lastTs === ts) {
|
||||
if (clockSeq < maxClockSeq) {
|
||||
clockSeq++;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
clockSeq = 0n;
|
||||
lastTs = ts;
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
return create(ts, clockSeq);
|
||||
}
|
||||
};
|
||||
|
||||
// `time_low`
|
||||
var tl = ((ms & 0xfffffff) * 10000 + ns) % 0x100000000;
|
||||
buff[i++] = tl >>> 24 & 0xff;
|
||||
buff[i++] = tl >>> 16 & 0xff;
|
||||
buff[i++] = tl >>> 8 & 0xff;
|
||||
buff[i++] = tl & 0xff;
|
||||
|
||||
// `time_mid`
|
||||
var tmh = (ms / 0x100000000 * 10000) & 0xfffffff;
|
||||
buff[i++] = tmh >>> 8 & 0xff;
|
||||
buff[i++] = tmh & 0xff;
|
||||
|
||||
// `time_high_and_version`
|
||||
buff[i++] = tmh >>> 24 & 0xf | 0x10; // include version
|
||||
buff[i++] = tmh >>> 16 & 0xff;
|
||||
|
||||
// `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
|
||||
buff[i++] = cs >>> 8 | 0x80;
|
||||
|
||||
// `clock_seq_low`
|
||||
buff[i++] = cs & 0xff;
|
||||
|
||||
// `node`
|
||||
for (var n = 0; n < 6; ++n) {
|
||||
buff[i + n] = node[n];
|
||||
}
|
||||
|
||||
return core.uuid(toHexString(buff));
|
||||
}
|
||||
|
||||
self.v1 = v1;
|
||||
self.v4 = v4;
|
||||
factory.create = create
|
||||
factory.initialize = initializeSeed;
|
||||
return factory;
|
||||
})();
|
||||
|
||||
self.custom = function formatAsUUID(mostSigBits, leastSigBits) {
|
||||
const most = mostSigBits.toString("16").padStart(16, "0");
|
||||
|
|
BIN
common/target/classes/app/common/UUIDv8.class
Normal file
BIN
common/target/classes/app/common/UUIDv8.class
Normal file
Binary file not shown.
27
common/test/app/common/uuid_test.cljc
Normal file
27
common/test/app/common/uuid_test.cljc
Normal file
|
@ -0,0 +1,27 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) UXBOX Labs SL
|
||||
|
||||
(ns app.common.uuid-test
|
||||
(:require
|
||||
[app.common.uuid :as uuid]
|
||||
[clojure.test :as t]
|
||||
[clojure.test.check.clojure-test :refer (defspec)]
|
||||
[clojure.test.check.generators :as gen]
|
||||
[clojure.test.check.properties :as props]))
|
||||
|
||||
(def uuid-gen
|
||||
(->> gen/large-integer (gen/fmap (fn [_] (uuid/next)))))
|
||||
|
||||
(defspec non-repeating-uuid-next-1 100000
|
||||
(props/for-all
|
||||
[uuid1 uuid-gen
|
||||
uuid2 uuid-gen
|
||||
uuid3 uuid-gen
|
||||
uuid4 uuid-gen
|
||||
uuid5 uuid-gen]
|
||||
(t/is (not= uuid1 uuid2 uuid3 uuid4 uuid5))))
|
||||
|
||||
|
|
@ -62,3 +62,4 @@
|
|||
"get-in" (bench-get-in)
|
||||
(println "available: str select-keys get-in")))
|
||||
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue