qzsee opened a new pull request, #70337:
URL: https://github.com/apache/doris/pull/70337

   ### What problem does this PR solve?
   
   Related PR: #66378, #66389, #66447, #66451
   
   Problem Summary:
   
   Every round, `CloudTabletRebalancer` rebuilds a current and a future 
generation of three routing indexes:
   
   - global: `be -> tabletIds`
   - table: `tableId -> be -> tabletIds`
   - partition: `partitionId -> indexId -> be -> tabletIds`
   
   All inner containers were `ConcurrentHashMap` / 
`ConcurrentHashMap.newKeySet()` holding boxed `Long`s. The partition and table 
levels create one concurrent set per (partition index, backend) and per (table, 
backend), so the per-container and per-element overhead dominates. With 
millions of routes, these indexes become one of the largest objects on the FE 
heap.
   
   #### Test cluster and metadata
   
   | Item | Value |
   |---|---|
   | Compute groups / backends | 3 compute groups, 5 backends (1 + 2 + 2) |
   | Tables | 192 (77 partitioned, 2 of them with 1 rollup; 115 
non-partitioned) |
   | Partitions | ~36,800 |
   | Tablets per compute group | 947,075 |
   | Routes per generation (all compute groups) | 2,841,225 |
   | Tablets per backend | 947,075 (single-backend group) / ~473,500 
(two-backend groups) |
   | Data | none, the rebalancer only depends on metadata |
   
   Table specs (empty tables created by a script):
   
   | Buckets | Partitioned tables (count × partitions) | Non-partitioned tables 
|
   |---|---|---|
   | 16 | 30 × 200, 10 × 500, 6 × 1000, 4 × 2000 | 50 |
   | 32 | 8 × 200, 2 × 200 (with 1 rollup), 4 × 500, 2 × 1000, 1 × 2000 | 30 |
   | 64 | 3 × 200, 2 × 500, 1 × 1000 | 20 |
   | 128 | 2 × 200, 1 × 500 | 10 |
   | 256 | 1 × 200 | 5 |
   
   With this metadata, a heap dump taken while the indexes were alive showed 
`CloudTabletRebalancer` retaining 839.82 MB, which is 4.6 times 
`CloudInternalCatalog` (182.16 MB) and 16 times `CloudTabletInvertedIndex` 
(52.56 MB).
   
   #### Changes
   
   This PR replaces the inner containers with primitive fastutil collections. 
Scheduling logic and decisions are unchanged.
   
   1. Partition level: `ConcurrentHashMap<partitionId, 
Long2ObjectOpenHashMap<indexId, Long2ObjectOpenHashMap<be, LongArrayList>>>`. A 
partition index usually has only a few tablets per backend, so a list is 
smaller than a set and its linear removal is cheap. Containers are created with 
small initial capacities.
   2. Table level: `ConcurrentHashMap<tableId, Long2ObjectOpenHashMap<be, 
LongOpenHashSet>>`. A large table can have many tablets on one backend, so a 
hash set keeps removal O(1).
   3. The partition and table indexes are only read and written by the 
rebalancer daemon thread (`statRouteInfo`, `balanceImpl`, warmup rollback in 
`processFailedWarmupTasks`), so their inner containers are intentionally not 
thread-safe and are accessed without locking. The outer maps are still 
`ConcurrentHashMap`.
   4. A tablet is added to the partition / table containers only when it is 
newly added to the global set of the same generation. Lists are not idempotent 
like the sets they replace, so this keeps them free of duplicates (e.g. two 
replicas of one tablet routed to the same backend).
   5. Global level: the inner sets become `LongOpenHashSet`. Unlike the other 
two levels, these sets are copied by other threads through 
`getSnapshotTabletsIn*ByBeId` (tablet report in `CloudReportHandler`, warm up 
in `CacheHotspotManager`, multi-cluster sync load in 
`StmtExecutor.syncLoadForTablets`). An open-addressing removal shifts unrelated 
elements, so an unguarded copy could miss a tablet that is not moving, and the 
tablet report would then drop a tablet that is still in use. Each set is 
therefore guarded by its own monitor:
      - the rebalancer thread modifies a set only while holding its monitor 
