Skip to content

[#2275] Refactor Usage to address contention - #2276

Open
mattrpav wants to merge 2 commits into
apache:mainfrom
mattrpav:amq-gh-2275-usage-longadder
Open

[#2275] Refactor Usage to address contention#2276
mattrpav wants to merge 2 commits into
apache:mainfrom
mattrpav:amq-gh-2275-usage-longadder

Conversation

@mattrpav

@mattrpav mattrpav commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Reviewer notes:

  1. Checkout the first commit to run the test and see the performance
  2. Checkout the second commit to see LongAdder performance
  3. Checkout the third commit to see AtomicLong performance

I'm leaning towards AtomicLong as its more commonly understood and would make the change less brittle to maintain going forward.

edit: Design note: Consider calculating max limit and doing comparisons vs calculations

JMH benchmark isolating the broker's topic send path: non-persistent
broker + NON_PERSISTENT BytesMessage, vm:// transport, async producer
send with unbounded window, producer flow control off, advisories/JMX/
scheduler/statistics/audit off, zero consumers. Parameters toggle a
shared topic vs a distinct topic per producer thread (distinctTopics)
and vm async dispatch vs direct dispatch on the calling thread
(vmAsync). Named *Benchmark so surefire skips it in CI.

Build (JDK 23+ needs proc=full for the JMH annotation processor):

  mvn -pl activemq-unit-tests -am install -DskipTests -Dmaven.compiler.proc=full

Run (example: direct dispatch, both topic modes, thread sweep):

  CP="activemq-unit-tests/target/test-classes:activemq-unit-tests/target/classes:$(mvn -q -pl activemq-unit-tests dependency:build-classpath -Dmdep.outputFile=/dev/stdout)"
  java -cp "$CP" org.openjdk.jmh.Main TopicRawThroughputBenchmark \
    -p numConsumers=0 -p useVirtualThread=false -p vmAsync=false \
    -p distinctTopics=false,true -f 2 -wi 3 -w 2s -i 5 -r 2s \
    -jvmArgs "-Xms4g -Xmx4g -XX:+AlwaysPreTouch"

On this commit (stock code) throughput DEGRADES as producer threads are
added, even with a topic per producer (e.g. ~5.2M msgs/sec at 1 thread
falling to ~1.1M at 22 threads on an 11-core machine). JMH stack
profiling attributes ~86% of lock wait time to the MemoryUsage
ReentrantReadWriteLock: every send takes exclusive write locks on a
usage hierarchy rooted in the broker-global SystemUsage, so destination
sharding cannot avoid the contention. The following commit removes it.
@mattrpav mattrpav changed the title [#2275] Refactroy MemoryUsage to use LongAdder [#2275] Refactor MemoryUsage to use LongAdder Aug 2, 2026
@mattrpav
mattrpav marked this pull request as draft August 2, 2026 11:59
@mattrpav mattrpav self-assigned this Aug 2, 2026
@mattrpav mattrpav changed the title [#2275] Refactor MemoryUsage to use LongAdder [#2275] WIP: Refactor MemoryUsage to use LongAdder Aug 2, 2026
@mattrpav
mattrpav force-pushed the amq-gh-2275-usage-longadder branch from ad3abff to 3ebd8d3 Compare August 2, 2026 21:57
@mattrpav mattrpav changed the title [#2275] WIP: Refactor MemoryUsage to use LongAdder [#2275] Refactor MemoryUsage to use LongAdder Aug 2, 2026
@mattrpav
mattrpav marked this pull request as ready for review August 3, 2026 01:50
@mattrpav
mattrpav requested a review from cshannon August 3, 2026 01:50
@mattrpav mattrpav changed the title [#2275] Refactor MemoryUsage to use LongAdder [#2275] Refactor MemoryUsage to address contention Aug 3, 2026
@mattrpav
mattrpav force-pushed the amq-gh-2275-usage-longadder branch from 3210d16 to 97286b9 Compare August 3, 2026 18:22
@mattrpav mattrpav moved this from Backlog to In review in Apache ActiveMQ v6.4.0 Aug 3, 2026

@cshannon cshannon left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for getting this started, I agree AtomicLong is probably the way to go for now.

It would be nice if we can get rid of the lock and calculate usage. Maybe we could compute the actual value (and not use percentage) on first use or startup (ie compute 70% of whatever the limit is) and just do long compare for checking if we hit the limit and then don't need to lock. I think we'd only need to lock if updating that computed value on change which would be rare (like JMX)

Replace the exclusive-lock counter in MemoryUsage with an AtomicLong and
cache the current percentUsage bucket as absolute value bounds
(PercentBounds) in base Usage. Mutations and reads compare the usage
value against the cached bounds and only take the usageLock when a bound
is crossed, where setPercentUsage recalculates the percent and bounds
and fires the usual listener events and waitForSpace signals. The locked
path recomputes after publishing until stable so a concurrent update
cannot leave the published percent stale.

Storage usage values change externally via store.size(), so base
isFull(int) and getPercentUsage() use the same bounds check to skip the
write lock on reads; the StoreUsage and TempUsage per-read recompute
overrides are removed and JobSchedulerUsage now refreshes its percent on
read.
@mattrpav
mattrpav force-pushed the amq-gh-2275-usage-longadder branch from 97286b9 to 47261e7 Compare August 3, 2026 22:48
@mattrpav mattrpav changed the title [#2275] Refactor MemoryUsage to address contention [#2275] Refactor Usage to address contention Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In review

Development

Successfully merging this pull request may close these issues.

2 participants