This is an automated email from the ASF dual-hosted git repository.
adoroszlai pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ozone.git
The following commit(s) were added to refs/heads/master by this push:
new 7800cec0f45 HDDS-15643. Eliminate redundant OM lookupKey RPC for EC
file checksum (#10594)
7800cec0f45 is described below
commit 7800cec0f45912061408cff2c0fd71fe33aadbf2
Author: Andrey Yarovoy <[email protected]>
AuthorDate: Thu Jun 25 15:07:10 2026 -0400
HDDS-15643. Eliminate redundant OM lookupKey RPC for EC file checksum
(#10594)
---
.../client/checksum/BaseFileChecksumHelper.java | 19 +-
.../client/checksum/ECFileChecksumHelper.java | 27 +-
.../checksum/ReplicatedFileChecksumHelper.java | 7 -
.../client/checksum/FileChecksumBenchmark.java | 594 +++++++++++++++++++++
.../client/checksum/TestFileChecksumHelper.java | 6 +-
5 files changed, 629 insertions(+), 24 deletions(-)
diff --git
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/BaseFileChecksumHelper.java
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/BaseFileChecksumHelper.java
index be63cb11e55..da85d4d3e17 100644
---
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/BaseFileChecksumHelper.java
+++
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/BaseFileChecksumHelper.java
@@ -68,12 +68,10 @@ public abstract class BaseFileChecksumHelper {
private long crcPerBlock = 0;
// initialization
- BaseFileChecksumHelper(
- OzoneVolume volume, OzoneBucket bucket, String keyName,
- long length,
+ public BaseFileChecksumHelper(OzoneVolume volume, OzoneBucket bucket,
+ String keyName, long length,
OzoneClientConfig.ChecksumCombineMode checksumCombineMode,
- ClientProtocol rpcClient) throws IOException {
-
+ ClientProtocol rpcClient, OmKeyInfo keyInfo) throws IOException {
this.volume = volume;
this.bucket = bucket;
this.keyName = keyName;
@@ -81,20 +79,13 @@ public abstract class BaseFileChecksumHelper {
this.combineMode = checksumCombineMode;
this.rpcClient = rpcClient;
this.xceiverClientFactory =
- ((RpcClient)rpcClient).getXceiverClientManager();
+ ((RpcClient) rpcClient).getXceiverClientManager();
+ this.keyInfo = keyInfo;
if (this.length > 0) {
fetchBlocks();
}
}
- public BaseFileChecksumHelper(OzoneVolume volume, OzoneBucket bucket,
- String keyName, long length,
- OzoneClientConfig.ChecksumCombineMode checksumCombineMode,
- ClientProtocol rpcClient, OmKeyInfo keyInfo) throws IOException {
- this(volume, bucket, keyName, length, checksumCombineMode, rpcClient);
- this.keyInfo = keyInfo;
- }
-
protected String getSrc() {
return "Volume: " + volume.getName() + " Bucket: " + bucket.getName() + " "
+ keyName;
diff --git
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECFileChecksumHelper.java
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECFileChecksumHelper.java
index 34259fbb371..1f6e18426aa 100644
---
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECFileChecksumHelper.java
+++
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ECFileChecksumHelper.java
@@ -18,8 +18,13 @@
package org.apache.hadoop.ozone.client.checksum;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.stream.Collectors;
import org.apache.hadoop.hdds.client.BlockID;
import org.apache.hadoop.hdds.client.ECReplicationConfig;
import org.apache.hadoop.hdds.client.StandaloneReplicationConfig;
@@ -29,6 +34,7 @@
import org.apache.hadoop.hdds.scm.OzoneClientConfig;
import org.apache.hadoop.hdds.scm.XceiverClientSpi;
import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
+import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
import org.apache.hadoop.hdds.scm.storage.ContainerProtocolCalls;
import org.apache.hadoop.hdds.security.token.OzoneBlockTokenIdentifier;
import org.apache.hadoop.ozone.client.OzoneBucket;
@@ -70,6 +76,7 @@ protected List<ContainerProtos.ChunkInfo>
getChunkInfos(OmKeyLocationInfo
Pipeline pipeline = keyLocationInfo.getPipeline();
List<DatanodeDetails> nodes = new ArrayList<>();
+ Map<DatanodeDetails, Integer> selectedReplicaIndexes = new HashMap<>();
ECReplicationConfig repConfig = (ECReplicationConfig)
pipeline.getReplicationConfig();
@@ -79,13 +86,31 @@ protected List<ContainerProtos.ChunkInfo>
getChunkInfos(OmKeyLocationInfo
// The stripe checksum we need to calculate checksums is only stored on
// replica_index = 1 and all the parity nodes.
nodes.add(dn);
+ selectedReplicaIndexes.put(dn, replicaIndex);
}
}
- pipeline = pipeline.toBuilder()
+ // Build a deterministic pipeline ID from the sorted node UUIDs so that
+ // XceiverClientManager can cache and reuse the gRPC connection across
files
+ // that share the same EC placement group (avoids a new connection per
file).
+ String nodeKey = nodes.stream()
+ .map(DatanodeDetails::getUuidString)
+ .sorted()
+ .collect(Collectors.joining(","));
+ PipelineID deterministicId = PipelineID.valueOf(
+ UUID.nameUUIDFromBytes(nodeKey.getBytes(StandardCharsets.UTF_8)));
+
+ // Use Pipeline.newBuilder() (not toBuilder()) so that nodeStatus starts
null.
+ // toBuilder() would copy the 5-node EC nodeStatus, causing setNodes(3
nodes)
+ // to detect the size mismatch and call PipelineID.randomId() ->
SecureRandom
+ // even though setId(deterministicId) immediately overrides it.
+ pipeline = Pipeline.newBuilder()
+ .setId(deterministicId)
.setReplicationConfig(StandaloneReplicationConfig
.getInstance(HddsProtos.ReplicationFactor.THREE))
+ .setState(pipeline.getPipelineState())
.setNodes(nodes)
+ .setReplicaIndexes(selectedReplicaIndexes)
.build();
List<ContainerProtos.ChunkInfo> chunks;
diff --git
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ReplicatedFileChecksumHelper.java
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ReplicatedFileChecksumHelper.java
index cded422180c..14d6d0b05a3 100644
---
a/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ReplicatedFileChecksumHelper.java
+++
b/hadoop-ozone/client/src/main/java/org/apache/hadoop/ozone/client/checksum/ReplicatedFileChecksumHelper.java
@@ -38,13 +38,6 @@
*/
public class ReplicatedFileChecksumHelper extends BaseFileChecksumHelper {
- public ReplicatedFileChecksumHelper(
- OzoneVolume volume, OzoneBucket bucket, String keyName, long length,
- OzoneClientConfig.ChecksumCombineMode checksumCombineMode,
- ClientProtocol rpcClient) throws IOException {
- super(volume, bucket, keyName, length, checksumCombineMode, rpcClient);
- }
-
public ReplicatedFileChecksumHelper(OzoneVolume volume, OzoneBucket bucket,
String keyName, long length,
OzoneClientConfig.ChecksumCombineMode checksumCombineMode,
diff --git
a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/checksum/FileChecksumBenchmark.java
b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/checksum/FileChecksumBenchmark.java
new file mode 100644
index 00000000000..1f0ba7c5c78
--- /dev/null
+++
b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/checksum/FileChecksumBenchmark.java
@@ -0,0 +1,594 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hadoop.ozone.client.checksum;
+
+import static org.mockito.Answers.CALLS_REAL_METHODS;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import org.apache.hadoop.hdds.client.BlockID;
+import org.apache.hadoop.hdds.client.ECReplicationConfig;
+import org.apache.hadoop.hdds.protocol.DatanodeDetails;
+import org.apache.hadoop.hdds.protocol.datanode.proto.ContainerProtos;
+import org.apache.hadoop.hdds.scm.OzoneClientConfig;
+import org.apache.hadoop.hdds.scm.XceiverClientFactory;
+import org.apache.hadoop.hdds.scm.XceiverClientReply;
+import org.apache.hadoop.hdds.scm.XceiverClientSpi;
+import org.apache.hadoop.hdds.scm.pipeline.Pipeline;
+import org.apache.hadoop.hdds.scm.pipeline.PipelineID;
+import org.apache.hadoop.ozone.client.OzoneBucket;
+import org.apache.hadoop.ozone.client.OzoneVolume;
+import org.apache.hadoop.ozone.client.rpc.RpcClient;
+import org.apache.hadoop.ozone.om.helpers.OmKeyArgs;
+import org.apache.hadoop.ozone.om.helpers.OmKeyInfo;
+import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfo;
+import org.apache.hadoop.ozone.om.helpers.OmKeyLocationInfoGroup;
+import org.apache.hadoop.ozone.om.protocol.OzoneManagerProtocol;
+import org.apache.ratis.thirdparty.com.google.protobuf.ByteString;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Benchmark for ECFileChecksumHelper that measures the actual checksum
+ * collection code path. Not part of the regular test suite — it takes ~2min
+ * per run (warmup + measurement across two EC configs × three latency
buckets).
+ *
+ * Validates fixes to BaseFileChecksumHelper and ECFileChecksumHelper:
+ * Fix 1: BaseFileChecksumHelper 7-arg constructor no longer chains to 6-arg
+ * (redundant OM lookupKey RPC eliminated: 2 calls/file -> 1).
+ * Fix 2: ECFileChecksumHelper uses a deterministic pipeline ID so
+ * XceiverClientManager can cache and reuse gRPC connections
+ * (new connection per file -> one connection per placement group).
+ * Fix 3: Pipeline.newBuilder() instead of toBuilder() avoids an unnecessary
+ * SecureRandom.nextBytes call on every file.
+ *
+ * Covers RS-3-2 (5 nodes, 3 data + 2 parity) and RS-6-3 (9 nodes, 6 data + 3
parity).
+ * For RS-3-2 the standalone pipeline has 3 selected nodes (index=1 + parity
{4,5}),
+ * stripe checksum = 12 bytes. For RS-6-3: 4 selected nodes (index=1 + parity
{7,8,9}),
+ * stripe checksum = 16 bytes.
+ *
+ * Each latency bucket runs a 10-second warmup followed by a 20-second
+ * measurement window. Throughput and per-file RPC counts are reported from
+ * the measurement window only.
+ *
+ * Run with:
+ * mvn test -pl hadoop-ozone/client \
+ * -Dtest=FileChecksumBenchmark#runBenchmark \
+ * -Dsurefire.failIfNoSpecifiedTests=false
+ *
+ * To profile with async-profiler, pass the agent via surefire argLine, e.g.:
+ * mvn test -pl hadoop-ozone/client \
+ * -Dtest=FileChecksumBenchmark#runBenchmark \
+ * -Dsurefire.failIfNoSpecifiedTests=false \
+ *
-DargLine="-agentpath:/opt/homebrew/lib/libasyncProfiler.dylib=start,event=wall,\
+ * interval=10ms,file=/tmp/profile.html"
+ */
+@Tag("benchmark")
+public class FileChecksumBenchmark {
+
+ private static final int NUM_THREADS = 5;
+ private static final int WARMUP_SECS = 10;
+ private static final int MEASURE_SECS = 20;
+ private static final int[] LATENCIES_MS = {0, 5, 10};
+ private static final long CONTAINER_ID = 1001L;
+ private static final long FILE_SIZE = 5000L;
+ // 136 = 4 x 34: every 4th file (idx % 4 == 0) gets a freshly built OmKeyInfo
+ // with random node UUIDs on each call, making the deterministic pipeline ID
+ // also effectively random -> guaranteed cache miss for those files.
+ // Max theoretical cache hit rate = 75% (102 stable / 136 total).
+ private static final int KEY_POOL = 136;
+ private static final int UNSTABLE_STEP = 4;
+
+ private static final ECReplicationConfig EC32 = new ECReplicationConfig(3,
2);
+ private static final ECReplicationConfig EC63 = new ECReplicationConfig(6,
3);
+
+ // RS-3-2: selected nodes = index 1 + parity {4, 5} = 3 nodes → 3 × 4 = 12
stripe bytes
+ private static final List<DatanodeDetails> EC_NODES = buildEcNodes(5);
+ private static final Pipeline EC_PIPELINE = buildEcPipeline(EC_NODES, EC32);
+ private static final OmKeyInfo[] KEY_INFOS = buildKeyInfos(EC_PIPELINE,
EC32);
+ private static final ContainerProtos.ContainerCommandResponseProto
GET_BLOCK_RESPONSE =
+ buildGetBlockResponse(12);
+
+ // RS-6-3: selected nodes = index 1 + parity {7, 8, 9} = 4 nodes → 4 × 4 =
16 stripe bytes
+ private static final List<DatanodeDetails> EC63_NODES = buildEcNodes(9);
+ private static final Pipeline EC63_PIPELINE = buildEcPipeline(EC63_NODES,
EC63);
+ private static final OmKeyInfo[] EC63_KEY_INFOS =
buildKeyInfos(EC63_PIPELINE, EC63);
+ private static final ContainerProtos.ContainerCommandResponseProto
EC63_GET_BLOCK_RESPONSE =
+ buildGetBlockResponse(16);
+
+ //
---------------------------------------------------------------------------
+ // Instrumented XceiverClientFactory
+ //
---------------------------------------------------------------------------
+
+ static class CountingXceiverClientFactory implements XceiverClientFactory {
+ // Stable pipeline IDs = KEY_POOL - KEY_POOL/UNSTABLE_STEP = 102.
+ // Cap the pool just above that so stable files always hit the cache while
+ // unstable files (random pipeline IDs) are never stored and are GC'd
immediately.
+ private static final int MAX_POOL_SIZE = KEY_POOL - KEY_POOL /
UNSTABLE_STEP + 10;
+ private final ContainerProtos.ContainerCommandResponseProto blockResponse;
+ private final AtomicLong newConnectionCount = new AtomicLong();
+ private final AtomicLong reuseCount = new AtomicLong();
+ // clientPool is intentionally NOT cleared between warmup and measurement:
+ // simulates a warmed-up JVM where connections established during warmup
+ // remain available -- the same state as a long-running service.
+ private final ConcurrentHashMap<String, XceiverClientSpi> clientPool =
+ new ConcurrentHashMap<>();
+
+ CountingXceiverClientFactory(
+ ContainerProtos.ContainerCommandResponseProto blockResponse) {
+ this.blockResponse = blockResponse;
+ }
+
+ void resetCounters() {
+ newConnectionCount.set(0);
+ reuseCount.set(0);
+ }
+
+ long getNewConnectionCount() {
+ return newConnectionCount.get();
+ }
+
+ long getReuseCount() {
+ return reuseCount.get();
+ }
+
+ @Override
+ public XceiverClientSpi acquireClientForReadData(Pipeline pipeline)
+ throws IOException {
+ String key = pipeline.getId().toString();
+ XceiverClientSpi existing = clientPool.get(key);
+ if (existing != null) {
+ reuseCount.incrementAndGet();
+ return existing;
+ }
+ XceiverClientSpi newClient = createMockDnClient(pipeline, blockResponse);
+ if (clientPool.size() < MAX_POOL_SIZE) {
+ XceiverClientSpi winner = clientPool.putIfAbsent(key, newClient);
+ if (winner != null) {
+ reuseCount.incrementAndGet();
+ return winner;
+ }
+ }
+ newConnectionCount.incrementAndGet();
+ return newClient;
+ }
+
+ @Override
+ public void releaseClientForReadData(XceiverClientSpi client,
+ boolean invalidate) { }
+
+ @Override
+ public XceiverClientSpi acquireClient(Pipeline pipeline) throws
IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void releaseClient(XceiverClientSpi client, boolean invalidate) { }
+
+ @Override
+ public XceiverClientSpi acquireClient(Pipeline pipeline,
+ boolean topologyAware) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void releaseClient(XceiverClientSpi client, boolean invalidate,
+ boolean topologyAware) { }
+
+ @Override
+ public void close() { }
+ }
+
+ //
---------------------------------------------------------------------------
+ // Benchmark result
+ //
---------------------------------------------------------------------------
+
+ static class BenchmarkResult {
+ private final long measuredFiles;
+ private final long wallMs;
+ private final long omCalls;
+ private final long xceiverNewCount;
+ private final long xceiverReuseCount;
+ private final int latencyMs;
+
+ BenchmarkResult(long measuredFiles, long wallMs, long omCalls,
+ long xceiverNewCount, long xceiverReuseCount, int latencyMs) {
+ this.measuredFiles = measuredFiles;
+ this.wallMs = wallMs;
+ this.omCalls = omCalls;
+ this.xceiverNewCount = xceiverNewCount;
+ this.xceiverReuseCount = xceiverReuseCount;
+ this.latencyMs = latencyMs;
+ }
+
+ long getMeasuredFiles() {
+ return measuredFiles;
+ }
+
+ long getWallMs() {
+ return wallMs;
+ }
+
+ long getOmCalls() {
+ return omCalls;
+ }
+
+ long getXceiverNewCount() {
+ return xceiverNewCount;
+ }
+
+ long getXceiverReuseCount() {
+ return xceiverReuseCount;
+ }
+
+ int getLatencyMs() {
+ return latencyMs;
+ }
+
+ double filesPerSec() {
+ return wallMs == 0
+ ? Double.POSITIVE_INFINITY : measuredFiles * 1000.0 / wallMs;
+ }
+
+ double omCallsPerFile() {
+ return measuredFiles == 0 ? 0 : (double) omCalls / measuredFiles;
+ }
+
+ long cacheHitPercent() {
+ long total = xceiverNewCount + xceiverReuseCount;
+ return total == 0 ? 0 : xceiverReuseCount * 100 / total;
+ }
+ }
+
+ //
---------------------------------------------------------------------------
+ // Core runner
+ //
---------------------------------------------------------------------------
+
+ private static BenchmarkResult measure(int latencyMs, ECReplicationConfig
repConfig,
+ OmKeyInfo[] keyPool, ContainerProtos.ContainerCommandResponseProto
blockResponse)
+ throws Exception {
+ AtomicLong omCallCount = new AtomicLong();
+ CountingXceiverClientFactory xceiverFactory =
+ new CountingXceiverClientFactory(blockResponse);
+ AtomicLong fileIdx = new AtomicLong();
+
+ OzoneManagerProtocol mockOm = mock(OzoneManagerProtocol.class);
+ when(mockOm.lookupKey(any(OmKeyArgs.class))).thenAnswer(invocation -> {
+ omCallCount.incrementAndGet();
+ if (latencyMs > 0) {
+ try {
+ Thread.sleep(latencyMs);
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ throw new IOException(ie);
+ }
+ }
+ OmKeyArgs args = invocation.getArgument(0);
+ int idx = Integer.parseInt(args.getKeyName().split("-")[1]);
+ // Every UNSTABLE_STEP-th file returns a freshly built OmKeyInfo with new
+ // random node UUIDs. Fix 2 computes its deterministic pipeline ID from
+ // those node UUIDs, so the result also changes each call -> cache miss.
+ if (idx % UNSTABLE_STEP == 0) {
+ return buildRandomKeyInfo(args.getKeyName(), repConfig);
+ }
+ return keyPool[idx];
+ });
+
+ RpcClient mockRpcClient = mock(RpcClient.class);
+ when(mockRpcClient.getOzoneManagerClient()).thenReturn(mockOm);
+ when(mockRpcClient.getXceiverClientManager()).thenReturn(xceiverFactory);
+
+ OzoneVolume mockVolume = mock(OzoneVolume.class);
+ when(mockVolume.getName()).thenReturn("vol");
+ OzoneBucket mockBucket = mock(OzoneBucket.class);
+ when(mockBucket.getName()).thenReturn("bucket");
+
+ OzoneClientConfig.ChecksumCombineMode combineMode =
+ OzoneClientConfig.ChecksumCombineMode.COMPOSITE_CRC;
+
+ Runnable task = () -> {
+ try {
+ int idx = (int) (fileIdx.getAndIncrement() % KEY_POOL);
+ String keyName = "file-" + idx;
+ OmKeyInfo keyInfo = mockRpcClient.getOzoneManagerClient().lookupKey(
+ new OmKeyArgs.Builder()
+ .setVolumeName("vol")
+ .setBucketName("bucket")
+ .setKeyName(keyName)
+ .setSortDatanodesInPipeline(true)
+ .setLatestVersionLocation(true)
+ .build());
+ new ECFileChecksumHelper(
+ mockVolume, mockBucket, keyName, FILE_SIZE, combineMode,
+ mockRpcClient, keyInfo)
+ .compute();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ };
+
+ // Warmup: establish connections, fill JIT caches, discard counts.
+ runFor(WARMUP_SECS * 1000L, task);
+ omCallCount.set(0);
+ xceiverFactory.resetCounters();
+ fileIdx.set(0);
+
+ // Measurement window.
+ long start = System.currentTimeMillis();
+ long measuredFiles = runFor(MEASURE_SECS * 1000L, task);
+ long wallMs = System.currentTimeMillis() - start;
+
+ return new BenchmarkResult(measuredFiles, wallMs, omCallCount.get(),
+ xceiverFactory.getNewConnectionCount(), xceiverFactory.getReuseCount(),
+ latencyMs);
+ }
+
+ private static long runFor(long durationMs, Runnable task) throws Exception {
+ AtomicBoolean running = new AtomicBoolean(true);
+ AtomicLong count = new AtomicLong();
+ ExecutorService executor = Executors.newFixedThreadPool(NUM_THREADS);
+ List<Future<?>> futures = new ArrayList<>();
+ for (int i = 0; i < NUM_THREADS; i++) {
+ futures.add(executor.submit((Callable<Void>) () -> {
+ while (running.get()) {
+ task.run();
+ count.incrementAndGet();
+ }
+ return null;
+ }));
+ }
+ Thread.sleep(durationMs);
+ running.set(false);
+ for (Future<?> f : futures) {
+ f.get();
+ }
+ executor.shutdown();
+ return count.get();
+ }
+
+ //
---------------------------------------------------------------------------
+ // JUnit entry point
+ //
---------------------------------------------------------------------------
+
+ @Test
+ public void runBenchmark() throws Exception {
+ System.out.println();
+ System.out.println("=== ECFileChecksum Collection Benchmark ===");
+ System.out.printf("Workload: %d threads, %ds warmup + %ds measurement per
config%n%n",
+ NUM_THREADS, WARMUP_SECS, MEASURE_SECS);
+
+ String header = String.format(
+ "%-10s %-10s %-10s %-10s %-12s %-10s %-14s %-14s %-9s",
+ "Latency", "Wall(ms)", "Files", "Files/s", "OM calls", "OM/file",
+ "XcNew(conn)", "XcReuse", "CacheHit%");
+ String rule = new String(new char[105]).replace('\0', '-');
+
+ System.out.println("--- RS-3-2 (3 data + 2 parity, 5 nodes) ---");
+ System.out.println(header);
+ System.out.println(rule);
+ for (int latencyMs : LATENCIES_MS) {
+ printRow(measure(latencyMs, EC32, KEY_INFOS, GET_BLOCK_RESPONSE));
+ }
+
+ System.out.println();
+ System.out.println("--- RS-6-3 (6 data + 3 parity, 9 nodes) ---");
+ System.out.println(header);
+ System.out.println(rule);
+ for (int latencyMs : LATENCIES_MS) {
+ printRow(measure(latencyMs, EC63, EC63_KEY_INFOS,
EC63_GET_BLOCK_RESPONSE));
+ }
+
+ }
+
+ private static void printRow(BenchmarkResult r) {
+ System.out.printf(
+ "%-10s %-10d %-10d %-10.1f %-12d %-10.2f %-14d %-14d %d%%%n",
+ r.getLatencyMs() + "ms",
+ r.getWallMs(),
+ r.getMeasuredFiles(),
+ r.filesPerSec(),
+ r.getOmCalls(),
+ r.omCallsPerFile(),
+ r.getXceiverNewCount(),
+ r.getXceiverReuseCount(),
+ r.cacheHitPercent());
+ }
+
+ //
---------------------------------------------------------------------------
+ // Helpers
+ //
---------------------------------------------------------------------------
+
+ private static List<DatanodeDetails> buildEcNodes(int count) {
+ List<DatanodeDetails> nodes = new ArrayList<>();
+ for (int i = 0; i < count; i++) {
+ nodes.add(DatanodeDetails.newBuilder()
+ .setUuid(UUID.fromString("00000000-0000-0000-0000-00000000000" + i))
+ .setHostName("dn" + i)
+ .setIpAddress("10.0.0." + i)
+ .build());
+ }
+ return nodes;
+ }
+
+ private static Pipeline buildEcPipeline(List<DatanodeDetails> nodes,
+ ECReplicationConfig repConfig) {
+ Map<DatanodeDetails, Integer> replicaIndexes = new HashMap<>();
+ for (int i = 0; i < nodes.size(); i++) {
+ replicaIndexes.put(nodes.get(i), i + 1);
+ }
+ return Pipeline.newBuilder()
+ .setId(PipelineID.randomId())
+ .setReplicationConfig(repConfig)
+ .setState(Pipeline.PipelineState.CLOSED)
+ .setNodes(nodes)
+ .setReplicaIndexes(replicaIndexes)
+ .build();
+ }
+
+ private static OmKeyInfo[] buildKeyInfos(Pipeline pipeline,
+ ECReplicationConfig repConfig) {
+ OmKeyInfo[] infos = new OmKeyInfo[KEY_POOL];
+ for (int i = 0; i < KEY_POOL; i++) {
+ OmKeyLocationInfo loc = new OmKeyLocationInfo.Builder()
+ .setBlockID(new BlockID(CONTAINER_ID, i))
+ .setPipeline(pipeline)
+ .setLength(FILE_SIZE)
+ .build();
+ infos[i] = new OmKeyInfo.Builder()
+ .setVolumeName("vol")
+ .setBucketName("bucket")
+ .setKeyName("file-" + i)
+ .setOmKeyLocationInfos(Collections.singletonList(
+ new OmKeyLocationInfoGroup(0,
+ Collections.singletonList(loc))))
+ .setCreationTime(0L)
+ .setModificationTime(0L)
+ .setDataSize(FILE_SIZE)
+ .setReplicationConfig(repConfig)
+ .setFileChecksum(null)
+ .setAcls(Collections.emptyList())
+ .build();
+ }
+ return infos;
+ }
+
+ private static ContainerProtos.ContainerCommandResponseProto
buildGetBlockResponse(
+ int stripeChecksumBytes) {
+ ByteString fourBytes = ByteString.copyFrom(new byte[4]);
+ ByteString stripeChecksum = ByteString.copyFrom(new
byte[stripeChecksumBytes]);
+
+ ContainerProtos.ChecksumData checksumData =
+ ContainerProtos.ChecksumData.newBuilder()
+ .setType(ContainerProtos.ChecksumType.CRC32)
+ .setBytesPerChecksum(512 * 1024)
+ .addChecksums(fourBytes)
+ .build();
+
+ ContainerProtos.ChunkInfo chunk = ContainerProtos.ChunkInfo.newBuilder()
+ .setChunkName("chunk0")
+ .setOffset(0)
+ .setLen(FILE_SIZE)
+ .setChecksumData(checksumData)
+ .setStripeChecksum(stripeChecksum)
+ .build();
+
+ ContainerProtos.DatanodeBlockID dnBlockId =
+ ContainerProtos.DatanodeBlockID.newBuilder()
+ .setContainerID(CONTAINER_ID)
+ .setLocalID(1)
+ .setBlockCommitSequenceId(1)
+ .build();
+
+ ContainerProtos.BlockData blockData =
+ ContainerProtos.BlockData.newBuilder()
+ .setBlockID(dnBlockId)
+ .addChunks(chunk)
+ .build();
+
+ ContainerProtos.GetBlockResponseProto getBlockResponse =
+ ContainerProtos.GetBlockResponseProto.newBuilder()
+ .setBlockData(blockData)
+ .build();
+
+ return ContainerProtos.ContainerCommandResponseProto.newBuilder()
+ .setCmdType(ContainerProtos.Type.GetBlock)
+ .setResult(ContainerProtos.Result.SUCCESS)
+ .setGetBlock(getBlockResponse)
+ .build();
+ }
+
+ /**
+ * Builds an OmKeyInfo whose EC pipeline has freshly generated random node
+ * UUIDs. Called on every lookupKey invocation for unstable files so the
+ * deterministic pipeline ID computed by Fix 2 is also effectively random per
+ * call, guaranteeing a cache miss.
+ */
+ private static OmKeyInfo buildRandomKeyInfo(String keyName,
+ ECReplicationConfig repConfig) {
+ int nodeCount = repConfig.getData() + repConfig.getParity();
+ List<DatanodeDetails> nodes = new ArrayList<>();
+ Map<DatanodeDetails, Integer> replicaIndexes = new HashMap<>();
+ for (int i = 0; i < nodeCount; i++) {
+ DatanodeDetails dn = DatanodeDetails.newBuilder()
+ .setUuid(UUID.randomUUID())
+ .setHostName("rdn" + i)
+ .setIpAddress("10.1.0." + i)
+ .build();
+ nodes.add(dn);
+ replicaIndexes.put(dn, i + 1);
+ }
+ Pipeline pipeline = Pipeline.newBuilder()
+ .setId(PipelineID.randomId())
+ .setReplicationConfig(repConfig)
+ .setState(Pipeline.PipelineState.CLOSED)
+ .setNodes(nodes)
+ .setReplicaIndexes(replicaIndexes)
+ .build();
+ OmKeyLocationInfo loc = new OmKeyLocationInfo.Builder()
+ .setBlockID(new BlockID(CONTAINER_ID, 0))
+ .setPipeline(pipeline)
+ .setLength(FILE_SIZE)
+ .build();
+ return new OmKeyInfo.Builder()
+ .setVolumeName("vol")
+ .setBucketName("bucket")
+ .setKeyName(keyName)
+ .setOmKeyLocationInfos(Collections.singletonList(
+ new OmKeyLocationInfoGroup(0,
+ Collections.singletonList(loc))))
+ .setCreationTime(0L)
+ .setModificationTime(0L)
+ .setDataSize(FILE_SIZE)
+ .setReplicationConfig(repConfig)
+ .setFileChecksum(null)
+ .setAcls(Collections.emptyList())
+ .build();
+ }
+
+ private static XceiverClientSpi createMockDnClient(Pipeline
standalonePipeline,
+ ContainerProtos.ContainerCommandResponseProto response) throws
IOException {
+ XceiverClientSpi mockDn = mock(XceiverClientSpi.class, CALLS_REAL_METHODS);
+ XceiverClientReply reply = new XceiverClientReply(
+ CompletableFuture.completedFuture(response));
+ try {
+ doReturn(reply).when(mockDn).sendCommandAsync(any());
+ } catch (ExecutionException | InterruptedException e) {
+ throw new IOException(e);
+ }
+ when(mockDn.getPipeline()).thenReturn(standalonePipeline);
+ return mockDn;
+ }
+}
diff --git
a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/checksum/TestFileChecksumHelper.java
b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/checksum/TestFileChecksumHelper.java
index bc894a58f9c..42243ae2d38 100644
---
a/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/checksum/TestFileChecksumHelper.java
+++
b/hadoop-ozone/client/src/test/java/org/apache/hadoop/ozone/client/checksum/TestFileChecksumHelper.java
@@ -145,7 +145,7 @@ private BaseFileChecksumHelper
checksumHelper(ReplicationType type, OzoneVolume
int length, OzoneClientConfig.ChecksumCombineMode combineMode, RpcClient
mockRpcClient, OmKeyInfo keyInfo)
throws IOException {
return type == ReplicationType.RATIS ? new ReplicatedFileChecksumHelper(
- mockVolume, mockBucket, "dummy", length, combineMode, mockRpcClient)
+ mockVolume, mockBucket, "dummy", length, combineMode, mockRpcClient,
keyInfo)
: new ECFileChecksumHelper(
mockVolume, mockBucket, "dummy", length, combineMode, mockRpcClient,
keyInfo);
}
@@ -346,8 +346,10 @@ public void testPutKeyChecksum() throws IOException {
OzoneClientConfig.ChecksumCombineMode combineMode =
OzoneClientConfig.ChecksumCombineMode.MD5MD5CRC;
+ OmKeyInfo keyInfo = rpcClient.getKeyInfo(
+ volume.getName(), bucket.getName(), keyName, false);
ReplicatedFileChecksumHelper helper = new ReplicatedFileChecksumHelper(
- volume, bucket, keyName, 10, combineMode, rpcClient);
+ volume, bucket, keyName, 10, combineMode, rpcClient, keyInfo);
helper.compute();
FileChecksum fileChecksum = helper.getFileChecksum();
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]