Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add retry policy config #360

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 0 additions & 30 deletions common/src/main/java/tech/ydb/common/retry/ErrorPolicy.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
*
* @author Aleksandr Gorshenin
*/
public abstract class ExponentialBackoffRetry implements RetryPolicy {
public class ExponentialBackoffRetry implements RetryPolicy {
private final long backoffMs;
private final int backoffCeiling;

protected ExponentialBackoffRetry(long backoffMs, int backoffCeiling) {
public ExponentialBackoffRetry(long backoffMs, int backoffCeiling) {
this.backoffMs = backoffMs;
this.backoffCeiling = backoffCeiling;
}
Expand All @@ -22,6 +22,11 @@ protected long backoffTimeMillis(int retryNumber) {
return delay + ThreadLocalRandom.current().nextLong(delay);
}

@Override
public long nextRetryMs(int retryCount, long elapsedTimeMs) {
return backoffTimeMillis(retryCount);
}

/**
* Return current base of backoff delays
* @return backoff base duration in milliseconds
Expand All @@ -37,4 +42,5 @@ public long getBackoffMillis() {
public int getBackoffCeiling() {
return backoffCeiling;
}

}
104 changes: 104 additions & 0 deletions common/src/main/java/tech/ydb/common/retry/RetryConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package tech.ydb.common.retry;


import tech.ydb.core.Status;
import tech.ydb.core.UnexpectedResultException;

/**
* Recipes should use the retry configuration to decide how to retry
* errors like unsuccessful {@link tech.ydb.core.Status}.
*
* @author Aleksandr Gorshenin
*/
@FunctionalInterface
public interface RetryConfig {

/**
* Returns retry policy for the given {@link Status} and {@code null} if that status is not retryable
*
* @param status status to check
* @return policy of retries or {@code null} if the status is not retryable
*/
RetryPolicy getStatusRetryPolicy(Status status);

/**
* Returns retry policy for the given exception and {@code null} if that exception is not retryable
*
* @param th exception to check
* @return policy of retries or {@code null} if the exception is not retryable
*/
default RetryPolicy getThrowableRetryPolicy(Throwable th) {
for (Throwable ex = th; ex != null; ex = ex.getCause()) {
if (ex instanceof UnexpectedResultException) {
return getStatusRetryPolicy(((UnexpectedResultException) ex).getStatus());
}
}
return null;
}

/**
* Infinity retries with default exponential delay.<br>This policy <b>does not</b> retries <i>conditionally</i>
* retryable errors so it can be used for both as idempotent and non idempotent operations
*
* @return retry configuration object
*/
static RetryConfig retryForever() {
return newConfig().retryForever();
}

/**
* Retries until the specified elapsed milliseconds expire.<br>This policy <b>does not</b> retries
* <i>conditionally</i> retryable errors so it can be used for both as idempotent and non idempotent operations
* @param maxElapsedMs maximum timeout for retries
* @return retry configuration object
*/
static RetryConfig retryUntilElapsed(long maxElapsedMs) {
return newConfig().retryUntilElapsed(maxElapsedMs);
}

/**
* Infinity retries with default exponential delay.<br>This policy <b>does</b> retries <i>conditionally</i>
* retryable errors so it can be used <b>ONLY</b> for idempotent operations
* @return retry configuration object
*/
static RetryConfig idempotentRetryForever() {
return newConfig().retryConditionallyRetryableErrors(true).retryForever();
}

/**
* Retries until the specified elapsed milliseconds expire.<br>This policy <b>does</b> retries
* <i>conditionally</i> retryable errors so it can be used <b>ONLY</b> for idempotent operations
* @param maxElapsedMs maximum timeout for retries
* @return retry configuration object
*/
static RetryConfig idempotentRetryUntilElapsed(long maxElapsedMs) {
return newConfig().retryConditionallyRetryableErrors(true).retryUntilElapsed(maxElapsedMs);
}

/**
* Disabled retries configuration. Any error is considered as non retryable
* @return retry configuration object
*/
static RetryConfig noRetries() {
return (Status status) -> null;
}

/**
* Create a new custom configuration of retries
* @return retry configuration builder
*/
static Builder newConfig() {
return new YdbRetryBuilder();
}

interface Builder {
Builder retryConditionallyRetryableErrors(boolean retry);
Builder retryNotFound(boolean retry);
Builder withSlowBackoff(long backoff, int ceiling);
Builder withFastBackoff(long backoff, int ceiling);

RetryConfig retryForever();
RetryConfig retryNTimes(int maxRetries);
RetryConfig retryUntilElapsed(long maxElapsedMs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*
* @author Aleksandr Gorshenin
*/
@FunctionalInterface
public interface RetryPolicy {
/**
* Called when an operation is failed for some reason to determine if it should be retried.
Expand Down
69 changes: 69 additions & 0 deletions common/src/main/java/tech/ydb/common/retry/YdbRetryBuilder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package tech.ydb.common.retry;

/**
*
* @author Aleksandr Gorshenin
*/
class YdbRetryBuilder implements RetryConfig.Builder {
private boolean idempotent = false;
private boolean retryNotFound = false;

private long fastBackoff = 5;
private int fastCeiling = 10;

private long slowBackoff = 500;
private int slowCeiling = 6;

@Override
public YdbRetryBuilder retryConditionallyRetryableErrors(boolean retry) {
this.idempotent = retry;
return this;
}

@Override
public YdbRetryBuilder retryNotFound(boolean retry) {
this.retryNotFound = retry;
return this;
}

@Override
public YdbRetryBuilder withSlowBackoff(long backoff, int ceiling) {
this.slowBackoff = backoff;
this.slowCeiling = ceiling;
return this;
}

@Override
public YdbRetryBuilder withFastBackoff(long backoff, int ceiling) {
this.fastBackoff = backoff;
this.fastCeiling = ceiling;
return this;
}

@Override
public RetryConfig retryForever() {
return new YdbRetryConfig(idempotent, retryNotFound,
(int retryCount, long elapsedTimeMs) -> 0,
new ExponentialBackoffRetry(fastBackoff, fastCeiling),
new ExponentialBackoffRetry(slowBackoff, slowCeiling)
);
}

@Override
public RetryConfig retryNTimes(int maxRetries) {
return new YdbRetryConfig(idempotent, retryNotFound,
(int retryCount, long elapsedTimeMs) -> retryCount >= maxRetries ? -1 : 0,
new RetryNTimes(maxRetries, fastBackoff, fastCeiling),
new RetryNTimes(maxRetries, slowBackoff, slowCeiling)
);
}

@Override
public RetryConfig retryUntilElapsed(long maxElapsedMs) {
return new YdbRetryConfig(idempotent, retryNotFound,
(int retryCount, long elapsedTimeMs) -> elapsedTimeMs > maxElapsedMs ? -1 : 0,
new RetryUntilElapsed(maxElapsedMs, fastBackoff, fastCeiling),
new RetryUntilElapsed(maxElapsedMs, slowBackoff, slowCeiling)
);
}
}
62 changes: 62 additions & 0 deletions common/src/main/java/tech/ydb/common/retry/YdbRetryConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package tech.ydb.common.retry;

import tech.ydb.core.Status;

/**
*
* @author Aleksandr Gorshenin
*/
class YdbRetryConfig implements RetryConfig {
private final boolean retryConditionally;
private final boolean retryNotFound;
private final RetryPolicy immediatelly;
private final RetryPolicy fast;
private final RetryPolicy slow;

YdbRetryConfig(boolean conditionally, boolean notFound, RetryPolicy instant, RetryPolicy fast, RetryPolicy slow) {
this.retryConditionally = conditionally;
this.retryNotFound = notFound;
this.immediatelly = instant;
this.fast = fast;
this.slow = slow;
}

@Override
public RetryPolicy getStatusRetryPolicy(Status status) {
if (status == null) {
return null;
}

switch (status.getCode()) {
// Instant retry
case BAD_SESSION:
case SESSION_BUSY:
return immediatelly;

// Fast backoff
case ABORTED:
case UNDETERMINED:
return fast;

// Slow backoff
case OVERLOADED:
case CLIENT_RESOURCE_EXHAUSTED:
return slow;

// Conditionally retryable statuses
case CLIENT_CANCELLED:
case CLIENT_INTERNAL_ERROR:
case TRANSPORT_UNAVAILABLE:
case UNAVAILABLE:
return retryConditionally ? fast : null;

// Not found has special flag for retries
case NOT_FOUND:
return retryNotFound ? fast : null;

// All other codes are not retryable
default:
return null;
}
}
}
Loading
Loading