Skip to content

[Performance] Reduce allocations on aligned tablet writes - #18358

Open
Caideyipi wants to merge 3 commits into
apache:masterfrom
Caideyipi:perf/aligned-bitmap-range-check
Open

[Performance] Reduce allocations on aligned tablet writes#18358
Caideyipi wants to merge 3 commits into
apache:masterfrom
Caideyipi:perf/aligned-bitmap-range-check

Conversation

@Caideyipi

@Caideyipi Caideyipi commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Description

Problem

#18249 made aligned MemTable bitmaps block-lazy, #18276 made primitive value arrays lazy, and #18330 reconciled their actual memory usage. Two allocation hot spots still remained on the aligned tablet write path, especially on Windows:

  1. BitMapLongImpl.getByteArray() was called while checking source-bitmap prefixes. A 64-column batch performed the check twice per column and created about 4 KiB of temporary arrays.
  2. A block whose primitive value array was still null is necessarily all-null, but the write path nevertheless created and marked a bitmap for that block. Wide sparse writes therefore allocated thousands of bitmap objects for columns that had no value array at all.

The second cost is small in a narrow ns-scale microbenchmark, but scales with column count and also adds allocation/GC pressure. It is visible in the 3,000-column Windows case below.

Fix

  • Use BitMap.isAllUnmarked(length) for ranges starting at offset 0, inspecting the backing bitmap directly without creating a byte-array copy.
  • Treat a null primitive value array as an implicit all-null block:
    • do not create a bitmap for an entirely null row/tablet segment;
    • materialize the primitive array only on the first non-null value;
    • if that first value is not at block offset 0, create one bitmap and mark only the preceding implicit-null prefix;
    • keep historical implicit-null blocks bitmap-free when extending a schema;
    • avoid materializing bitmaps when deleting an already implicit-null block or column.
  • Update all-value-deleted-map construction and column value counting so they distinguish implicit-null blocks from materialized all-non-null blocks.
  • Keep the existing non-zero-offset bitmap scanner unchanged; an alternative bit-by-bit path was measured at about 3x its CPU cost in the worst case.

Correctness coverage

  • Long-backed and array-backed bitmap range checks: empty, prefix, partial, byte-boundary, and multi-long ranges.
  • Entirely null blocks remain value-array/bitmap free.
  • First non-null values correctly mark an implicit-null prefix.
  • Historical implicit-null blocks and schema extensions remain bitmap-free.
  • Mixed implicit/materialized blocks produce the same all-value-deleted map as per-column null checks.
  • Deleting implicit-null blocks does not allocate bitmaps.
  • Clone, flush clone, WAL serialization/deserialization, iterator behavior, and aligned MemTable RAM accounting.

Manual performance UT

AlignedSparseWritePerformanceTest is disabled by default and reports current-thread CPU time, allocated bytes, peak heap delta, and theoretical avoided bitmap RAM for:

  • dense writes;
  • sparse writes;
  • all-null writes;
  • a null prefix followed by the first non-null value;
  • schema extension over historical rows.

Run the default 64-column case with:

mvn -o -nsu -pl iotdb-core/datanode test \
  -Dtest=AlignedSparseWritePerformanceTest \
  -Diotdb.aligned.sparse-write.perf.enabled=true

The following properties are independently tunable:

iotdb.aligned.sparse-write.perf.columns
iotdb.aligned.sparse-write.perf.written-columns
iotdb.aligned.sparse-write.perf.rows
iotdb.aligned.sparse-write.perf.historical-rows
iotdb.aligned.sparse-write.perf.warmup.iterations
iotdb.aligned.sparse-write.perf.iterations
iotdb.aligned.sparse-write.perf.rounds

Example wide-table run:

mvn -o -nsu -pl iotdb-core/datanode test \
  -Dtest=AlignedSparseWritePerformanceTest \
  -Diotdb.aligned.sparse-write.perf.enabled=true \
  -Diotdb.aligned.sparse-write.perf.columns=3000 \
  -Diotdb.aligned.sparse-write.perf.written-columns=1 \
  -Diotdb.aligned.sparse-write.perf.rows=64 \
  -Diotdb.aligned.sparse-write.perf.warmup.iterations=20 \
  -Diotdb.aligned.sparse-write.perf.iterations=200 \
  -Diotdb.aligned.sparse-write.perf.rounds=5

Performance

Measured on Windows with Java 17.

Prefix bitmap check

The range-check benchmark used 64 long-backed bitmaps, 102,400,000 checks per round, and the median of 7 rounds.

Range Legacy CPU Optimized CPU CPU delta Legacy allocation Optimized allocation
Prefix (start=0, length=64) 15.869 ns/check 1.221 ns/check -14.648 ns/check 32 B/check 0 B/check
Partial (start=1, length=63) 15.411 ns/check 14.954 ns/check -0.458 ns/check 32 B/check 32 B/check

Sparse aligned write

For 3,000 columns, one written column, and 64 rows per batch:

Metric Before implicit-null blocks After Delta
Current-thread CPU 312.500 us/batch 78.125 us/batch -75.0%
Thread allocation 288,752 B/batch 42,814 B/batch -85.2%

