Skip to content

Use LongAdder for LDAPStatistics counters#678

Open
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:features/compare-2
Open

Use LongAdder for LDAPStatistics counters#678
vharseko wants to merge 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:features/compare-2

Conversation

@vharseko

@vharseko vharseko commented Jul 3, 2026

Copy link
Copy Markdown
Member

Summary

Part of the bind/compare hot-path series (#660, #667#670, #672, #674): the
last shared-hot-cache-line point identified by both profiling passes.
LDAPStatistics is a single instance shared by every connection of a
connection handler
(keep-stats is on by default), and its counters are
updated 7–9 times per operation of any type:

  • updateMessageRead: messagesRead, operationsInitiated, per-type
    request counter;
  • updateMessageWritten: messagesWritten, per-type response counter,
    operationsCompleted;
  • updateOperationMonitoringData: per-type operation count + resident time;
  • updateBytesRead/updateBytesWritten.

With AtomicLong every worker thread CAS-es the same few cache lines on
every LDAP message — the same pattern removed from AuthenticatedUsers
(#660), the password schemes (#667), getDefaultPasswordPolicy (#669),
CompressedSchema (#670) and AciList (#674).

Change

All 51 counters become final LongAdder: updates use increment()/
add() (striped cells, no shared CAS under contention), reads use
sum() and only happen when cn=monitor data is requested;
clearStatistics() uses reset(), which has the same
non-atomic-under-concurrent-writes semantics the previous set(0) had.
Public getter signatures are unchanged (long). getMonitorData()
passes explicit sum() values instead of live counter objects.

Performance note

Honest caveat, as with the rest of the series: on the 8-core benchmark
host the effect is not locally measurable — an earlier A/B with
keep-stats:false (removing the counters entirely) was within noise at
~40k ops/s. The value is structural: LongAdder removes ~7–9 shared-line
CAS per operation for every operation type, a ceiling that grows with
core count, at the cost of slower reads on the rarely-used monitoring
path — exactly the trade-off LongAdder is designed for.

Testing

mvn -P precommit -pl opendj-server-legacy verify for
CompareOperationTestCase (37), SearchOperationTestCase (75),
AbandonOperationTestCase (30), TestLDAPConnectionHandler (3) — these
suites assert exact counter values through the public getters:
145 tests, 0 failures.

Files

  • opendj-server-legacy/src/main/java/org/opends/server/protocols/ldap/LDAPStatistics.java

The statistics counters are shared by every connection of a handler and
updated 7-9 times per operation (message read/written, per-type request
and response counters, operation monitoring count/time, bytes counters).
With AtomicLong all worker threads CAS the same few cache lines on every
LDAP message — the same shared-hot-line pattern removed elsewhere on the
hot path. Switch to LongAdder: updates go to striped cells, and the cost
moves to reads, which only happen when cn=monitor data is requested.

Public getters still return long (sum()); clearStatistics() uses reset(),
which has the same non-atomic-under-concurrency semantics the previous
set(0) had. Monitor data now passes explicit sum() values.
@vharseko vharseko added this to the 5.2.0 milestone Jul 3, 2026
@vharseko vharseko requested a review from maximthomas July 3, 2026 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

benchmark enhancement performance Performance / concurrency / lock-contention work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants