enable gRPC client retries with jitter by default#549
Conversation
Verify the server was called exactly once, confirming retries were actually suppressed for codes.Internal (not just that the error code was correct). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
eslerm
left a comment
There was a problem hiding this comment.
🤖: Retry-with-jitter is well-implemented — backoff config, code filtering, and WaitForReady are all correct. Two concerns.
| EnableClientStreamReceiveTimeHistogram bool `envconfig:"ENABLE_CLIENT_STREAM_RECEIVE_TIME_HISTOGRAM" default:"true"` | ||
| EnableClientStreamSendTimeHistogram bool `envconfig:"ENABLE_CLIENT_STREAM_SEND_TIME_HISTOGRAM" default:"true"` | ||
| GrpcClientMaxRetry uint `envconfig:"GRPC_CLIENT_MAX_RETRY" default:"0"` | ||
| GrpcClientMaxRetry uint `envconfig:"GRPC_CLIENT_MAX_RETRY" default:"2"` |
There was a problem hiding this comment.
🤖: Flipping default:"0" → default:"2" silently activates retries for every consumer of GRPCDialOptions(). During outages this adds ~300ms latency and up to 3x RPC traffic before errors surface. Worth calling out in the PR description and/or a CHANGELOG entry so teams can opt out with GRPC_CLIENT_MAX_RETRY=0 if needed.
| return &healthpb.HealthCheckResponse{Status: healthpb.HealthCheckResponse_SERVING}, nil | ||
| } | ||
|
|
||
| func TestGRPCDialOptions_RetriesUnavailable(t *testing.T) { |
There was a problem hiding this comment.
🤖: sync.OnceValue means state() is locked to MaxRetry=42 from TestGetEnv before this test runs. The headline invariant — retries capped at 2 by default — has no test coverage. Consider adding a test that calls envconfig.Process("", &env) on a fresh struct and asserts env.GrpcClientMaxRetry == 2.
Summary
Change the default gRPC client retry configuration to provide baseline
resilience against transient
Unavailableerrors (connection failures,load balancer overloads, Cloud Run cold starts).
GRPC_CLIENT_MAX_RETRYfrom 0 → 2BackoffExponential(100ms)toBackoffExponentialWithJitter(100ms, 0.2)to desynchronize retriersand prevent thundering herd on recovery
codes.Unavailable(request never reached server)Motivation
Addresses CUS-172 /
chainguard-dev/internal-dev#18988,
a follow-up from INC-44 where internal usage spikes overwhelmed the
shared load balancer and caused cascading dial timeouts.
Current state in mono (11 gRPC clients from the API server)
Problems:
as user-visible errors immediately
causing synchronized retry waves (thundering herd)
connections are silently dropped by Cloud Run after ~10 min, causing
reconnection storms under load
Design decisions
Why
codes.Unavailableonly (notcodes.Internal):Unavailablemeans the request never reached the server — always safeto retry.
Internalmeans the server received and potentially processedthe request. Retrying non-idempotent mutations (e.g., datastore
CreateRPCs) after an
Internalerror could produceAlreadyExistsinstead ofsuccess if the first attempt committed but the response was lost. The 5
per-client overrides in mono that add
codes.Internalmade an explicitchoice that this tradeoff is acceptable for those services.
Why default 2 (not higher):
Matches the retry count already used by the 5 production clients that
have been tuned. With exponential backoff (100ms base + 20% jitter),
2 retries complete within ~300ms, keeping tail latency reasonable.
Override: Services can set
GRPC_CLIENT_MAX_RETRY=0to disableretries entirely if needed.
Follow-up in mono
After this lands and mono bumps
go-grpc-kit, the per-client retryoverrides in mono can be simplified:
grpc_retry.WithBackoff(BackoffLinear(50ms))andgrpc_retry.WithMax(2)— they now get 2 retries with jitter from thedefault. They only need to keep
grpc_retry.WithCodes(Unavailable, Internal)to retain the
Internalretry behavior.Automatically gain 2 retries on
Unavailablewith no code changes.Test plan
cd pkg/options && go test ./...(
grpc_client_handled_totalwith retry labels)🤖 Generated with Claude Code
for
https://linear.app/chainguard/issue/CUS-234/enable-grpc-client-retries-with-jitter-by-default
https://linear.app/chainguard/issue/CUS-235/add-keepalive-to-all-grpc-clients