fix(grpc-js): fix jitter for client retries

This commit is contained in:
Nazar Ilkiv 2025-06-04 11:14:07 +03:00
parent 21d40b0247
commit 82b331d9e1
1 changed files with 33 additions and 8 deletions

View File

@ -136,7 +136,12 @@ interface UnderlyingCall {
* sent * sent
* NO_RETRY: Retries are disabled. Exists to track the transition to COMMITTED * NO_RETRY: Retries are disabled. Exists to track the transition to COMMITTED
*/ */
type RetryingCallState = 'RETRY' | 'HEDGING' | 'TRANSPARENT_ONLY' | 'COMMITTED' | 'NO_RETRY'; type RetryingCallState =
| 'RETRY'
| 'HEDGING'
| 'TRANSPARENT_ONLY'
| 'COMMITTED'
| 'NO_RETRY';
/** /**
* The different types of objects that can be stored in the write buffer, with * The different types of objects that can be stored in the write buffer, with
@ -217,7 +222,9 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
private readonly bufferTracker: MessageBufferTracker, private readonly bufferTracker: MessageBufferTracker,
private readonly retryThrottler?: RetryThrottler private readonly retryThrottler?: RetryThrottler
) { ) {
const maxAttemptsLimit = channel.getOptions()['grpc-node.retry_max_attempts_limit'] ?? DEFAULT_MAX_ATTEMPTS_LIMIT; const maxAttemptsLimit =
channel.getOptions()['grpc-node.retry_max_attempts_limit'] ??
DEFAULT_MAX_ATTEMPTS_LIMIT;
if (callConfig.methodConfig.retryPolicy) { if (callConfig.methodConfig.retryPolicy) {
this.state = 'RETRY'; this.state = 'RETRY';
const retryPolicy = callConfig.methodConfig.retryPolicy; const retryPolicy = callConfig.methodConfig.retryPolicy;
@ -230,7 +237,10 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
this.maxAttempts = Math.min(retryPolicy.maxAttempts, maxAttemptsLimit); this.maxAttempts = Math.min(retryPolicy.maxAttempts, maxAttemptsLimit);
} else if (callConfig.methodConfig.hedgingPolicy) { } else if (callConfig.methodConfig.hedgingPolicy) {
this.state = 'HEDGING'; this.state = 'HEDGING';
this.maxAttempts = Math.min(callConfig.methodConfig.hedgingPolicy.maxAttempts, maxAttemptsLimit); this.maxAttempts = Math.min(
callConfig.methodConfig.hedgingPolicy.maxAttempts,
maxAttemptsLimit
);
} else if (channel.getOptions()['grpc.enable_retries'] === 0) { } else if (channel.getOptions()['grpc.enable_retries'] === 0) {
this.state = 'NO_RETRY'; this.state = 'NO_RETRY';
this.maxAttempts = 1; this.maxAttempts = 1;
@ -247,10 +257,17 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
const deadlineInfo: string[] = []; const deadlineInfo: string[] = [];
const latestCall = this.underlyingCalls[this.underlyingCalls.length - 1]; const latestCall = this.underlyingCalls[this.underlyingCalls.length - 1];
if (this.underlyingCalls.length > 1) { if (this.underlyingCalls.length > 1) {
deadlineInfo.push(`previous attempts: ${this.underlyingCalls.length - 1}`); deadlineInfo.push(
`previous attempts: ${this.underlyingCalls.length - 1}`
);
} }
if (latestCall.startTime > this.startTime) { if (latestCall.startTime > this.startTime) {
deadlineInfo.push(`time to current attempt start: ${formatDateDifference(this.startTime, latestCall.startTime)}`); deadlineInfo.push(
`time to current attempt start: ${formatDateDifference(
this.startTime,
latestCall.startTime
)}`
);
} }
deadlineInfo.push(...latestCall.call.getDeadlineInfo()); deadlineInfo.push(...latestCall.call.getDeadlineInfo());
return deadlineInfo; return deadlineInfo;
@ -412,12 +429,18 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
); );
} }
private getNextRetryJitter() {
/* Jitter of +-20% is applied: https://github.com/grpc/proposal/blob/master/A6-client-retries.md#exponential-backoff */
return Math.random() * (1.2 - 0.8) + 0.8;
}
private getNextRetryBackoffMs() { private getNextRetryBackoffMs() {
const retryPolicy = this.callConfig?.methodConfig.retryPolicy; const retryPolicy = this.callConfig?.methodConfig.retryPolicy;
if (!retryPolicy) { if (!retryPolicy) {
return 0; return 0;
} }
const nextBackoffMs = Math.random() * this.nextRetryBackoffSec * 1000; const jitter = this.getNextRetryJitter();
const nextBackoffMs = jitter * this.nextRetryBackoffSec * 1000;
const maxBackoffSec = Number( const maxBackoffSec = Number(
retryPolicy.maxBackoff.substring(0, retryPolicy.maxBackoff.length - 1) retryPolicy.maxBackoff.substring(0, retryPolicy.maxBackoff.length - 1)
); );
@ -669,7 +692,7 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
state: 'ACTIVE', state: 'ACTIVE',
call: child, call: child,
nextMessageToSend: 0, nextMessageToSend: 0,
startTime: new Date() startTime: new Date(),
}); });
const previousAttempts = this.attempts - 1; const previousAttempts = this.attempts - 1;
const initialMetadata = this.initialMetadata!.clone(); const initialMetadata = this.initialMetadata!.clone();
@ -862,7 +885,9 @@ export class RetryingCall implements Call, DeadlineInfoProvider {
} }
getAuthContext(): AuthContext | null { getAuthContext(): AuthContext | null {
if (this.committedCallIndex !== null) { if (this.committedCallIndex !== null) {
return this.underlyingCalls[this.committedCallIndex].call.getAuthContext(); return this.underlyingCalls[
this.committedCallIndex
].call.getAuthContext();
} else { } else {
return null; return null;
} }