Windows current-thread CPU samples are quantized by the platform timer, so the CPU result is directional rather than ns-precise. Thread allocation is stable and shows the main effect directly. The final path avoids 155,948 bytes of bitmap RAM accounting per sparse batch in this setup.

For the default 64-column case, final allocations were:

Scenario Allocation
Dense 18,783 B/batch
Sparse, one written column 1,647 B/batch
All-null 1,375 B/batch
Null prefix, then first non-null 1,708 B/batch

Extending one column over 65,536 historical rows (1,024 blocks) avoids 53,248 bytes of bitmap RAM accounting per column. The remaining cost is the existing value-list placeholders and schema-array growth, not bitmap objects.

The existing aligned bitmap accounting benchmark also remained non-regressive: dense allocation was unchanged, while the source-bitmap prefix optimization removed about 4 KiB/batch from the measured null-heavy path.

Dense fully aligned single-device follow-up

The implicit-null optimization above targets sparse/all-null writes and therefore does not materially affect the dense, fully aligned ATOMS case. A separate fixed cost remained from #18330: every aligned tablet created a HashSet, called splitByDevice, and retained the set in the RAM-cost snapshot even when the tablet could only contain one device.

This follow-up:

  • uses the first selected row's device directly for tree-model tablets, which are always single-device;
  • uses the same fast path for relational tablets already marked singleDevice by the planner;
  • preserves the existing splitByDevice + HashSet path for relational multi-device tablets;
  • adds a correctness UT covering tree single-device, relational multi-device, and relational single-device dispatch;
  • extends AlignedBitmapMemoryAccountingPerformanceTest with a default-off, configurable legacy/current benchmark of the complete snapshot lifecycle (write-before snapshot plus write-after correction). The benchmark forces snapshots to escape so HotSpot cannot hide the production allocations through scalar replacement.

Run it with:

mvn -o -nsu -pl iotdb-core/datanode test \
  -Dtest=AlignedBitmapMemoryAccountingPerformanceTest \
  -Diotdb.aligned.bitmap.accounting.perf.enabled=true

The existing columns, rows, warmup.iterations, iterations, and rounds properties remain tunable. With the defaults (64 columns, 256 rows, 2,048,000 snapshot lifecycles per round, median of 5 rounds) on Windows/Java 17:

Path CPU Thread allocation Peak heap delta/round
Legacy set-based snapshot 76.294 ns/tablet 264 B/tablet 168.251 MiB
Single-device fast path 45.776 ns/tablet 32 B/tablet 32.039 MiB
Delta -30.518 ns/tablet -232 B/tablet -136.212 MiB

The snapshot sub-path is about 40% faster and allocates 87.9% less, but dense tablet writing itself measured 23-31 us/tablet on Windows. The net saving is only about 0.10-0.13% of dense-write CPU and 0.29% of dense-write allocation. This is a low-risk cleanup, not an explanation for the large ATOMS regression.

Verification

  • mvn -o -nsu spotless:apply -pl iotdb-core/datanode
  • mvn -o -nsu -pl iotdb-core/datanode test-compile -DskipTests
    • Checkstyle: 0 violations
  • AlignedTVListTest: 19 passed
  • AlignedTVListIteratorTest: 12 passed
  • Six affected TsFileProcessorTest methods: 6 passed
  • AlignedSparseWritePerformanceTest: passed when enabled for both 64-column and 3,000-column configurations
  • AlignedBitmapMemoryAccountingPerformanceTest: passed when enabled with the configurable dense single-device legacy/current benchmark
  • git diff --check: passed

This PR has:

  • been self-reviewed.
  • added comments explaining the implicit-null-block invariant and prefix fast path.
  • added or updated unit tests for the changed paths.

Key changed/added classes
  • AlignedTVList: allocation-free prefix checks and deferred bitmap allocation for implicit-null blocks.
  • AlignedTVListTest: correctness and memory-accounting coverage.
  • TsFileProcessor and RelationalInsertTabletNode: direct-device RAM snapshot path for known single-device tablets.
  • TsFileProcessorTest: aligned MemTable RAM expectations and single-/multi-device snapshot dispatch coverage.
  • AlignedBitmapRangeCheckPerformanceTest: opt-in legacy/current prefix range benchmark.
  • AlignedSparseWritePerformanceTest: configurable sparse-write CPU/allocation benchmark.
  • AlignedBitmapMemoryAccountingPerformanceTest: configurable dense single-device snapshot CPU/allocation benchmark.

@Caideyipi
Caideyipi force-pushed the perf/aligned-bitmap-range-check branch from 90a8f48 to b2227fd Compare July 30, 2026 07:29
@Caideyipi Caideyipi changed the title [Performance] Avoid bitmap copies for aligned tablet prefixes [Performance] Reduce bitmap allocations for sparse aligned writes Jul 30, 2026
@Caideyipi
Caideyipi force-pushed the perf/aligned-bitmap-range-check branch from b2227fd to e04c71a Compare July 30, 2026 08:43
@Caideyipi Caideyipi changed the title [Performance] Reduce bitmap allocations for sparse aligned writes [Performance] Reduce allocations on aligned tablet writes Jul 30, 2026
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.

1 participant