You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-issues@jackrabbit.apache.org by "Rishabh Daim (Jira)" <ji...@apache.org> on 2026/09/19 06:53:00 UTC
[jira] [Created] (OAK-12405) MapEntry.compareTo() allocates a new Comparator chain on every call, adding GC pressure during segment map writes
Rishabh Daim created OAK-12405:
----------------------------------
Summary: MapEntry.compareTo() allocates a new Comparator chain on every call, adding GC pressure during segment map writes
Key: OAK-12405
URL: https://issues.apache.org/jira/browse/OAK-12405
Project: Jackrabbit Oak
Issue Type: Improvement
Components: segment-tar
Reporter: Rishabh Daim
h2. Summary
{{MapEntry.compareTo()}} in oak-segment-tar builds a brand-new {{Comparator}} chain (3 allocated objects: {{comparingLong}} + {{thenComparing}} + a {{Comparator.nullsLast(Comparator.naturalOrder())}} wrapper) on {*}every single pairwise comparison{*}, instead of building it once and reusing it.
{code:java}
// oak-segment-tar/src/main/java/org/apache/jackrabbit/oak/segment/MapEntry.java:150-155
@Override
public int compareTo(@NotNull MapEntry that) {
return Comparator.comparingLong((MapEntry me) -> me.getHash() & HASH_MASK)
.thenComparing(MapEntry::getName)
.thenComparing(MapEntry::getValue, Comparator.nullsLast(Comparator.naturalOrder()))
.compare(this, that);
}
{code}
This is the same anti-pattern as OAK-12378 ({{PropertyTemplate.compareTo()}}), fixed by hoisting the comparator chain into a {{static final Comparator<MapEntry>}} field. This method is invoked via {{Arrays.sort}}/{{ComparableTimSort}} when persisting a {{MapRecord}} (see {{RecordWriters$MapLeafWriter.writeRecordContent}} and {{DefaultSegmentWriter$SegmentWriteOperation.writeMapLeaf}}), i.e. whenever a node's children are written as a persisted map — relevant to any node with enough children to use {{MapRecord}} rather than a flat list.
h2. Evidence
Found while profiling a bulk parallel-copy migration workload (JFR + async-profiler, ~793K nodes, 4 worker threads, Java assertions disabled to avoid unrelated noise). {{MapEntry.compareTo}} accounted for 14 of 31,263 total sampled leaf-frame weight in the async-profiler CPU capture, and 12 of 8,930 JFR execution samples with it as the top-of-stack frame — a smaller contributor than {{PropertyTemplate.compareTo}} in OAK-12378 (that recording was during compaction, a much more write-record-sorting-heavy workload), but the identical allocation pattern, confirmed via the following representative stack:
{code:java}
java.util.Arrays.sort(Object[])
java.util.ComparableTimSort.sort(...)
java.util.ComparableTimSort.binarySort(...)
org.apache.jackrabbit.oak.segment.MapEntry.compareTo(MapEntry)
org.apache.jackrabbit.oak.segment.MapEntry.compareTo(Object)
java.util.Comparator.thenComparing(...)
java.util.Comparator.comparing(...)
org.apache.jackrabbit.oak.segment.RecordWriters$MapLeafWriter.writeRecordContent(...)
org.apache.jackrabbit.oak.segment.DefaultSegmentWriter$SegmentWriteOperation.writeMapLeaf(...)
org.apache.jackrabbit.oak.segment.DefaultSegmentWriter$SegmentWriteOperation.writeMap(...)
{code}
Confirmed present verbatim (lines 150-155) on current {{apache/jackrabbit-oak}} trunk (commit {{44851487c5e4d0f9b9b46a40da79d9c78a3c83b9}}), not just an internal fork.
h2. Suggested fix
Same shape as the OAK-12378 fix: hoist the {{Comparator}} chain into a {{private static final Comparator<MapEntry>}} field built once, and have {{compareTo}} delegate to it. No behavior change; existing {{MapEntry}} tests should cover {{compareTo}} unchanged.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)