0
Fork 0
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:
Andrey Antukh 2022-08-01 13:07:00 +02:00
parent abfca5c89a
commit fbfcb827ed
11 changed files with 212 additions and 105 deletions

View file

@ -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"]}))

View file

@ -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"

View file

@ -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
View 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"]}))

View file

@ -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

View 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);
}
}
}

View file

@ -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)))

View file

@ -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");

Binary file not shown.

View 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))))

View file

@ -62,3 +62,4 @@
"get-in" (bench-get-in)
(println "available: str select-keys get-in")))