mirror of
https://github.com/penpot/penpot.git
synced 2025-02-02 04:19:08 -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"]
|
{:src-dirs ["dev/java"]
|
||||||
:class-dir class-dir
|
:class-dir class-dir
|
||||||
:basis basis
|
: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"}
|
com.github.luben/zstd-jni {:mvn/version "1.5.2-3"}
|
||||||
org.clojure/data.fressian {:mvn/version "1.0.0"}
|
org.clojure/data.fressian {:mvn/version "1.0.0"}
|
||||||
|
|
||||||
|
|
||||||
io.prometheus/simpleclient {:mvn/version "0.15.0"}
|
io.prometheus/simpleclient {:mvn/version "0.15.0"}
|
||||||
io.prometheus/simpleclient_hotspot {:mvn/version "0.15.0"}
|
io.prometheus/simpleclient_hotspot {:mvn/version "0.15.0"}
|
||||||
io.prometheus/simpleclient_jetty {:mvn/version "0.15.0"
|
io.prometheus/simpleclient_jetty {:mvn/version "0.15.0"
|
||||||
|
|
|
@ -34,7 +34,9 @@
|
||||||
[mockery.core :as mk]
|
[mockery.core :as mk]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
[yetti.request :as yrq])
|
[yetti.request :as yrq])
|
||||||
(:import org.postgresql.ds.PGSimpleDataSource))
|
(:import
|
||||||
|
java.util.UUID
|
||||||
|
org.postgresql.ds.PGSimpleDataSource))
|
||||||
|
|
||||||
(def ^:dynamic *system* nil)
|
(def ^:dynamic *system* nil)
|
||||||
(def ^:dynamic *pool* nil)
|
(def ^:dynamic *pool* nil)
|
||||||
|
@ -128,8 +130,8 @@
|
||||||
|
|
||||||
(defn mk-uuid
|
(defn mk-uuid
|
||||||
[prefix & args]
|
[prefix & args]
|
||||||
(uuid/namespaced uuid/zero (apply str prefix args)))
|
(UUID/nameUUIDFromBytes (-> (apply str prefix args)
|
||||||
|
(.getBytes "UTF-8"))))
|
||||||
;; --- FACTORIES
|
;; --- FACTORIES
|
||||||
|
|
||||||
(defn create-profile*
|
(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]}
|
:exclusions [org.clojure/data.json]}
|
||||||
|
|
||||||
frankiesardo/linked {:mvn/version "1.3.0"}
|
frankiesardo/linked {:mvn/version "1.3.0"}
|
||||||
danlentz/clj-uuid {:mvn/version "0.1.9"}
|
|
||||||
commons-io/commons-io {:mvn/version "2.11.0"}
|
commons-io/commons-io {:mvn/version "2.11.0"}
|
||||||
com.sun.mail/jakarta.mail {:mvn/version "2.0.1"}
|
com.sun.mail/jakarta.mail {:mvn/version "2.0.1"}
|
||||||
|
|
||||||
|
@ -36,7 +35,7 @@
|
||||||
fipp/fipp {:mvn/version "0.6.26"}
|
fipp/fipp {:mvn/version "0.6.26"}
|
||||||
io.aviso/pretty {:mvn/version "1.1.1"}
|
io.aviso/pretty {:mvn/version "1.1.1"}
|
||||||
environ/environ {:mvn/version "1.2.0"}}
|
environ/environ {:mvn/version "1.2.0"}}
|
||||||
:paths ["src"]
|
:paths ["src" "target/classes"]
|
||||||
:aliases
|
:aliases
|
||||||
{:dev
|
{:dev
|
||||||
{:extra-deps
|
{:extra-deps
|
||||||
|
@ -48,6 +47,10 @@
|
||||||
mockery/mockery {:mvn/version "RELEASE"}}
|
mockery/mockery {:mvn/version "RELEASE"}}
|
||||||
:extra-paths ["test" "dev"]}
|
:extra-paths ["test" "dev"]}
|
||||||
|
|
||||||
|
:build
|
||||||
|
{:extra-deps {io.github.clojure/tools.build {:git/tag "v0.8.1" :git/sha "7d40500"}}
|
||||||
|
:ns-default build}
|
||||||
|
|
||||||
:test
|
:test
|
||||||
{:extra-paths ["test"]
|
{:extra-paths ["test"]
|
||||||
:extra-deps
|
: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
|
(ns app.common.uuid
|
||||||
(:refer-clojure :exclude [next uuid zero?])
|
(:refer-clojure :exclude [next uuid zero?])
|
||||||
(:require
|
(:require
|
||||||
#?(:clj [app.common.data.macros :as dm])
|
|
||||||
#?(:clj [clj-uuid :as impl])
|
|
||||||
#?(:clj [clojure.core :as c])
|
#?(:clj [clojure.core :as c])
|
||||||
#?(:cljs [app.common.uuid-impl :as impl])
|
#?(:cljs [app.common.uuid-impl :as impl])
|
||||||
#?(:cljs [cljs.core :as c]))
|
#?(: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")
|
(def zero #uuid "00000000-0000-0000-0000-000000000000")
|
||||||
|
|
||||||
|
@ -22,32 +22,31 @@
|
||||||
|
|
||||||
(defn next
|
(defn next
|
||||||
[]
|
[]
|
||||||
#?(:clj (impl/v1)
|
#?(:clj (UUIDv8/create)
|
||||||
:cljs (impl/v1)))
|
:cljs (impl/v8)))
|
||||||
|
|
||||||
(defn random
|
(defn random
|
||||||
"Alias for clj-uuid/v4."
|
"Alias for clj-uuid/v4."
|
||||||
[]
|
[]
|
||||||
#?(:clj (impl/v4)
|
#?(:clj (UUID/randomUUID)
|
||||||
:cljs (impl/v4)))
|
:cljs (impl/v4)))
|
||||||
|
|
||||||
#?(:clj
|
|
||||||
(defn namespaced
|
|
||||||
[ns data]
|
|
||||||
(impl/v5 ns data)))
|
|
||||||
|
|
||||||
(defn uuid
|
(defn uuid
|
||||||
"Parse string uuid representation into proper UUID instance."
|
"Parse string uuid representation into proper UUID instance."
|
||||||
[s]
|
[s]
|
||||||
#?(:clj (UUID/fromString s)
|
#?(:clj (UUID/fromString s)
|
||||||
:cljs (c/uuid s)))
|
:cljs (c/parse-uuid s)))
|
||||||
|
|
||||||
(defn custom
|
(defn custom
|
||||||
([a] #?(:clj (UUID. 0 a) :cljs (c/uuid (impl/custom 0 a))))
|
([a] #?(:clj (UUID. 0 a) :cljs (c/parse-uuid (impl/custom 0 a))))
|
||||||
([b a] #?(:clj (UUID. b a) :cljs (c/uuid (impl/custom b a)))))
|
([b a] #?(:clj (UUID. b a) :cljs (c/parse-uuid (impl/custom b a)))))
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(dm/export impl/get-word-high))
|
(defn get-word-high
|
||||||
|
[id]
|
||||||
|
(.getMostSignificantBits ^UUID id)))
|
||||||
|
|
||||||
#?(:clj
|
#?(: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++]]);
|
hexMap[buf[i++]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const buff = new Uint8Array(16);
|
self.v4 = (function () {
|
||||||
|
const buff8 = new Uint8Array(16);
|
||||||
|
|
||||||
function v4() {
|
return function v4() {
|
||||||
fill(buff);
|
fill(buff8);
|
||||||
buff[6] = (buff[6] & 0x0f) | 0x40;
|
buff8[6] = (buff8[6] & 0x0f) | 0x40;
|
||||||
buff[8] = (buff[8] & 0x3f) | 0x80;
|
buff8[8] = (buff8[8] & 0x3f) | 0x80;
|
||||||
return core.uuid(toHexString(buff));
|
return core.uuid(toHexString(buff8));
|
||||||
}
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
let initialized = false;
|
self.v8 = (function () {
|
||||||
let node;
|
const buff = new ArrayBuffer(16);
|
||||||
let clockseq;
|
const buff8 = new Uint8Array(buff);
|
||||||
let lastms = 0;
|
const view = new DataView(buff);
|
||||||
let lastns = 0;
|
|
||||||
|
|
||||||
function v1() {
|
const timeRef = 1640991600 * 1000; // ms since 2022-01-01T00:00:00
|
||||||
let cs = clockseq;
|
const maxClockSeq = 16384n; // 14 bits space
|
||||||
|
|
||||||
if (!initialized) {
|
let clockSeq = 0n;
|
||||||
const seed = new Uint8Array(8)
|
let lastTs = 0n;
|
||||||
fill(seed);
|
let baseMsb;
|
||||||
|
let baseLsb;
|
||||||
|
|
||||||
// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
|
function initializeSeed() {
|
||||||
node = [
|
fill(buff8);
|
||||||
seed[0] | 0x01,
|
baseMsb = 0x0000_0000_0000_8000n; // Version 8;
|
||||||
seed[1],
|
baseLsb = view.getBigUint64(8, false) & 0x0fff_ffff_ffff_ffffn | 0x8000_0000_0000_0000n; // Variant 2;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ms = Date.now();
|
function currentTimestamp() {
|
||||||
let ns = lastns + 1;
|
return BigInt.asUintN(64, "" + (Date.now() - timeRef));
|
||||||
let dt = (ms - lastms) + (ns - lastns) / 10000;
|
|
||||||
|
|
||||||
// Per 4.2.1.2, Bump clockseq on clock regression
|
|
||||||
if (dt < 0) {
|
|
||||||
cs = cs + 1 & 0x3fff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
|
initializeSeed();
|
||||||
// time interval
|
|
||||||
if (dt < 0 || ms > lastms) {
|
const create = function create(ts, clockSeq) {
|
||||||
ns = 0;
|
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
|
const factory = function v8() {
|
||||||
if (ns >= 10000) {
|
while (true) {
|
||||||
throw new Error("uuid v1 can't create more than 10M uuids/s")
|
let ts = currentTimestamp();
|
||||||
}
|
|
||||||
|
|
||||||
lastms = ms;
|
// Protect from clock regression
|
||||||
lastns = ns;
|
if ((ts-lastTs) < 0) {
|
||||||
clockseq = cs;
|
initializeSeed();
|
||||||
|
clockSeq = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Per 4.1.4 - Convert from unix epoch to Gregorian epoch
|
if (lastTs === ts) {
|
||||||
ms += 12219292800000;
|
if (clockSeq < maxClockSeq) {
|
||||||
|
clockSeq++;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clockSeq = 0n;
|
||||||
|
lastTs = ts;
|
||||||
|
}
|
||||||
|
|
||||||
let i = 0;
|
return create(ts, clockSeq);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// `time_low`
|
factory.create = create
|
||||||
var tl = ((ms & 0xfffffff) * 10000 + ns) % 0x100000000;
|
factory.initialize = initializeSeed;
|
||||||
buff[i++] = tl >>> 24 & 0xff;
|
return factory;
|
||||||
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;
|
|
||||||
|
|
||||||
self.custom = function formatAsUUID(mostSigBits, leastSigBits) {
|
self.custom = function formatAsUUID(mostSigBits, leastSigBits) {
|
||||||
const most = mostSigBits.toString("16").padStart(16, "0");
|
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)
|
"get-in" (bench-get-in)
|
||||||
(println "available: str select-keys get-in")))
|
(println "available: str select-keys get-in")))
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue