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]
