grpcconf: add opt-in round_robin LB policy and client keepalive#7
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the shared grpcconf.Service client defaults to improve resiliency when dialing internal gRPC backends (particularly across Kubernetes pod rolls and dead/black-holed connections).
Changes:
- Default gRPC client load balancing to
round_robinviagrpc.WithDefaultServiceConfig. - Add client keepalive parameters (
Time: 5m,Timeout: 20s,PermitWithoutStream: false) to bound stalls on dead connections. - Clarify that caller-provided dial options are appended last (so they can override defaults).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Thanks — checked this against the pinned gRPC version (
Since |
Add a Policy field to Service selecting the client load-balancing policy: pick_first (the default, matching gRPC) or round_robin. round_robin spreads calls across all backend pods returned by a headless Service and drops failed sub-channels on re-resolution, instead of pick_first pinning to a single pod — the root cause of the 2026-07-13 API 504 storm. Callers opt in per service via config. Also add an unconditional client keepalive so a black-holed connection is detected in minutes rather than at the kernel TCP timeout (~15m), and an explicit blank import of the round_robin balancer so an upstream change can't silently drop it and revert us to pick_first. keepaliveTime is 5m to stay within the servers' default EnforcementPolicy.MinTime; a follow-up relaxes that and shortens it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
d9e2c91 to
c1f82c2
Compare
Bundled with the gRPC reliability changes in this release. v1.82.0 has no changes to the round_robin/pick_first balancers or DNS resolver; note the balancer registry is now case-sensitive by default, which our exact "round_robin" service-config name satisfies. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 3 changed files in this pull request and generated 1 comment.
Comments suppressed due to low confidence (1)
pkg/grpcconf/grpcconf.go:107
- When PolicyRoundRobin is enabled, the effectiveness of
round_robindepends on the name resolver returning multiple backend addresses (e.g., DNS A records for a headless Service) and re-resolving on failures. This code always dialss.URL()(Host+":"+Port) without normalizing/validating the target scheme, so a misconfigured Host value could silently result in no effective load balancing.
func (s *Service) Connection(opts ...grpc.DialOption) (*grpc.ClientConn, error) {
do := s.DialOptions()
do = append(do, opts...)
return grpc.NewClient(s.URL(), do...)
}
Summary
gRPC clients built via
grpcconf.Serviceused gRPC's defaultpick_firstbalancer: each client opens one connection to a single backend pod and pins to it for the connection's lifetime. When that pod is rolled the client keeps dialing the dead endpoint, and with no keepalive a stalled request hangs until the kernel TCP timeout (~15 min). This is the root cause of the 2026-07-13 API 504 storm.Changes
Policyfield onService—pick_first(default / zero value) orround_robin.round_robinis opt-in per service via config; callers targeting a headless Service setpolicy: round_robinto spread calls across all backend pods and drop failed sub-channels on re-resolution.Time: 5m,Timeout: 20s,PermitWithoutStream: false) so a black-holed connection is detected in minutes rather than at the TCP timeout. Applies regardless of policy (safe against the servers' defaultEnforcementPolicy)._ "google.golang.org/grpc/balancer/roundrobin"so an upstream grpc change can't silently drop the transitively-registered balancer and revert us topick_first.Caller-supplied dial opts are still applied last and win.
Behaviour / rollout note
Because round_robin is now opt-in and defaults to
pick_first, merging this alone does not change runtime behaviour — each consuming service (api → silo/transform/access/sequence) must setpolicy: round_robinin its config to get the fix. Tracked as a follow-up.Keepalive 5m floor
The silo/transform/access/sequence servers run gRPC's default
EnforcementPolicy(MinTime5m); a faster client ping would betoo_many_pings-GOAWAY'd. 5m is the safe floor for a client-only change. Server-side follow-ups (invopop/silo#231, invopop/transform#83, invopop/access#238, invopop/sequence#25) relax the policy; a companion PR (#8) then shortens the interval.Testing
go build ./...,go vet,gofmtclean. Confirmedround_robinresolves at runtime (balancer.Get("round_robin") != nil).