Skip to content

branch-4.1: [Enhancement](memory) Add ConcurrentLong2ObjectHashMap and ConcurrentLong2LongHashMap (#61332)#63797

Merged
yiguolei merged 2 commits into
apache:branch-4.1from
mymeiyi:branch-4.1-pick-61332
May 29, 2026
Merged

branch-4.1: [Enhancement](memory) Add ConcurrentLong2ObjectHashMap and ConcurrentLong2LongHashMap (#61332)#63797
yiguolei merged 2 commits into
apache:branch-4.1from
mymeiyi:branch-4.1-pick-61332

Conversation

@mymeiyi
Copy link
Copy Markdown
Contributor

@mymeiyi mymeiyi commented May 28, 2026

pick #61332

…Long2LongHashMap (apache#61332)

Add two thread-safe primitive-key concurrent hash maps built on
fastutil, designed as drop-in replacements for `ConcurrentHashMap<Long,
V>` and `ConcurrentHashMap<Long, Long>` in memory-sensitive FE paths.

- **`ConcurrentLong2ObjectHashMap<V>`** — replaces
`ConcurrentHashMap<Long, V>`
- **`ConcurrentLong2LongHashMap`** — replaces `ConcurrentHashMap<Long,
Long>`

`ConcurrentHashMap<Long, V>` costs ~64 bytes per entry due to Long
boxing, Node wrapper, and segment overhead. These fastutil-based maps
reduce that to ~16 bytes per entry — a **4x memory reduction**.

In Doris FE, several critical data structures use
`ConcurrentHashMap<Long, V>` at tablet/partition scale (millions of
entries), making this a significant memory optimization opportunity.

- **Segment-based locking** (default 16 segments) for concurrent
throughput, similar to Java 7's ConcurrentHashMap design
- Full `Map` interface compatibility for drop-in replacement
- Atomic operations: `putIfAbsent`, `computeIfAbsent`, `replace`,
`remove(key, value)`
- Thread-safe iteration via snapshot-based
`entrySet()`/`keySet()`/`values()`

| Collection | Per-entry overhead | 1M entries |
|------------|-------------------|------------|
| `ConcurrentHashMap<Long, V>` | ~64 bytes | ~61 MB |
| `ConcurrentLong2ObjectHashMap<V>` | ~16 bytes | ~15 MB |
| `ConcurrentHashMap<Long, Long>` | ~80 bytes | ~76 MB |
| `ConcurrentLong2LongHashMap` | ~16 bytes | ~15 MB |

- [x] `ConcurrentLong2ObjectHashMapTest` — 432 lines covering
put/get/remove, putIfAbsent, computeIfAbsent, replace, concurrent writes
from multiple threads, iteration consistency, empty map edge cases
- [x] `ConcurrentLong2LongHashMapTest` — 455 lines covering CRUD,
default value semantics, concurrent operations, atomic operations,
iteration, edge cases

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 28, 2026 06:45
@mymeiyi mymeiyi requested a review from yiguolei as a code owner May 28, 2026 06:45
@hello-stephen
Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@mymeiyi
Copy link
Copy Markdown
Contributor Author

mymeiyi commented May 28, 2026

run buildall

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR backports an FE enhancement introducing two segmented-lock concurrent hash maps with primitive long keys (and either object or long values) built on fastutil, aimed at reducing memory overhead compared to ConcurrentHashMap<Long, …> in large-scale metadata paths.

Changes:

  • Add ConcurrentLong2ObjectHashMap<V> and ConcurrentLong2LongHashMap implementations in fe-foundation (fastutil-backed, segment ReentrantReadWriteLock).
  • Add JUnit tests covering CRUD, atomic ops, concurrency behavior, and Gson serialization round-trips.
  • Update Maven module dependencies/comments to include fastutil-core in fe-foundation and document classpath priority in fe-core.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
fe/pom.xml Minor cleanup (removes a comment near the managed fastutil-core dependency).
fe/fe-foundation/pom.xml Adds fastutil-core dependency to support new primitive concurrent maps.
fe/fe-foundation/src/main/java/org/apache/doris/foundation/util/ConcurrentLong2ObjectHashMap.java New segmented-lock primitive-key concurrent map for long -> V.
fe/fe-foundation/src/main/java/org/apache/doris/foundation/util/ConcurrentLong2LongHashMap.java New segmented-lock primitive-key concurrent map for long -> long, including addTo.
fe/fe-core/src/test/java/org/apache/doris/common/util/ConcurrentLong2ObjectHashMapTest.java Adds unit/concurrency/serialization tests for ConcurrentLong2ObjectHashMap.
fe/fe-core/src/test/java/org/apache/doris/common/util/ConcurrentLong2LongHashMapTest.java Adds unit/concurrency/serialization tests for ConcurrentLong2LongHashMap.
fe/fe-core/pom.xml Updates comment to explain keeping fastutil-core as a direct dependency for classpath precedence.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +49 to +51
* <p>Iteration methods ({@link #long2ObjectEntrySet()}, {@link #keySet()}, {@link #values()})
* return snapshot copies and are weakly consistent.
*
Comment on lines +52 to +55
* <p><b>Important:</b> All compound operations from both {@link Long2ObjectMap} and {@link Map}
* interfaces (computeIfAbsent, computeIfPresent, compute, merge, putIfAbsent, replace, remove)
* are overridden to ensure atomicity within a segment.
*
Comment on lines +369 to +372
public V merge(long key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Segment<V> seg = segmentFor(key);
seg.lock.writeLock().lock();
try {
Comment on lines +433 to +449
// ---- Iteration (weakly consistent snapshots) ----

@Override
public ObjectSet<Long2LongMap.Entry> long2LongEntrySet() {
ObjectOpenHashSet<Long2LongMap.Entry> snapshot = new ObjectOpenHashSet<>();
for (Segment seg : segments) {
seg.lock.readLock().lock();
try {
for (Long2LongMap.Entry entry : seg.map.long2LongEntrySet()) {
snapshot.add(new AbstractLong2LongMap.BasicEntry(entry.getLongKey(), entry.getLongValue()));
}
} finally {
seg.lock.readLock().unlock();
}
}
return snapshot;
}
Comment on lines +52 to +55
* <p><b>Important:</b> All compound operations from both {@link Long2LongMap} and {@link Map}
* interfaces (computeIfAbsent, computeIfPresent, compute, merge, mergeLong, putIfAbsent,
* replace, remove) are overridden to ensure atomicity within a segment.
*/
Comment on lines +18 to +20
package org.apache.doris.foundation.util;

import com.google.gson.Gson;
Comment on lines +202 to +246
public V putIfAbsent(long key, V value) {
Objects.requireNonNull(value, "Null values are not permitted");
Segment<V> seg = segmentFor(key);
seg.lock.writeLock().lock();
try {
V existing = seg.map.get(key);
if (existing != null || seg.map.containsKey(key)) {
return existing;
}
seg.map.put(key, value);
return null;
} finally {
seg.lock.writeLock().unlock();
}
}

public boolean replace(long key, V oldValue, V newValue) {
Objects.requireNonNull(newValue, "Null values are not permitted");
Segment<V> seg = segmentFor(key);
seg.lock.writeLock().lock();
try {
V curValue = seg.map.get(key);
if (!Objects.equals(curValue, oldValue) || (curValue == null && !seg.map.containsKey(key))) {
return false;
}
seg.map.put(key, newValue);
return true;
} finally {
seg.lock.writeLock().unlock();
}
}

public V replace(long key, V value) {
Objects.requireNonNull(value, "Null values are not permitted");
Segment<V> seg = segmentFor(key);
seg.lock.writeLock().lock();
try {
if (seg.map.containsKey(key)) {
return seg.map.put(key, value);
}
return null;
} finally {
seg.lock.writeLock().unlock();
}
}
Comment on lines +204 to +243
public long putIfAbsent(long key, long value) {
Segment seg = segmentFor(key);
seg.lock.writeLock().lock();
try {
if (seg.map.containsKey(key)) {
return seg.map.get(key);
}
seg.map.put(key, value);
return defaultReturnValue();
} finally {
seg.lock.writeLock().unlock();
}
}

public boolean replace(long key, long oldValue, long newValue) {
Segment seg = segmentFor(key);
seg.lock.writeLock().lock();
try {
if (seg.map.containsKey(key) && seg.map.get(key) == oldValue) {
seg.map.put(key, newValue);
return true;
}
return false;
} finally {
seg.lock.writeLock().unlock();
}
}

public long replace(long key, long value) {
Segment seg = segmentFor(key);
seg.lock.writeLock().lock();
try {
if (seg.map.containsKey(key)) {
return seg.map.put(key, value);
}
return defaultReturnValue();
} finally {
seg.lock.writeLock().unlock();
}
}
Comment on lines +327 to +331
@Override
public V computeIfAbsent(Long key, Function<? super Long, ? extends V> mappingFunction) {
return computeIfAbsent(key.longValue(), (long k) -> mappingFunction.apply(k));
}

Comment on lines +334 to +351
@Override
public Long computeIfAbsent(Long key, Function<? super Long, ? extends Long> mappingFunction) {
long k = key.longValue();
Segment seg = segmentFor(k);
seg.lock.writeLock().lock();
try {
if (seg.map.containsKey(k)) {
return seg.map.get(k);
}
Long newValue = mappingFunction.apply(key);
if (newValue != null) {
seg.map.put(k, newValue.longValue());
}
return newValue;
} finally {
seg.lock.writeLock().unlock();
}
}
@mymeiyi
Copy link
Copy Markdown
Contributor Author

mymeiyi commented May 28, 2026

run buildall

@hello-stephen
Copy link
Copy Markdown
Contributor

FE Regression Coverage Report

Increment line coverage 0.00% (0/9) 🎉
Increment coverage report
Complete coverage report

@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label May 29, 2026
@github-actions
Copy link
Copy Markdown
Contributor

PR approved by at least one committer and no changes requested.

@github-actions
Copy link
Copy Markdown
Contributor

PR approved by anyone and no changes requested.

@yiguolei yiguolei merged commit 7517b64 into apache:branch-4.1 May 29, 2026
28 of 31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by one committer. reviewed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants