feat(storage): add a static delay open request hedging strategy - #16344
feat(storage): add a static delay open request hedging strategy#16344ajayky-os wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces experimental request hedging for ReadObject() streams to reduce tail latency by racing duplicate requests. It implements HedgedObjectReadSource and a dynamically-scaling HedgingThreadPool with token-bucket rate limiting and concurrency throttling. The review comments identify critical issues that must be addressed: a potential self-join deadlock when capturing std::shared_ptr<HedgingThreadPool> in the hedge task lambda, a concurrency race condition where checking and incrementing active hedges are not atomic, and performance overhead from zero-initializing the read buffer with std::vector<char> instead of std::unique_ptr<char[]>.
7111789 to
5794352
Compare
5794352 to
1bb66ee
Compare
| } | ||
| return; | ||
| } | ||
| std::unique_ptr<char[]> buffer(new char[n]); |
There was a problem hiding this comment.
Is there any limit on n? if not then what if n is very large like 100 MB and it couldn't get allocated? will it be crashed without capturing the error?
There was a problem hiding this comment.
- Added MaximumHedgeBufferOption: Added this to options.h with a default of 64 * 1024 * 1024 (64MB) and registered it in the ClientOptionList.
- Added the Bypass Logic: In connection_impl.cc, right before deciding whether to wrap the stream in a HedgedObjectReadSource, added a check against the user's requested range. If they ask for 100MB in a single read, it safely bypasses the hedge pool and executes entirely inline:
if (request.HasOption<ReadRange>()) {
auto const range = request.GetOption<ReadRange>().value();
if (range.begin >= 0 && range.end >= range.begin &&
static_cast<std::size_t>(range.end - range.begin) > max_buffer) {
return retry_source_factory(); // Safely run inline!
}
}
|
Instead of adding a new |
549783f to
f3a3c65
Compare
|
/gcbrun |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #16344 +/- ##
========================================
Coverage 92.24% 92.24%
========================================
Files 2227 2232 +5
Lines 209614 210014 +400
========================================
+ Hits 193351 193723 +372
- Misses 16263 16291 +28 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
21028b9 to
226f19f
Compare
f55a602 to
0023056
Compare
|
/gcbrun |
0023056 to
a1b38a3
Compare
|
/gcbrun |
1 similar comment
|
/gcbrun |
This will be a breaking change for people already using the |
a1b38a3 to
27342aa
Compare
|
/gcbrun |
Thanks for pointing out. I have updated all the calls to set I hit some roadblocks in setting the existing
|
Implement TTFB Speculative Hedging with Configurable Connect Timeouts
Overview
This PR introduces a concurrent, speculative hedging architecture to the GCS C++ SDK. The primary goal is to mitigate extreme tail latencies (e.g., 20s+ stalls) caused by OS-level TCP/kernel drops during periods of high-throughput network congestion.
This architecture introduces two primary mitigation layers:
This is the first PR in a series. A follow-up adds a dynamic strategy that adapts the hedge delay to observed latency percentiles; this PR uses a fixed, configurable delay.
Architectural Highlights
1. TTFB-Exclusive Hedging (No Data Corruption)
Naive hedging of an `ObjectReadSource` stream risks severe data corruption and network exhaustion by duplicating many payload downloads.
This implementation explicitly restricts hedging to the stream's Open Phase (TTFB). Once a socket wins the initial connection race, the background thread gracefully exits, and all subsequent payload chunk reads continue sequentially on the caller's thread. This guarantees structural integrity and prevents multi-stream bandwidth DDoS.
2. Bounded Hedging Thread Pool
To prevent queue starvation and CPU exhaustion under heavy load, the `HedgingThreadPool` enforces strict gating mechanisms:
3. Configurable Native Connect Timeout
The existing `DownloadStallTimeoutOption` maps to `CURLOPT_LOW_SPEED_TIME`, meaning aggressive timeouts would unintentionally kill healthy large-payload streams during minor jitter.
This PR introduces `HttpConnectTimeoutOption`, which maps explicitly to `CURLOPT_CONNECTTIMEOUT_MS`, allowing users to build a strict guillotine specifically for stalled TCP handshakes without threatening payload integrity.
Usage
Users can opt-in to the Hybrid Architecture via standard configuration options:
auto options = google::cloud::Options{}
.setgoogle::cloud::storage_experimental::EnableReadHedgingOption(true)
.setgoogle::cloud::storage_experimental::ReadHedgeDelayOption(std::chrono::milliseconds(500))
.setgoogle::cloud::storage_experimental::MaxConcurrentHedgesOption(15)
.setgoogle::cloud::storage_experimental::HttpConnectTimeoutOption(std::chrono::milliseconds(1000));
auto client = gcs::Client(options);
(Note: `hedge_pool_` is only allocated if `EnableReadHedgingOption` is true, enforcing the zero-overhead principle for non-hedging users).
Performance Benchmarks
We executed a 1-hour sequential testing suite (`us-central1`, 60 concurrent workers, 1MB payloads) comparing the baseline SDK against this architecture.
Baseline (Hedging Disabled):
Static Hedging (Enabled):