Implementing Retry Logic

1. Configuring Retry Policy

Example: Built-in retry via service config

{
  "methodConfig": [{
    "name": [{ "service": "user.v1.UserService" }],
    "retryPolicy": {
      "maxAttempts": 4,
      "initialBackoff": "0.1s",
      "maxBackoff": "1s",
      "backoffMultiplier": 2.0,
      "retryableStatusCodes": ["UNAVAILABLE", "DEADLINE_EXCEEDED"]
    }
  }]
}
FieldDetail
maxAttemptsTotal attempts incl. first (max 5 by default)
backoffMultiplierExponential factor
retryableStatusCodesCodes that trigger retry

2. Setting Max Attempts

RecommendationDetail
3–4Typical
Avoid >5Amplifies load on degraded backend
Bounded by deadlineTotal time = parent ctx deadline

3. Configuring Initial Backoff

SettingDetail
0.1sTypical for in-DC
JittergRPC automatically applies random factor

4. Configuring Max Backoff

SettingDetail
1–10sCap to prevent unbounded growth
Per-attemptCombined with multiplier and jitter

5. Setting Retryable Status Codes

CodeRetryable?
UNAVAILABLEYes — typical
DEADLINE_EXCEEDEDYes if idempotent
RESOURCE_EXHAUSTEDSometimes; honor RetryInfo
INTERNAL / UNKNOWNAvoid unless explicitly safe
INVALID_ARGUMENT, NOT_FOUNDNever

6. Using Hedging Policy

Example: Hedging

"hedgingPolicy": {
  "maxAttempts": 3,
  "hedgingDelay": "0.05s",
  "nonFatalStatusCodes": ["UNAVAILABLE"]
}
BehaviorDetail
Parallel sendsAfter delay, send extra attempts in parallel
First winsCancel others on success
Requires idempotencyServer may receive multiple copies

7. Implementing Custom Retry Logic

Example: Custom retry interceptor

func Retry(max int) grpc.UnaryClientInterceptor {
    return func(ctx context.Context, m string, req, reply any,
        cc *grpc.ClientConn, inv grpc.UnaryInvoker, opts ...grpc.CallOption) error {
        var err error
        backoff := 100 * time.Millisecond
        for i := 0; i < max; i++ {
            err = inv(ctx, m, req, reply, cc, opts...)
            if err == nil { return nil }
            if status.Code(err) != codes.Unavailable { return err }
            select {
            case <-time.After(backoff + time.Duration(rand.Int63n(int64(backoff)))):
            case <-ctx.Done(): return ctx.Err()
            }
            backoff *= 2
        }
        return err
    }
}

8. Setting Per-Attempt Timeout

NeedApproach
Bound each tryWrap call with child ctx (timeout / maxAttempts)
Total budgetParent ctx deadline applies to whole policy

9. Using Exponential Backoff

ScheduleDetail
Initial 100 ms×2 each attempt → 100, 200, 400, 800 ms
Cap at maxBackoffPlateau prevents runaway
Add jitterPrevents thundering herd

10. Handling Non-Idempotent Methods

StrategyDetail
No retryDefault safest
Idempotency keyClient-generated UUID in request; server dedupes
Annotateoption idempotency_level = IDEMPOTENT;