Skip to content

Commit 96cfd47

Browse files
authored
feat(client): Enable routing cookie and attempt headers for enhanced retries (#12964)
Implements client-side logic to support the Cloud Bigtable routing cookie protocol (see [go/cbt-support-routing-cookie](http://go/cbt-support-routing-cookie)). This feature enhances server-initiated retries and improves resilience. Specific modifications: * **Feature Flag:** The client now signals support for this feature by setting `RoutingCookie: true` in the `bigtable-features` header. * **Cookie Propagation:** Any headers prefixed with `x-goog-cbt-cookie-` received in response metadata are collected and sent back on subsequent requests for the same operation. * **Attempt Header:** * The `x-goog-cbt-attempt` header is added to each outgoing RPC attempt. * This integer value starts at 0 and is incremented before each call. * Crucially, the counter is reset to 0 if the client receives *any* metadata (headers or trailers) from the server, indicating the request reached the backend. This helps differentiate between network issues and server-side retries. * **Internal Tracking:** The `opTracer` in `metrics.go` now stores the current cookies and the `routingAttempt` number. These changes enable more sophisticated load balancing and retry strategies, by giving the service and routing layers visibility into the client's retry state and a mechanism to influence client behavior. Fixes: #12658
1 parent 1c63383 commit 96cfd47

File tree

3 files changed

+41
-11
lines changed

3 files changed

+41
-11
lines changed

bigtable/bigtable.go

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ const (
5555
queryExpiredViolationType = "PREPARED_QUERY_EXPIRED"
5656
preparedQueryExpireEarlyDuration = time.Second
5757
methodNameReadRows = "ReadRows"
58+
59+
// For routing cookie
60+
cookiePrefix = "x-goog-cbt-cookie-"
5861
)
5962

6063
var errNegativeRowLimit = errors.New("bigtable: row limit cannot be negative")
@@ -398,6 +401,7 @@ type Table struct {
398401
// and enabled on the client
399402
func (c *Client) newFeatureFlags() metadata.MD {
400403
ff := btpb.FeatureFlags{
404+
RoutingCookie: true,
401405
ReverseScans: true,
402406
LastScannedRowResponses: true,
403407
ClientSideMetricsEnabled: c.metricsTracerFactory.enabled,
@@ -2281,15 +2285,37 @@ func gaxInvokeWithRecorder(ctx context.Context, mt *builtinMetricsTracer, method
22812285
mt.setMethod(method)
22822286

22832287
callWrapper := func(ctx context.Context, callSettings gax.CallSettings) error {
2288+
op := &mt.currOp
2289+
// Inject cookie and attempt information
2290+
md := metadata.New(nil)
2291+
for k, v := range op.cookies {
2292+
md.Append(k, v)
2293+
}
2294+
2295+
existingMD, _ := metadata.FromOutgoingContext(ctx)
2296+
finalMD := metadata.Join(existingMD, md)
2297+
newCtx := metadata.NewOutgoingContext(ctx, finalMD)
2298+
22842299
mt.recordAttemptStart()
22852300

22862301
// f makes calls to CBT service
2287-
err := f(ctx, &attemptHeaderMD, &attempTrailerMD, callSettings)
2302+
err := f(newCtx, &attemptHeaderMD, &attempTrailerMD, callSettings)
22882303

22892304
// Record attempt specific metrics
22902305
mt.recordAttemptCompletion(attemptHeaderMD, attempTrailerMD, err)
2306+
2307+
extractCookies(attemptHeaderMD, op)
2308+
extractCookies(attempTrailerMD, op)
22912309
return err
22922310
}
22932311

22942312
return gax.Invoke(ctx, callWrapper, opts...)
22952313
}
2314+
2315+
func extractCookies(md metadata.MD, op *opTracer) {
2316+
for k, v := range md {
2317+
if strings.HasPrefix(k, cookiePrefix) {
2318+
op.cookies[k] = v[len(v)-1]
2319+
}
2320+
}
2321+
}
Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1 @@
1-
TestMutateRows_Retry_WithRoutingCookie\|
2-
TestReadRow_Retry_WithRoutingCookie\|
3-
TestReadRows_Retry_WithRoutingCookie\|
4-
TestReadRows_Retry_WithRoutingCookie_MultipleErrorResponses\|
5-
TestReadRows_Retry_WithRetryInfo_MultipleErrorResponse\|
6-
TestSampleRowKeys_Retry_WithRoutingCookie
1+
TestReadRows_Retry_WithRetryInfo_MultipleErrorResponse

bigtable/metrics.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,9 @@ type opTracer struct {
457457
currAttempt attemptTracer
458458

459459
appBlockingLatency float64
460+
461+
// For routing cookie and gRPC attempt number
462+
cookies map[string]string
460463
}
461464

462465
func (o *opTracer) setStartTime(t time.Time) {
@@ -524,14 +527,20 @@ func (a *attemptTracer) setServerLatencyErr(err error) {
524527
}
525528

526529
func (tf *builtinMetricsTracerFactory) createBuiltinMetricsTracer(ctx context.Context, tableName string, isStreaming bool) builtinMetricsTracer {
527-
if !tf.enabled {
528-
return builtinMetricsTracer{builtInEnabled: false}
529-
}
530530
// Operation has started but not the attempt.
531531
// So, create only operation tracer and not attempt tracer
532-
currOpTracer := opTracer{}
532+
currOpTracer := opTracer{
533+
cookies: make(map[string]string),
534+
}
533535
currOpTracer.setStartTime(time.Now())
534536

537+
if !tf.enabled {
538+
return builtinMetricsTracer{
539+
builtInEnabled: false,
540+
currOp: currOpTracer,
541+
}
542+
}
543+
535544
return builtinMetricsTracer{
536545
ctx: ctx,
537546
builtInEnabled: tf.enabled,

0 commit comments

Comments
 (0)