[opt](memory) Replace boxed Long-keyed maps with fastutil primitive collections in FE#61292
[opt](memory) Replace boxed Long-keyed maps with fastutil primitive collections in FE#61292dataroaring wants to merge 1 commit intomasterfrom
Conversation
…ollections in FE Replace HashMap<Long, V>, ConcurrentHashMap<Long, V>, HashSet<Long> and similar boxed collections with fastutil primitive-type-specialized collections across FE hot paths. This eliminates Long autoboxing overhead and reduces per-entry memory footprint by 3-6x depending on the collection type. Key changes: - Add ConcurrentLong2ObjectHashMap and ConcurrentLong2LongHashMap as thread-safe wrappers over fastutil maps with segment-based locking - Add Gson TypeAdapters for Long2ObjectOpenHashMap, Long2LongOpenHashMap, LongOpenHashSet, and the concurrent variants for serialization compatibility - Replace collections in 27 files across catalog, transaction, statistics, alter, clone, cloud, and load subsystems For a cluster with 1.3M tablets, estimated heap savings: 350-700MB. Co-Authored-By: Claude Opus 4.6 <[email protected]>
|
Thank you for your contribution to Apache Doris. Please clearly describe your PR:
|
| @@ -72,7 +73,7 @@ public enum IndexExtState { | |||
|
|
|||
| public MaterializedIndex() { | |||
| this.state = IndexState.NORMAL; | |||
| this.idToTablets = new HashMap<>(); | |||
| this.idToTablets = new Long2ObjectOpenHashMap<>(); | |||
There was a problem hiding this comment.
should update attr defination to Long2ObjectOpenHashMap too?
There was a problem hiding this comment.
put this class into fe-foundation module
There was a problem hiding this comment.
Pull request overview
This PR introduces primitive-key/primitive-value concurrent maps and expands the use of fastutil primitive collections across FE code to reduce boxing overhead and heap usage, while keeping Gson persistence compatible during rolling upgrades.
Changes:
- Added
ConcurrentLong2LongHashMapandConcurrentLong2ObjectHashMap(segmentedReentrantReadWriteLockdesign) plus comprehensive unit tests. - Replaced a variety of
Map<Long, …>/Set<Long>usages with fastutil primitive collections (Long2*OpenHashMap,LongOpenHashSet) and the new concurrent primitive maps in transaction/statistics/catalog paths. - Extended
GsonUtilswith adapters to serialize/deserialize the new concurrent maps and fastutil primitive collections in a backward-compatible JSON shape.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| fe/fe-core/src/test/java/org/apache/doris/common/ConcurrentLong2ObjectHashMapTest.java | Adds unit tests for the new concurrent long→object map and its Gson round-trip behavior. |
| fe/fe-core/src/test/java/org/apache/doris/common/ConcurrentLong2LongHashMapTest.java | Adds unit tests for the new concurrent long→long map, including concurrency and Gson checks. |
| fe/fe-core/src/main/java/org/apache/doris/transaction/TransactionState.java | Switches several txn structures to fastutil primitive maps/sets to reduce boxing. |
| fe/fe-core/src/main/java/org/apache/doris/transaction/TableCommitInfo.java | Replaces partition commit info map with Long2ObjectOpenHashMap. |
| fe/fe-core/src/main/java/org/apache/doris/transaction/PublishVersionDaemon.java | Uses fastutil primitive maps for visible-version bookkeeping. |
| fe/fe-core/src/main/java/org/apache/doris/transaction/DatabaseTransactionMgr.java | Uses ConcurrentLong2LongHashMap for subTxn mapping/counts and fastutil sets for replica IDs. |
| fe/fe-core/src/main/java/org/apache/doris/task/PublishVersionTask.java | Uses Long2ObjectOpenHashMap for delta-row tracking. |
| fe/fe-core/src/main/java/org/apache/doris/statistics/util/StatisticsUtil.java | Updates stats logic to use ConcurrentLong2LongHashMap fields. |
| fe/fe-core/src/main/java/org/apache/doris/statistics/TableStatsMeta.java | Migrates internal counters/maps to ConcurrentLong2LongHashMap and adjusts stale cleanup. |
| fe/fe-core/src/main/java/org/apache/doris/statistics/ColStatsMeta.java | Migrates per-partition update rows map to ConcurrentLong2LongHashMap. |
| fe/fe-core/src/main/java/org/apache/doris/statistics/BaseAnalysisTask.java | Updates local variable types to the new concurrent primitive map. |
| fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisManager.java | Initializes partitionUpdateRows with ConcurrentLong2LongHashMap. |
| fe/fe-core/src/main/java/org/apache/doris/statistics/AnalysisInfo.java | Migrates persisted partitionUpdateRows/indexesRowCount to ConcurrentLong2LongHashMap. |
| fe/fe-core/src/main/java/org/apache/doris/persist/gson/GsonUtils.java | Registers Gson adapters for the new concurrent maps and fastutil primitive collections. |
| fe/fe-core/src/main/java/org/apache/doris/master/ReportHandler.java | Uses Long2ObjectOpenHashMap when building tablet ID maps. |
| fe/fe-core/src/main/java/org/apache/doris/load/routineload/RoutineLoadManager.java | Replaces blacklist and txn→job mapping with ConcurrentLong2LongHashMap. |
| fe/fe-core/src/main/java/org/apache/doris/load/DeleteJob.java | Replaces several Set<Long> fields with LongOpenHashSet. |
| fe/fe-core/src/main/java/org/apache/doris/common/ConcurrentLong2ObjectHashMap.java | Adds segmented concurrent primitive-key long→object map implementation. |
| fe/fe-core/src/main/java/org/apache/doris/common/ConcurrentLong2LongHashMap.java | Adds segmented concurrent primitive-key/value long→long map implementation. |
| fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/DeleteBitmapUpdateLockContext.java | Migrates multiple internal maps to fastutil primitive maps for lower overhead. |
| fe/fe-core/src/main/java/org/apache/doris/cloud/transaction/CloudGlobalTransactionMgr.java | Replaces multiple concurrent maps with ConcurrentLong2LongHashMap and adjusts cleanup logic. |
| fe/fe-core/src/main/java/org/apache/doris/cloud/catalog/CloudTabletInvertedIndex.java | Uses Long2ObjectOpenHashMap for tablet→replica metadata. |
| fe/fe-core/src/main/java/org/apache/doris/clone/TabletScheduler.java | Uses Long2ObjectOpenHashMap for scheduler tracking maps. |
| fe/fe-core/src/main/java/org/apache/doris/catalog/TempPartitions.java | Uses Long2ObjectOpenHashMap for temp partition ID lookup. |
| fe/fe-core/src/main/java/org/apache/doris/catalog/TabletInvertedIndex.java | Uses Long2ObjectOpenHashMap for tablet metadata storage. |
| fe/fe-core/src/main/java/org/apache/doris/catalog/PartitionInfo.java | Migrates multiple partition metadata maps to Long2ObjectOpenHashMap. |
| fe/fe-core/src/main/java/org/apache/doris/catalog/Partition.java | Uses Long2ObjectOpenHashMap for index maps inside partitions. |
| fe/fe-core/src/main/java/org/apache/doris/catalog/OlapTable.java | Migrates index meta map to Long2ObjectOpenHashMap and updates meta copying logic. |
| fe/fe-core/src/main/java/org/apache/doris/catalog/MaterializedIndex.java | Migrates tablet ID maps to Long2ObjectOpenHashMap. |
| fe/fe-core/src/main/java/org/apache/doris/alter/SchemaChangeJobV2.java | Migrates multiple job metadata maps to fastutil primitive maps. |
| fe/fe-core/src/main/java/org/apache/doris/alter/RollupJobV2.java | Migrates rollup tablet mapping structures to fastutil primitive maps. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| subTxnIdToTxnId.forEach((subTxnId, txnId) -> { | ||
| if (txnId == transactionId) { | ||
| subTxnIdToTxnId.remove(subTxnId); | ||
| } | ||
| } | ||
| }); |
| subTxnIdToTxnId.forEach((subTxnId, txnId) -> { | ||
| if (txnId == transactionId) { | ||
| subTxnIdToTxnId.remove(subTxnId); | ||
| } | ||
| } | ||
| }); |
| Long boxedResult = map.getOrDefault(999L, map.defaultReturnValue()); | ||
| Assertions.assertEquals(0L, boxedResult); |
| V newValue = mappingFunction.apply(key); | ||
| seg.map.put(key, newValue); | ||
| return newValue; |
| } | ||
| V newValue = mappingFunction.get(key); | ||
| seg.map.put(key, newValue); | ||
| return newValue; |
| * Applies the given action to each entry under read-lock per segment. | ||
| */ | ||
| public void forEach(LongLongConsumer action) { | ||
| for (Segment seg : segments) { | ||
| seg.lock.readLock().lock(); | ||
| try { | ||
| for (Long2LongMap.Entry entry : seg.map.long2LongEntrySet()) { | ||
| action.accept(entry.getLongKey(), entry.getLongValue()); | ||
| } | ||
| } finally { | ||
| seg.lock.readLock().unlock(); | ||
| } | ||
| } |
| table.state = state; | ||
| table.indexIdToMeta = ImmutableMap.copyOf(indexIdToMeta); | ||
| table.indexIdToMeta = new Long2ObjectOpenHashMap<>(indexIdToMeta); | ||
| table.indexNameToId = ImmutableMap.copyOf(indexNameToId); |
| import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; | ||
| import it.unimi.dsi.fastutil.longs.LongOpenHashSet; | ||
|
|
| import it.unimi.dsi.fastutil.longs.LongBinaryOperator; | ||
| import it.unimi.dsi.fastutil.longs.LongOpenHashSet; | ||
| import it.unimi.dsi.fastutil.longs.LongSet; | ||
| import it.unimi.dsi.fastutil.objects.ObjectArrayList; | ||
| import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet; |
Summary
Replace
HashMap<Long, V>,ConcurrentHashMap<Long, V>,HashSet<Long>and similar boxed collections with fastutil primitive-type-specialized collections (Long2ObjectOpenHashMap,Long2LongOpenHashMap,LongOpenHashSet, etc.) across FE hot paths to reduce memory footprint and eliminate autoboxing overhead.Key changes:
ConcurrentLong2ObjectHashMap<V>andConcurrentLong2LongHashMap— thread-safe wrappers over fastutil maps using segment-based locking, replacingConcurrentHashMap<Long, V>where concurrent access is neededLong2ObjectOpenHashMap,Long2LongOpenHashMap,LongOpenHashSet, and the concurrent variants. Wire format is backward-compatible withHashMap<Long, V>(string-keyed JSON objects) for rolling upgrade safetyMemory savings per collection type:
HashMap<Long, Long>HashMap<Long, Object>HashSet<Long>ConcurrentHashMap<Long, V>Scope of changes:
Priority 1 (tablet/replica scale, millions of entries):
MaterializedIndex.idToTabletsDeleteBitmapUpdateLockContext— 4× Long2Long maps + nested mapsTransactionState— deltaRows, loadedTblIndexes, errorReplicasPublishVersionTask/PublishVersionDaemonfieldsReportHandler.ReportTaskfieldsDeleteJobtablet setsPriority 2 (per-partition scale, thousands+):
PartitionInfo— 7 maps keyed by partition_idOlapTable.idToPartitionTableCommitInfo.idToPartitionCommitInfoDatabaseTransactionMgr— running/final transaction maps, subTxnIdToTxnIdTableStatsMeta/AnalysisInfo/ColStatsMeta— partition update rows, indexes row countPriority 3-4 (alter, cloud, load):
SchemaChangeJobV2/RollupJobV2tablet mapsCloudGlobalTransactionMgrfieldsRoutineLoadManagerfieldsTabletSchedulerfieldsEstimated total heap savings for a cluster with 1.3M tablets: 350-700MB.
Test plan
ConcurrentLong2ObjectHashMap(432 lines, covers CRUD, concurrency, iteration)ConcurrentLong2LongHashMap(455 lines, covers CRUD, concurrency, default values)🤖 Generated with Claude Code