feat(grpc-gcp): move scale-up to background worker - #14206
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces asynchronous background scale-up logic for GcpManagedChannel to prevent blocking caller threads during channel creation, adding new configuration options like scaleUpCooldown and maxScaleUpPercent along with comprehensive tests. The review feedback highlights critical concurrency issues that need to be addressed: first, recursive retries in submitScaleUpWorker upon executor rejection could block gRPC transport threads or cause a StackOverflowError; second, race conditions in shutdownNow() and shutdown() could allow background channels to be published after shutdown has initiated, leading to silent resource leaks. These state updates should be properly synchronized.
f100bbd to
441212f
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces an asynchronous background scale-up worker for dynamic scaling in GcpManagedChannel, moving the scale-up logic off the caller thread. It adds new configuration options such as scaleUpCooldown and maxScaleUpPercent to control scaling behavior, ensures inactive channels are skipped during channel selection, and adds comprehensive unit tests. The review feedback identifies critical issues, including a potential ArithmeticException when the channel pool is empty, thread safety concerns in shutdown() and shutdownNow() due to unsynchronized snapshot creation, and exception-handling robustness during channel building. Additionally, it suggests using Google Truth assertions consistently across the new test file for better readability.
441212f to
d41c30a
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces an asynchronous background scale-up worker to GcpManagedChannel to handle dynamic scaling off the caller thread, along with new configuration options (scaleUpCooldown, maxScaleUpPercent) and robust shutdown handling. It also updates channel selection strategies to skip inactive channels and adds comprehensive unit tests. The review feedback suggests optimizing the performance of monitoring and routing methods (getMinActiveStreams, getMaxActiveStreams, and pickLeastBusyWithFallback) to avoid potential lock contention on the critical path by utilizing atomic variables or thread-safe collections.
| import javax.annotation.Nullable; | ||
|
|
||
| /** A channel management factory that implements grpc.Channel APIs. */ | ||
| public class GcpManagedChannel extends ManagedChannel { |
There was a problem hiding this comment.
For a follow-up pull request: I think that this class needs to be split. It does more than 'just being a managed channel' and is almost 3,000 LoC, which makes it hard to read.
There was a problem hiding this comment.
Yes, will do once all changes are queued lands here
| public int getMaxActiveStreams() { | ||
| return channelRefs.stream().mapToInt(ChannelRef::getActiveStreamsCount).max().orElse(0); | ||
| return channelRefs.stream() | ||
| .filter(ChannelRef::isActive) | ||
| .mapToInt(ChannelRef::getActiveStreamsCount) | ||
| .max() | ||
| .orElse(0); | ||
| } |
There was a problem hiding this comment.
This method allocates a number of different (small) objects on every invocation, so it should preferably only be called on the hot path when it is really necessary. It is currently always called in pickLeastBusyNoFallback() when p2c is used for channel selection, including when dynamic scaling is disabled. Meaning that it is potentially called on every RPC for no good reason. Could we fix that?
There was a problem hiding this comment.
Updated it to lazy be called lazily only when DCP is enabled.
d41c30a to
da55725
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request introduces asynchronous background scale-up logic to GcpManagedChannel to prevent caller threads from blocking during channel creation, alongside new configuration options like scaleUpCooldown and maxScaleUpPercent to control scaling behavior. It also updates channel selection methods to skip inactive channels and improves channel reuse and shutdown handling. The review feedback highlights two critical issues where calling getChannelRef or getChannelRefByAffinityRef directly when the pool is initialized with zero channels (initSize(0)) will fail because channelRefs is empty. It is recommended to call createFirstChannel() at the entry of both methods to ensure the first channel is properly initialized.
| private Duration scaleUpCooldown = Duration.ofSeconds(10); | ||
| private int scaleDownConsecutiveLowLoadChecks = 3; | ||
| private int consecutiveLowLoadChecks; | ||
| private int maxScaleUpPercent = 30; |
There was a problem hiding this comment.
(Not related to this PR, and not something we should do in this PR, but it came up in my head while reading this): Should we consider an 'emergency load level' where this maxScaleUpPercent is ignored. Meaning: if the system has been idle for a while and has scaled down to its minChannels (e.g. 2), and then gets a sudden burst of traffic, this limit slows down the scale-up. And while that is normally reasonable, would it maybe make sense to say something like 'if the overall load over all channels is >75 streams per channel, then we ignore this cooldown'?
With dynamic scaling enabled, maybeDynamicUpscale() ran synchronously on the caller thread during channel selection, before the stream was counted. It scaled on the pool-wide average only, one channel per trigger, with no cooldown — so a burst could add channels one RPC at a time while the caller waited on channel construction, and a single hot channel never triggered growth if the average stayed low.
Change