(`fillBeToTablets`, `updateBeToTablets`); the lock is uncontended while 
`statRouteInfo` fills the sets, because they are published afterwards by the 
volatile field write;
      - snapshot methods copy a set only while holding its monitor, and 
`addAll` takes fastutil's primitive copy path, so the lock is held for a few 
milliseconds even for a backend with ~950k tablets;
      - the rebalancer thread's own reads and `getTabletNumByBackendId` 
(`size()` only) do not lock.
   
      The lock is never nested and never held together with a table or catalog 
lock. A snapshot is now an exact point-in-time copy; previously the weakly 
consistent iteration could include or miss the tablet being moved.
   6. Snapshot results change from `HashSet<Long>` to `LongOpenHashSet` and are 
still returned as `Set<Long>`. Callers only use `contains` / `removeAll`.
   7. The global set presize hint is no longer capped at `1 << 16` and uses the 
previous round's size directly. With `LongOpenHashSet` the table is the whole 
storage: under the cap, a backend with ~950k tablets regrew 4 times every 
round, discarding about as much memory as the final set. An over-sized hint 
after a catalog shrink lasts one round and is never larger than the previous 
round's set, which is still alive while the new one is built.
   8. Balancing helpers take `Map<Long, ? extends Collection<Long>>`. 
`reservoirPick` / `containsTablet` iterate `LongCollection`s without boxing, 
and `pickedTabletIds` is a `LongOpenHashSet`.
   
   #### Result
   
   Measured on the same metadata (MAT retained heap, both generations, taken 
while the indexes were alive; `currentRoundTabletScanCount` 5,682,450 vs 
5,682,462):
   
   | Field | Before | After | Change |
   |---|---|---|---|
   | `partitionToTablets` + `futurePartitionToTablets` | 299.58 MB | 96.36 MB | 
-67.8% |
   | `beToTabletsInTable` + `futureBeToTabletsInTable` | 235.74 MB | 107.36 MB 
| -54.5% |
   | `beToTabletsGlobal` + `futureBeToTabletsGlobal` | 232.16 MB | 100.66 MB | 
-56.6% |
   | Others (mostly boxed tablet ids) | 72.34 MB | 3.31 MB | -95.4% |
   | **`CloudTabletRebalancer` retained** | **839.82 MB** | **307.69 MB** | 
**-63.4%** |
   
   The global sets were the last holders of boxed tablet ids, so the boxed 
`Long`s retained by the rebalancer (about 68 MB, 24 bytes each) are gone as 
well. The remaining 300 MB is almost entirely the `long[]` tables of the three 
levels.
   
   ### Release note
   
   Reduce FE memory used by the cloud tablet rebalancer by storing its routing 
indexes in primitive collections.
   
   ### Check List (For Author)
   
   - Test <!-- At least one of them must be included. -->
       - [ ] Regression test <!-- TODO: cloud_p0/balance -->
       - [x] Unit Test
           - `CloudTabletRebalancerTest` (37 tests), including:
               - `testSnapshotDoesNotMissUnmovedTabletsWhileTabletsAreMoving`: 
one thread keeps moving tablets between two backends while another keeps taking 
snapshots; every unmoved tablet must be present. It fails with the locks 
removed.
               - `testStatRouteInfoPresizesLargeGlobalTabletSetsWithoutBound`
               - `testFillBeToTabletsAddsDuplicateRouteOnce`
               - `testPickTabletPreferCold_primitiveCandidates`
           - `CacheHotspotManagerTest` (15 tests)
       - [x] Manual test (add detailed scripts or steps below)
           - Metadata: see "Test cluster and metadata" above.
           - Held the rebalancer thread after `statRouteInfo` and took `jmap 
-dump:live` with the old and new builds on the same metadata; the retained 
sizes are in "Result" above.
           - TODO: compare `cloud_tablet_rebalancer_last_round_duration_ms` 
before and after.
   - Behavior changed:
       - [x] No.
   - Does this need documentation?
       - [x] No.
   
   ### Check List (For Reviewer who merge this PR)
   
   - [ ] Confirm the release note
   - [ ] Confirm test cases
   - [ ] Confirm document
   - [ ] Add branch pick label <!-- Add branch pick label that this PR should 
merge into -->
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to