HHH-18947 UUID v7 - Use randomized counter instead of counter + random

This will add approximately ten bits of entropy to generated UUID,
          while preserving uniqueness and monotonicity
This commit is contained in:
Čedomir Igaly 2024-12-14 14:00:16 +01:00 committed by Christian Beikov
parent 57c0b26d86
commit 2215d2b9bf
1 changed files with 24 additions and 29 deletions

View File

@ -13,7 +13,6 @@ import org.hibernate.Internal;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.UUIDGenerationStrategy; import org.hibernate.id.UUIDGenerationStrategy;
import static java.time.temporal.ChronoUnit.MILLIS;
/** /**
* Implements UUID Version 7 generation strategy as defined by the <a href="https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7">RFC 9562</a>. * Implements UUID Version 7 generation strategy as defined by the <a href="https://datatracker.ietf.org/doc/html/rfc9562#name-uuid-version-7">RFC 9562</a>.
@ -26,8 +25,7 @@ import static java.time.temporal.ChronoUnit.MILLIS;
* to guarantee additional monotonicity. * to guarantee additional monotonicity.
* </li> * </li>
* <li>2 bits - variant field, set to 0b10.</li> * <li>2 bits - variant field, set to 0b10.</li>
* <li>14 bits - counter to guarantee additional monotonicity, resets to 0 when timestamp changes. </li> * <li>62 bits - pseudorandom counter to guarantee additional monotonicity, uniqueness, and good entropy.</li>
* <li>48 bits - pseudorandom data to provide uniqueness.</li>
* </ul> * </ul>
* *
* @author Cedomir Igaly * @author Cedomir Igaly
@ -37,6 +35,8 @@ import static java.time.temporal.ChronoUnit.MILLIS;
*/ */
public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGenerator { public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGenerator {
private static final long MAX_RANDOM_SEQUENCE = 0x3FFF_FFFF_FFFF_FFFFL;
public static final UuidVersion7Strategy INSTANCE = new UuidVersion7Strategy(); public static final UuidVersion7Strategy INSTANCE = new UuidVersion7Strategy();
private static class Holder { private static class Holder {
@ -44,36 +44,37 @@ public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGe
} }
public record State(Instant lastTimestamp, int lastSequence) { public record State(Instant lastTimestamp, long lastSequence, long nanos) {
State(Instant lastTimestamp, long lastSequence) {
this( lastTimestamp, lastSequence, nanos( lastTimestamp ) );
}
public long millis() { public long millis() {
return lastTimestamp.toEpochMilli(); return lastTimestamp.toEpochMilli();
} }
public long nanos() { private static long nanos(Instant timestamp) {
return (long) ( ( lastTimestamp.getNano() % 1_000_000L ) * 0.004096 ); return (long) ((timestamp.getNano() % 1_000_000L) * 0.004096);
} }
public State getNextState() { public State getNextState() {
final Instant now = Instant.now(); final Instant now = Instant.now();
if ( lastTimestamp.toEpochMilli() < now.toEpochMilli() ) { if ( lastTimestamp.toEpochMilli() < now.toEpochMilli() ||
return new State( lastTimestamp.toEpochMilli() == now.toEpochMilli() && nanos < nanos( now ) ) {
now.truncatedTo( MILLIS ), return new State( now, randomSequence() );
randomSequence()
);
} }
else if ( lastSequence == 0x3FFF ) { final long nextSequence = lastSequence + Holder.numberGenerator.nextLong( 0xFFFF_FFFFL );
return new State( if ( nextSequence > MAX_RANDOM_SEQUENCE ) {
lastTimestamp.plusMillis( 1 ), return new State( lastTimestamp.plusNanos( 250 ), randomSequence() );
Holder.numberGenerator.nextInt( 1 << 14 )
);
} }
else { else {
return new State( lastTimestamp, lastSequence + 1 ); return new State( lastTimestamp, nextSequence );
} }
} }
private static int randomSequence() { private static long randomSequence() {
return Holder.numberGenerator.nextInt( 1 << 14 ); return Holder.numberGenerator.nextLong( MAX_RANDOM_SEQUENCE );
} }
} }
@ -81,11 +82,11 @@ public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGe
@Internal @Internal
public UuidVersion7Strategy() { public UuidVersion7Strategy() {
this( Instant.EPOCH, Integer.MIN_VALUE ); this( Instant.EPOCH, Holder.numberGenerator.nextLong( MAX_RANDOM_SEQUENCE ) );
} }
@Internal @Internal
public UuidVersion7Strategy(final Instant initialTimestamp, final int initialSequence) { public UuidVersion7Strategy(final Instant initialTimestamp, final long initialSequence) {
this.lastState = new AtomicReference<>( new State( initialTimestamp, initialSequence ) ); this.lastState = new AtomicReference<>( new State( initialTimestamp, initialSequence ) );
} }
@ -115,14 +116,8 @@ public class UuidVersion7Strategy implements UUIDGenerationStrategy, UuidValueGe
| state.nanos() & 0xFFFL, | state.nanos() & 0xFFFL,
// LSB bits 0-1 - variant = 4 // LSB bits 0-1 - variant = 4
0x8000_0000_0000_0000L 0x8000_0000_0000_0000L
// LSB bits 2-15 - counter // LSB bits 2-15 - pseudorandom counter
| (long) state.lastSequence << 48 | state.lastSequence
// LSB bits 16-63 - pseudorandom data
| randomNode()
); );
} }
private static long randomNode() {
return Holder.numberGenerator.nextLong( 0x1_0000_0000_0000L ) | 0x1000_0000_0000L;
}
} }