Skip to content

HDDS-15680. Cache per-block EC checksum pipeline within a file#10709

Open
yandrey321 wants to merge 7 commits into
apache:masterfrom
yandrey321:HDDS-15680
Open

HDDS-15680. Cache per-block EC checksum pipeline within a file#10709
yandrey321 wants to merge 7 commits into
apache:masterfrom
yandrey321:HDDS-15680

Conversation

@yandrey321

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

When computing a file checksum for an erasure-coded key, ECFileChecksumHelper builds a temporary STANDALONE pipeline (replica index 1 + parity nodes) for every block. All blocks of a file share the same EC placement group, so that pipeline is identical for every block — rebuilding it per block (node filtering, deterministic-ID hashing, allocation) is wasted work that grows with the number of blocks.

This PR caches the standalone pipeline per placement group inside the helper, so it is built once per file (on the first block) and reused for the rest. It builds on HDDS-15643 (#10594) — already on master — which removed the redundant OM lookupKey RPC and made the pipeline ID deterministic for cross-file connection reuse. This adds the intra-file reuse on top. It is purely client-side CPU savings: no wire, OM/SCM, or checksum-result changes.

What is the link to the Apache JIRA

https://issues.apache.org/jira/browse/HDDS-15680

How was this patch tested?

New unit tests in TestFileChecksumHelper cover the cache hit/miss paths, the node filtering, and the acquire-failure path.

Benchmarked with FileChecksumBenchmark#runBlockCacheBenchmark (5 threads, each checksumming random-placement EC files), comparing the cache off vs on while holding everything else equal.

Results — throughput gain (cache on ÷ off), by file size in blocks

EC Blocks/file Before After Speedup
RS-3-2 1 244,351 239,566 ~1.00×
RS-3-2 3 126,262 130,494 1.03×
RS-3-2 5 86,606 91,613 1.06×
RS-3-2 10 48,442 51,364 1.06×
RS-6-3 1 236,776 229,178 ~1.00×
RS-6-3 3 123,013 128,283 1.04×
RS-6-3 5 84,271 88,997 1.06×
RS-6-3 10 47,155 50,820 1.08×

Single-block files show no change (nothing to cache — the 1-block row is a control and sits within ~2-3% noise). The gain grows with file size as the cache eliminates the redundant per-block pipeline rebuilds — up to ~6–8% for large multi-block EC files, slightly higher for wider EC schemes (RS-6-3). The improvement is client-side CPU only and does not change the checksum result.

As a summary the fix would have slight performance penalty for small files, and single digit improvement for files spans to multiple blocks.

yandrey321 and others added 7 commits June 25, 2026 22:56
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-file XceiverClient reuse

buildChecksumPipeline now derives the STANDALONE pipeline ID from the sorted
UUIDs of the selected nodes via UUID.nameUUIDFromBytes. Files that land on
the same EC placement group produce an identical pipeline ID, so
XceiverClientManager can reuse the same gRPC connection across files
(cross-file reuse, Fix 2).

Pipeline.newBuilder() is used instead of ecPipeline.toBuilder() because
toBuilder copies the EC nodeStatus map; the resulting size mismatch caused
Pipeline's internal consistency check to fall back to a random ID, which
defeated the determinism.

Benchmark (5 threads, RS-3-2, 1-block files):
  Fix 3 only (random ID per file): ~800 files/s at 5ms, ~412 files/s at 10ms
  Fix 2+3 (deterministic ID):    ~94K files/s at 5ms, ~95K files/s at 10ms
  Speedup: 117x at 5ms DN latency, 231x at 10ms DN latency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…-3-only vs Fix-2+3 scenarios, fix Mockito contention

- Consolidate duplicate constants: WARMUP_SECS, MEASURE_SECS, LATENCIES_MS,
  MAX_POOL_SIZE (one constant replaces each pair)
- Restore runBlockCacheBenchmark scenarios to the meaningful comparison:
  Scenario A = Fix 3 only (per-file unique pipeline, intra-file reuse,
  NewConn/file = 1.00); Scenario B = Fix 2+3 (cross-file + intra-file
  reuse, NewConn/file = 0.00). This shows the latency-driven speedup clearly.
- Apply withSettings().stubOnly() to all hot-path Mockito mocks. Without
  stubOnly(), Mockito records every invocation via a global synchronized
  SequenceNumber and captures the full call stack via StackWalker on each
  call. At 0ms DN latency with 5 threads sharing a pool-hit XceiverClient
  mock, this caused severe serialization and f.get() blocking for minutes.
  stubOnly() skips invocation recording, eliminating both bottlenecks.
- Add buildRandomKeyInfoMultiBlock for Scenario A (N-block files with a
  fresh unique EC pipeline per file). Refactor buildRandomKeyInfo to delegate.
- Add -Dsurefire.fork.timeout=3600 to run instructions (default 1200s is
  less than the benchmark's ~25 min runtime).

Benchmark results (5 threads, 10s warmup + 20s measurement):
RS-3-2 5ms:  A=804 files/s (NewConn/file=1.00) vs B=200K files/s (0.00) -> 249x
RS-3-2 10ms: A=413 files/s (NewConn/file=1.00) vs B=194K files/s (0.00) -> 470x
RS-6-3 5ms:  A=698 files/s (NewConn/file=1.00) vs B=178K files/s (0.00) -> 256x
RS-6-3 10ms: A=361 files/s (NewConn/file=1.00) vs B=179K files/s (0.00) -> 496x
CacheHit% = 0/50/66/80% for 1/2/3/5 blocks (exactly (N-1)/N, Fix 3 verified).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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