Skip to content

impl(pubsub): implement publish request hedging runtime - #6809

Closed
suzmue wants to merge 1 commit into
googleapis:mainfrom
suzmue:pubsub-hedging-runtime
Closed

suzmue wants to merge 1 commit into
googleapis:mainfrom
suzmue:pubsub-hedging-runtime

Conversation

@suzmue

@suzmue suzmue commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Implement request hedging for batch publishing in the Pub/Sub publisher. When request hedging is enabled, if an initial publish RPC does not complete within the configured delay threshold and tokens are available in the token bucket, a hedged publish RPC is dispatched with NeverRetry. The first attempt to succeed resolves the pending publish futures, refills the token bucket, and cancels any remaining in-flight attempts via a shared CancellationToken. Only errors from the initial request are returned to the user, all other errors are discarded.

Rate-limited hedges (where no token is available at deadline expiry) are discarded to shed load and avoid sending excessive requests during sustained backend latency spikes.

Hedged request timeouts are capped at 10 seconds pending retry policy timeout integration (it should be the minimum of 10 seconds and total_timeout of the original request).

For #6776

Implement request hedging for batch publishing in the Pub/Sub publisher.
When request hedging is enabled, if an initial publish RPC does not complete
within the configured delay threshold and tokens are available in the token
bucket, a hedged publish RPC is dispatched with `NeverRetry`. The first attempt
to succeed resolves the pending publish futures, refills the token bucket,
and cancels any remaining in-flight attempts via a shared CancellationToken.

Rate-limited hedges (where no token is available at deadline expiry) are
discarded to shed load and avoid sending excessive requests during sustained
backend latency spikes.

Hedged request timeouts are capped at 10 seconds pending retry policy timeout
integration.
@suzmue
suzmue requested a review from a team as a code owner September 15, 2026 03:16
@product-auto-label product-auto-label Bot added the api: pubsub Issues related to the Pub/Sub API. label Sep 15, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces request hedging to the Pub/Sub publisher by adding a dedicated HedgingScheduler and BatchState management. It refactors the batching logic to support concurrent and sequential actors with hedging options, and adds comprehensive test coverage for various hedging scenarios. Feedback on the changes suggests removing the unused base64 dependency and RequestOptionsBuilder import, as well as releasing the Mutex lock early in BatchState::complete to minimize lock contention during non-atomic operations.

Comment thread src/pubsub/Cargo.toml
Comment on lines 32 to 34
async-trait.workspace = true
base64.workspace = true
google-cloud-auth = { workspace = true }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The base64 dependency is added to Cargo.toml but is not used anywhere in the changes or the crate. We should remove it to avoid unnecessary dependency bloat and keep the build clean.

Suggested change
async-trait.workspace = true
base64.workspace = true
google-cloud-auth = { workspace = true }
async-trait.workspace = true
google-cloud-auth = { workspace = true }
References
  1. Expose Hidden Costs: Question new dependencies or patterns that add significant boilerplate for minimal gain. (link)

Comment on lines +15 to +16
use google_cloud_gax::options::RequestOptionsBuilder;
use google_cloud_gax::retry_policy::{NeverRetry, RetryPolicyExt};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The RequestOptionsBuilder import is unused in this file and can be safely removed.

Suggested change
use google_cloud_gax::options::RequestOptionsBuilder;
use google_cloud_gax::retry_policy::{NeverRetry, RetryPolicyExt};
use google_cloud_gax::retry_policy::{NeverRetry, RetryPolicyExt};
References
  1. Formatting: Code must be formatted with cargo fmt. (link)

Comment on lines +131 to +140
fn complete(&self, resp: crate::Result<PublishResponse>) {
let mut lock = self.txs.lock().unwrap();
if let Some((txs, done_tx)) = lock.take() {
self.cancel_token.cancel();
if resp.is_ok() {
self.token_bucket.refill();
}
let _ = done_tx.send(batch_resolve_publish_futures(resp, txs));
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Holding a Mutex lock while performing potentially blocking or complex operations (such as cancelling a token, refilling a token bucket, or sending messages over channels) can lead to unnecessary lock contention and potential deadlocks. Since we only need to atomically take the senders from self.txs, we can lock the mutex, take() the option, and release the lock immediately before executing the rest of the completion logic.

Suggested change
fn complete(&self, resp: crate::Result<PublishResponse>) {
let mut lock = self.txs.lock().unwrap();
if let Some((txs, done_tx)) = lock.take() {
self.cancel_token.cancel();
if resp.is_ok() {
self.token_bucket.refill();
}
let _ = done_tx.send(batch_resolve_publish_futures(resp, txs));
}
}
fn complete(&self, resp: crate::Result<PublishResponse>) {
let txs_and_done_tx = self.txs.lock().unwrap().take();
if let Some((txs, done_tx)) = txs_and_done_tx {
self.cancel_token.cancel();
if resp.is_ok() {
self.token_bucket.refill();
}
let _ = done_tx.send(batch_resolve_publish_futures(resp, txs));
}
}
References
  1. Unnecessary locks: Scrutinize any use of Mutex<>. Do multiple threads really need to access this data? Could we avoid locks with a different model? (link)

@dbolduc

dbolduc commented Sep 15, 2026

Copy link
Copy Markdown
Member

I think this PR is too big for me to provide good feedback. Can we break it down, e.g. start with some of the types from hedging.rs?

@codecov

codecov Bot commented Sep 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.77778% with 17 lines in your changes missing coverage. Please review.
✅ Project coverage is 97.03%. Comparing base (9ba7faa) to head (b88249b).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/pubsub/src/publisher/hedging.rs 97.36% 11 Missing ⚠️
src/pubsub/src/publisher/actor.rs 98.19% 6 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff            @@
##             main    #6809    +/-   ##
========================================
  Coverage   97.02%   97.03%            
========================================
  Files         326      327     +1     
  Lines      107511   108204   +693     
========================================
+ Hits       104310   104992   +682     
- Misses       3201     3212    +11     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@suzmue
suzmue marked this pull request as draft September 15, 2026 17:16
@suzmue

suzmue commented Sep 15, 2026

Copy link
Copy Markdown
Contributor Author

I think this PR is too big for me to provide good feedback. Can we break it down, e.g. start with some of the types from hedging.rs?

Closing this, I am breaking it up:

So far see:
#6818
#6820

@suzmue suzmue closed this Sep 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: pubsub Issues related to the Pub/Sub API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants