Repository navigation
[Java] Close GcsUtilV2's small-file performance gaps with GcsUtilV1 - #40677
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #40677 +/- ##
=============================================
- Coverage 56.16% 0 -56.17%
=============================================
Files 1124 0 -1124
Lines 178336 0 -178336
Branches 1489 0 -1489
=============================================
- Hits 100170 0 -100170
+ Misses 75645 0 -75645
+ Partials 2521 0 -2521
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Assigning reviewers: R: @chamikaramj for label java. This pull request likely touches a core component ("core" label). Please review with scrutiny. Note: If you would like to opt out of this review, comment Available commands:
The PR bot will only process comments in the main thread (not review comments). |
There was a problem hiding this comment.
📋 Review Summary
This PR effectively closes the small-file performance gap between GcsUtilV2 and GcsUtilV1 by sharing the process-wide HTTP transport (Transport::getTransport), opening resumable upload sessions asynchronously on a background thread pool, and increasing default rewrite concurrency from 32 to 128. The changes are clean, well-documented, and thoroughly tested.
🔍 General Feedback
- The asynchronous
WriteChannelinitialization inGcsWritableByteChannelproperly handles exception translation, thread interruption, and metric recording (ServiceCallMetricandMetricsContainerbinding). - Sharing
Transport::getTransportacrossStorageinstances resolves connection pool fragmentation when--gcsPerformanceMetricscreates per-operation scoped clients. - Unit tests in
GcsUtilV2Testcover background execution, error propagation on write/close, and precondition/chunk-size verification.
💡 Tip: Comment /gemini review on this pull request to trigger a new Gemini code review.
|
Also adding @damccorm since that's some more changes I mentioned offline to close the performance gaps between GcsUtil V2 and V1. |
damccorm
left a comment
There was a problem hiding this comment.
Nice investigation and benchmark results! Left two minor comments on GcsWritableByteChannel.
Summary
On Dataflow, GcsUtil V2 (java-storage) was up to 36% slower than V1 (gcsio) for workloads with thousands of small files. This PR makes three changes to
GcsUtilV2that bring it within noise of V1:Transport.getTransport(), the same one V1 uses. Before, each client built its ownNetHttpTransport. With--gcsPerformanceMetrics, V2 builds a client on every open and create, so every file got a separate connection pool, and most ranged GETs paid a fresh TLS handshake.Storage.writer()blocks while it starts the resumable upload session.create()now does that on a background thread, and the channel waits for it at the firstwrite()or atclose(). gcsio does the same for V1.--gcsMaxConcurrentRewritesstill overrides the default.Performance
Setup: ParquetIOLT on Dataflow,
f10_s1k(40 GB), 3 × e2-standard-2, legacy worker, one run per arm, so treat differences under about ±15% as noise. Each row shows the effect of adding that change on top of the previous ones. V1 served as a control and stayed within ±7%.All three together, compared with master:
Bytes and GCS request counts are unchanged; only connection setup, session-start latency and parallelism changed.
Behavior changes
GcsUtilV2.create()no longer reports errors from starting the upload session. They surface at the firstwrite()or atclose(), as anIOException(e.g.AccessDeniedException), the same as V1.GcsInsertAPI-request metric is recorded when the session start actually succeeds or fails, not whencreate()returns.--gcsMaxConcurrentRewritesis now 128 (it was 32), but the actual number of threads isMIN(total_number_of_files, maxCurrrentRewrites).Notes
StorageBatchis a possible follow-up.