Skip to content

Generate entryUUID from a per-thread SecureRandom#683

Open
vharseko wants to merge 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:features/adddel-2
Open

Generate entryUUID from a per-thread SecureRandom#683
vharseko wants to merge 2 commits into
OpenIdentityPlatform:masterfrom
vharseko:features/adddel-2

Conversation

@vharseko

@vharseko vharseko commented Jul 3, 2026

Copy link
Copy Markdown
Member

Summary

Companion to #682 from the ADD/DELETE hot-path profiling. The EntryUUID
plugin runs for every LDAP add and used UUID.randomUUID(), which
draws from a single shared static SecureRandom whose engine
(SHA1PRNG/NativePRNG, provider-dependent) synchronizes nextBytes
one more JDK-internal monitor that all concurrent adds serialize on.

Change

Generate the RFC 4122 version 4 UUID from a
ThreadLocal<SecureRandom>: 16 random bytes with the version and variant
bits set exactly as UUID.randomUUID() sets them. Format verified
standalone — 200,000 generated UUIDs, all unique, version()==4,
variant()==2, matching the reference implementation. The deterministic
DN-based UUID used for LDIF import (parallel-import reproducibility) is
unchanged.

Honest performance note

No local throughput change is expected or claimed on the benchmark host:
the concurrent add path is dominated by JE record-lock waits on shared
index keys (~822 s of waitForLock per 80 s JFR window in the ADD/DELETE
baseline — see #682) and by the JE write-ahead log. The SecureRandom
monitor is held for microseconds; this removes a JDK-internal
serialization point whose relative weight grows with core count and add
rate — the same structural rationale as the rest of the series.

Testing

mvn -P precommit -pl opendj-server-legacy verify:
EntryUUIDPluginTestCase (61 — behavioural validation of the generated
attribute through the UUID syntax), AddOperationTestCase (137):
198 tests, 0 failures. Plus the standalone 200k-UUID format check
described above.

Files

  • opendj-server-legacy/src/main/java/org/opends/server/plugins/EntryUUIDPlugin.java

The EntryUUID plugin runs for every LDAP add and used UUID.randomUUID(),
which draws from a single shared SecureRandom whose engine is
synchronized — one more monitor all concurrent adds serialize on.

Generate the RFC 4122 version 4 UUID from a ThreadLocal<SecureRandom>
instead: same format (version and variant bits set identically, verified
against UUID.randomUUID), same uniqueness guarantees, no shared state.
The deterministic DN-based UUID used for LDIF import is unchanged.

No measurable local throughput change is expected or claimed: the add
path is dominated by JE record-lock waits on shared index keys; this
removes a JDK-internal serialization point that grows with core count.
@vharseko vharseko added this to the 5.2.0 milestone Jul 3, 2026
@vharseko
vharseko requested a review from maximthomas July 3, 2026 20:13
@vharseko vharseko added the performance Performance / concurrency / lock-contention work label Jul 6, 2026
@prthakre

Copy link
Copy Markdown
Contributor

Below code is another alternative:

        // Generate 128 random bits (two 64-bit longs)
        long msb = RANDOM.nextLong();
        long lsb = RANDOM.nextLong();

        // Apply the UUID v4 Version and Variant bits
        
        // Clear the version bits (bits 12-15 of MSB) and set it to 4 (0100)
        msb &= 0xFFFFFFFFFFFF0FFFL;
        msb |= 0x0000000000004000L;

        // Clear the variant bits (bits 62-63 of LSB) and set to IETF variant (10)
        lsb &= 0x3FFFFFFFFFFFFFFFL;
        lsb |= 0x8000000000000000L;

@vharseko

Copy link
Copy Markdown
Member Author

Thanks for the suggestion — the bit masks are correct and the output distribution is identical. However, SecureRandom inherits nextLong() from java.util.Random, where it is implemented as two next(32) calls, and SecureRandom.next(int) in turn goes through nextBytes() on a temporary 4-byte array. So two nextLong() calls cost four engine invocations plus four temp arrays, versus a single nextBytes(16) in the current code — strictly more overhead on the very path this PR is trimming. The byte-based version also mirrors the JDK's UUID.randomUUID() reference implementation byte-for-byte, which is what the 200k-UUID equivalence check verified, so I'd prefer to keep it as is.

@prthakre

Copy link
Copy Markdown
Contributor

Thanks for the deep dive, makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement performance Performance / concurrency / lock-contention work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants