Skip to content

ARTEMIS-6144 Improve getPagedResult - #6563

Open
GChuf wants to merge 2 commits into
apache:mainfrom
GChuf:ARTEMIS-6144
Open

ARTEMIS-6144 Improve getPagedResult#6563
GChuf wants to merge 2 commits into
apache:mainfrom
GChuf:ARTEMIS-6144

Conversation

@GChuf

@GChuf GChuf commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Benchmarks below:

Benchmark addressCount Throughput (main) Throughput (6144) Δ Throughput Alloc (main, B/op) Alloc (6144, B/op) Δ Alloc
testAllResults 1000 1,127.29 ops/s 3,583.19 ops/s +217.9% 37,502.19 47,313.95 +26.2%
testAllResults 10000 82.95 ops/s 219.52 ops/s +164.6% 449,604.60 460,263.58 +2.4%
testFirstPage 1000 1,126.65 ops/s 3,661.16 ops/s +225.0% 22,958.20 47,289.91 +106.0%
testFirstPage 10000 80.90 ops/s 229.92 ops/s +184.2% 280,597.19 460,238.15 +64.0%
testMiddlePage 1000 1,127.60 ops/s 3,591.73 ops/s +218.5% 23,422.20 47,529.95 +102.9%
testMiddlePage 10000 81.81 ops/s 219.18 ops/s +167.9% 281,651.85 460,479.60 +63.5%

@GChuf
GChuf marked this pull request as draft July 6, 2026 13:19
@GChuf
GChuf marked this pull request as ready for review July 7, 2026 12:04
@GChuf

GChuf commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

Ready for review.
After a bunch of sanity checks:
The JMH tests show a positive improvement in speed, a bit higher memory usage though - but the mem usage is small.
JProfiler shows reduction in relative cpu usage from 20-25% to about 6-10%.
The majority of the benefits, and all the extra mem usage come from fieldCache = new IdentityHashMap<>(array.length). Otherwise I was getting better memory numbers before I added the IdentityHashMap.

Map<Object, Object> fieldCache = new IdentityHashMap<>(array.length);
for (T item : array) {
if (item != null) {
fieldCache.put(item, getField(item, sortField));

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.

In the old code, getField() was called inside the comparator's try/catch, so an exception during field extraction was swallowed and treated as equal. However, the new code computes fields in a pre-loop with no exception handling.

This could be a problem for views like QueueView where getField() does server.locateQueue(...) and dereferences the result. Here if a queue is deleted mid-listing you get an NPE. Previously one bad element sorted as "equal" and the listing succeeded; now a single bad element throws out of getPagedResult, failing the entire management query.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks - added a try/catch in the loop.

this.sortOrder = json.getString(SORT_ORDER);
} else if (json.containsKey(SORT_COLUMN)) {
this.sortField = json.getString(SORT_COLUMN);
this.sortOrder = json.getString(SORT_ORDER);

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.

A payload with SORT_COLUMN but no SORT_ORDER now throws where the old code didn't.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed by adding a check if json contains SORT_ORDER.

Arrays.sort(array, cachedComparator);

if (page == -1 || pageSize == -1) {
return Arrays.asList(array);

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.

This now returns a mutable Arrays.asList(...) instead of Collections.unmodifiableList(...). This might not make any difference, but it's a contract change.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Code now returns Collections.unmodifiableList(collectionList) or Collections.unmodifiableList(collectionList.subList(start, end)).

Comment on lines +139 to +146
if (sortOrderDescending) {
if (rightValue instanceof Comparable r) {
return r.compareTo(leftValue);
}
} else {
if (leftValue instanceof Comparable l) {
return l.compareTo(rightValue);
}

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.

getField returns null for absent/optional fields, so a sort field with some null values is realistic.

Consider the scenario with ascending order, leftValue = abc, and rightValue = null:

  • Old code: rightValue instanceof Comparable is false so return 0. Clean, no exception.
  • New code: only leftValue is checked; it is Comparable, so it calls "abc".compareTo(null) throwing a NullPointerException which is caught so return 0.

The result (i.e., 0) is the same, but the new path throws and catches an exception on roughly every comparison that involves a null, inside the hottest loop of the sort. This is orders of magnitude more expensive than the avoided instanceof. So sorting any data containing a null will be slower, not faster.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The new code now also checks for null values before executing the compareTo method:

 if (leftValue == rightValue) {
    return 0;
 }
 // push nulls to bottom of the list
 if (leftValue == null) {
    return 1;
 }
 if (rightValue == null) {
    return -1;
 }

@GChuf
GChuf marked this pull request as draft September 3, 2026 06:23
@GChuf
GChuf marked this pull request as ready for review September 4, 2026 10:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants