Pipe: optimize InsertRows schema aggregation#18195
Conversation
| if (!Arrays.equals(firstMeasurements, measurements)) { | ||
| Collections.addAll(distinctMeasurements, measurements); | ||
| } |
There was a problem hiding this comment.
measurements may be a subsequence of firstMeasurements.
To avoid adding measurements and double-iteration in this case,
may find the first position where firstMeasurements[i] == measurements[j].
Then, from j to measurements.length, if firstMeasurements[i + n] != measurements[j +n], add measurements[j +n].
Still, this cannot avoid all unnecessary additions. Maybe you can find some data structure that is more efficient for this case.
There was a problem hiding this comment.
Thanks. Addressed in d3cf02e. For shorter schemas, the aggregation now uses Objects.equals to match the longest prefix of measurements that is an ordered subsequence of firstMeasurements, and only sends the unmatched suffix through the LinkedHashSet. This avoids redundant set probes for true subsequences while retaining LinkedHashSet as the general fallback for reordered, disjoint, or newly added measurements and preserving the legacy encounter order, duplicates, and null handling. I also added regression coverage for a non-contiguous subsequence with equal-but-distinct String instances, an interleaved new measurement, and the null-measurement-array behavior.
Description
Problem
An async-profiler CPU profile of an
insertRecordsworkload with Pipe enabled showed that synchronous Pipe event construction dominated the sampled write path:PipeInsertionDataNodeListener.listenToInsertNodeaccounted for 1725 of 3614 total samples (47.73%).insertRecordsrequest branch.Stream.distinct(), especiallyHashMap.resizeandHashMap.putVal.For every additional row of the same device, the previous implementation rebuilt a Stream, a distinct set, and a result array from the accumulated and new measurements. This causes heavy temporary allocation for identical schemas and repeated scans when the measurement union grows.
Changes
LinkedHashSetonly when a device repeats, preserving encounter order.All aggregation containers remain method-local; shared-state and concurrency behavior are unchanged.
Performance UT
TsFileEpochManagerPerformanceTestcompares the legacy aggregation implementation from base commit7ffb1364b84with the optimized production implementation. It is skipped by default and contains no timing assertion.Default behavior, disabled/skipped:
mvn -pl iotdb-core/datanode -am -Dtest=TsFileEpochManagerPerformanceTest -Dsurefire.failIfNoSpecifiedTests=false testManual performance run:
mvn -pl iotdb-core/datanode -am -Dtest=TsFileEpochManagerPerformanceTest -Dsurefire.failIfNoSpecifiedTests=false -Diotdb.pipe.epoch.insert.rows.aggregation.perf.enabled=true -Diotdb.pipe.epoch.insert.rows.aggregation.perf.rows=10000 -Diotdb.pipe.epoch.insert.rows.aggregation.perf.measurements=100 -Diotdb.pipe.epoch.insert.rows.aggregation.perf.warmup.iterations=5 -Diotdb.pipe.epoch.insert.rows.aggregation.perf.iterations=10 testAt head commit
d3cf02ee1b4, the test was rerun through the isolated Java 17 JUnitCore harness in three independent JVMs with a 1 GiB maximum heap. Each result is the median of 10 measured iterations after 5 warmup iterations:Workload: 10,000 rows for one device. Every row has the same content-equivalent schema, with 100 independently decoded measurement names per row. This benchmark exercises the identical-schema path; it does not directly measure the shorter-subsequence fast path.
Environment: Java 17.0.15 Oracle HotSpot, Windows NT 10.0.26200.0 x64, 13th Gen Intel Core i9-13900H, benchmark JVM
-Xmx1024m.This is a method-level steady-state microbenchmark of schema aggregation, not an end-to-end write-throughput benchmark.
Validation
mvn spotless:apply -pl iotdb-core/datanodegit diff --checkTsFileEpochManagerTest: 3 tests passedTsFileEpochManagerTestplus the default-skippedTsFileEpochManagerPerformanceTest:OK (4 tests)d3cf02ee1b4; results are shown above.The full Maven test path could not reach these tests locally because the workspace has stale cross-module/generated artifacts, and the reactor fallback is blocked while compiling generated Thrift code by a missing
javax.annotationclass. The isolated Java 17 runs above verify the changed classes; CI provides clean-reactor verification.This PR has:
Key changed/added classes
TsFileEpochManagerTsFileEpochManagerTestTsFileEpochManagerPerformanceTest