peterxcli opened a new pull request, #11136:
URL: https://github.com/apache/ozone/pull/11136

   ## What changes were proposed in this pull request?
   
   The S3 Gateway CopyObject path re-hashes every byte of the object during the 
copy: `ObjectEndpoint#copyObject` wraps the source `OzoneInputStream` in a 
`java.security.DigestInputStream`, and the computed MD5 becomes the destination 
key's ETag (in `ObjectEndpoint#copy` for the normal write path and in 
`ObjectEndpointStreaming#copyKeyWithStream` for the datastream path).
   
   A whole-object copy produces byte-identical content, and since HDDS-10403 
the source key's metadata already carries its content-based ETag. When that 
stored ETag is a plain (non-multipart) MD5, it is also the correct content MD5 
for the destination, so the recomputation is pure wasted CPU.
   
   This PR:
   
   * On CopyObject (not UploadPartCopy), when the source key metadata holds an 
ETag without the `-N` multipart suffix, skips the `DigestInputStream` entirely 
and stores the source's ETag on the destination key. The `CopyObjectResponse` 
ETag is still read back from the re-fetched destination key metadata, so the 
response ETag and the stored ETag stay consistent.
   * Otherwise (missing ETag, or aggregate `-N` ETag from an MPU-created 
source) keeps the current digesting behavior, so the destination still gets a 
proper content MD5. For MPU-sourced whole-object copies this matches AWS, which 
returns a plain MD5 for copies under 5GB.
   * Applies the same logic to both write variants: `ObjectEndpoint#copy` / 
`S3ObjectWriteGuard#copyFrom` and the datastream path 
`ObjectEndpointStreaming#copyKeyWithStream`.
   
   Pure CPU optimization; no protocol change and no proto change. 
UploadPartCopy is untouched — part ETags are digests of the part's own bytes 
and are produced on a separate code path.
   
   ## What is the link to the Apache JIRA
   
   https://issues.apache.org/jira/browse/HDDS-16315
   
   ## How was this patch tested?
   
   New and updated unit tests in the s3gateway module (`TestObjectPut`: 51 
tests, `TestUploadWithStream`: 6 tests, `TestMultipartUploadWithCopy`: 3 tests 
— all passing):
   
   * `TestObjectPut#testCopyObjectReusesSourceETagWithoutRehashing` — plain-MD5 
source: the source ETag is reused; the thread-local MD5 digest is mocked to 
throw if `update` is called, so any re-hashing fails the test. Also asserts the 
`CopyObjectResponse` ETag equals the ETag stored on the destination key.
   * `TestObjectPut#testCopyObjectRecomputesETagForMultipartSource` — `-N` 
source ETag: destination gets a freshly computed content MD5.
   * `TestObjectPut#testCopyObjectComputesETagWhenSourceHasNoETag` — source 
without an ETag: destination gets a freshly computed content MD5.
   * `TestObjectPut#testCopyObjectMessageDigestResetDuringException` — updated 
to seed the source with a `-N` ETag so it keeps exercising the digesting path 
and its thread-local digest reset.
   * `TestUploadWithStream#testUploadWithCopyReusesSourceETag` and 
`#testUploadWithCopyRecomputesETagForMultipartSource` — the same matrix for the 
datastream write path; the existing `#testUploadWithCopy` covers the streaming 
no-ETag case.
   * `TestMultipartUploadWithCopy` passes unchanged, confirming UploadPartCopy 
behavior is unaffected.
   
   `./hadoop-ozone/dev-support/checks/checkstyle.sh` was run over the 
repository and reports 0 violations.
   
   ### Benchmark
   
   Measured on an Apple M4 / JDK 21 with an off-tree harness driving 
`ObjectEndpoint` against the
   in-memory client stub. The A/B is driven by the shipped code rather than by 
two builds: a source
   whose stored ETag is a plain MD5 takes the new path, a source whose ETag 
ends in `-N` takes the
   digesting path. Arms are interleaved in one JVM, medians over >= 12 
iterations after warmup.
   
   | object | digesting (before) | ETag reused (after) | saved |
   |---|---|---|---|
   | 1 MiB | 3.78 ms | 1.54 ms | 2.24 ms (59%) |
   | 8 MiB | 14.78 ms | 2.35 ms | 12.43 ms (84%) |
   | 64 MiB | 95.98 ms | 8.76 ms | 87.22 ms (91%) |
   | 256 MiB | 407.32 ms | 46.22 ms | 361.09 ms (89%) |
   
   Path selection was confirmed by injecting a counting `MessageDigest` into
   `EndpointBase.getMD5DigestInstance()`: the digesting arm feeds exactly 
67,108,864 bytes to MD5 for
   a 64 MiB object (16 reads at the 4 MiB `ozone.s3g.client.buffer.size`), the 
reuse arm feeds 0.
   
   The stub holds everything in memory, so these percentages are an upper bound 
and not end-to-end
   cluster numbers. The portable result is the absolute one: the change removes 
roughly **1.2-1.4 s
   of S3 Gateway CPU per GiB copied** (JDK MD5 measures 0.72-0.81 GiB/s here; 
`openssl speed md5`
   independently reports 0.85 GiB/s). Because MD5 is serial and runs on the 
request thread, that is
   also ~1.2 core-seconds freed per GiB of copy throughput on a loaded gateway.
   
   Generated-by: Claude Code (claude-fable-5)
   


-- 
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