Skip to content

Commit 767fda0

Browse files
committed
Added retry config to common module
1 parent b3125bd commit 767fda0

File tree

8 files changed

+486
-36
lines changed

8 files changed

+486
-36
lines changed

common/src/main/java/tech/ydb/common/retry/ErrorPolicy.java

Lines changed: 0 additions & 30 deletions
This file was deleted.

common/src/main/java/tech/ydb/common/retry/ExponentialBackoffRetry.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
*
88
* @author Aleksandr Gorshenin
99
*/
10-
public abstract class ExponentialBackoffRetry implements RetryPolicy {
10+
public class ExponentialBackoffRetry implements RetryPolicy {
1111
private final long backoffMs;
1212
private final int backoffCeiling;
1313

14-
protected ExponentialBackoffRetry(long backoffMs, int backoffCeiling) {
14+
public ExponentialBackoffRetry(long backoffMs, int backoffCeiling) {
1515
this.backoffMs = backoffMs;
1616
this.backoffCeiling = backoffCeiling;
1717
}
@@ -22,6 +22,11 @@ protected long backoffTimeMillis(int retryNumber) {
2222
return delay + ThreadLocalRandom.current().nextLong(delay);
2323
}
2424

25+
@Override
26+
public long nextRetryMs(int retryCount, long elapsedTimeMs) {
27+
return backoffTimeMillis(retryCount);
28+
}
29+
2530
/**
2631
* Return current base of backoff delays
2732
* @return backoff base duration in milliseconds
@@ -37,4 +42,5 @@ public long getBackoffMillis() {
3742
public int getBackoffCeiling() {
3843
return backoffCeiling;
3944
}
45+
4046
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package tech.ydb.common.retry;
2+
3+
import tech.ydb.core.StatusCode;
4+
import tech.ydb.core.UnexpectedResultException;
5+
6+
/**
7+
* Recipes should use the retry configuration to decide how to retry
8+
* errors like unsuccessful {@link tech.ydb.core.StatusCode}.
9+
*
10+
* @author Aleksandr Gorshenin
11+
*/
12+
@FunctionalInterface
13+
public interface RetryConfig {
14+
15+
/**
16+
* Returns retry policy for the given status code and {@code null} if that status code is not retryable
17+
*
18+
* @param code status code to check
19+
* @return policy of retries or {@code null} if the status code is not retryable
20+
*/
21+
RetryPolicy isStatusRetryable(StatusCode code);
22+
23+
/**
24+
* Returns retry policy for the given exception and {@code null} if that exception is not retryable
25+
*
26+
* @param th exception to check
27+
* @return policy of retries or {@code null} if the exception is not retryable
28+
*/
29+
default RetryPolicy isThrowableRetryable(Throwable th) {
30+
for (Throwable ex = th; ex != null; ex = ex.getCause()) {
31+
if (ex instanceof UnexpectedResultException) {
32+
return isStatusRetryable(((UnexpectedResultException) ex).getStatus().getCode());
33+
}
34+
}
35+
return null;
36+
}
37+
38+
/**
39+
* Retries a non idempotent operation forever with default exponential delay
40+
* @return retry configuration object
41+
*/
42+
static RetryConfig retryForever() {
43+
return newConfig().retryForever();
44+
}
45+
46+
/**
47+
* Retries a non idempotent operation with default exponential until the specified elapsed milliseconds expire
48+
* @param maxElapsedMs maximum timeout for retries
49+
* @return retry configuration object
50+
*/
51+
static RetryConfig retryUntilElapsed(long maxElapsedMs) {
52+
return newConfig().retryUntilElapsed(maxElapsedMs);
53+
}
54+
55+
/**
56+
* Retries an idempotent operation forever with default exponential delay
57+
* @return retry configuration object
58+
*/
59+
static RetryConfig idempotentRetryForever() {
60+
return newConfig().retryIdempotent(true).retryForever();
61+
}
62+
63+
/**
64+
* Retries an idempotent operation with default exponential until the specified elapsed milliseconds expire
65+
* @param maxElapsedMs maximum timeout for retries
66+
* @return retry configuration object
67+
*/
68+
static RetryConfig idempotentRetryUntilElapsed(long maxElapsedMs) {
69+
return newConfig().retryIdempotent(true).retryUntilElapsed(maxElapsedMs);
70+
}
71+
72+
/**
73+
* Disabled retries configuration. Any error is considered as non retryable
74+
* @return retry configuration object
75+
*/
76+
static RetryConfig noRetries() {
77+
return (StatusCode code) -> null;
78+
}
79+
80+
/**
81+
* Create a new custom configuration of retries
82+
* @return retry configuration builder
83+
*/
84+
static Builder newConfig() {
85+
return new YdbRetryBuilder();
86+
}
87+
88+
interface Builder {
89+
Builder retryIdempotent(boolean retry);
90+
Builder retryNotFound(boolean retry);
91+
Builder withSlowBackoff(long backoff, int ceiling);
92+
Builder withFastBackoff(long backoff, int ceiling);
93+
94+
RetryConfig retryForever();
95+
RetryConfig retryNTimes(int maxRetries);
96+
RetryConfig retryUntilElapsed(long maxElapsedMs);
97+
}
98+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package tech.ydb.common.retry;
2+
3+
/**
4+
*
5+
* @author Aleksandr Gorshenin
6+
*/
7+
class YdbRetryBuilder implements RetryConfig.Builder {
8+
private boolean idempotent = false;
9+
private boolean retryNotFound = false;
10+
11+
private long fastBackoff = 5;
12+
private int fastCeiling = 10;
13+
14+
private long slowBackoff = 500;
15+
private int slowCeiling = 6;
16+
17+
@Override
18+
public YdbRetryBuilder retryIdempotent(boolean retry) {
19+
this.idempotent = retry;
20+
return this;
21+
}
22+
23+
@Override
24+
public YdbRetryBuilder retryNotFound(boolean retry) {
25+
this.retryNotFound = retry;
26+
return this;
27+
}
28+
29+
@Override
30+
public YdbRetryBuilder withSlowBackoff(long backoff, int ceiling) {
31+
this.slowBackoff = backoff;
32+
this.slowCeiling = ceiling;
33+
return this;
34+
}
35+
36+
@Override
37+
public YdbRetryBuilder withFastBackoff(long backoff, int ceiling) {
38+
this.fastBackoff = backoff;
39+
this.fastCeiling = ceiling;
40+
return this;
41+
}
42+
43+
@Override
44+
public RetryConfig retryForever() {
45+
return new YdbRetryConfig(idempotent, retryNotFound,
46+
(int retryCount, long elapsedTimeMs) -> 0,
47+
new ExponentialBackoffRetry(fastBackoff, fastCeiling),
48+
new ExponentialBackoffRetry(slowBackoff, slowCeiling)
49+
);
50+
}
51+
52+
@Override
53+
public RetryConfig retryNTimes(int maxRetries) {
54+
return new YdbRetryConfig(idempotent, retryNotFound,
55+
(int retryCount, long elapsedTimeMs) -> retryCount >= maxRetries ? -1 : 0,
56+
new RetryNTimes(maxRetries, fastBackoff, fastCeiling),
57+
new RetryNTimes(maxRetries, slowBackoff, slowCeiling)
58+
);
59+
}
60+
61+
@Override
62+
public RetryConfig retryUntilElapsed(long maxElapsedMs) {
63+
return new YdbRetryConfig(idempotent, retryNotFound,
64+
(int retryCount, long elapsedTimeMs) -> elapsedTimeMs > maxElapsedMs ? -1 : 0,
65+
new RetryUntilElapsed(maxElapsedMs, fastBackoff, fastCeiling),
66+
new RetryUntilElapsed(maxElapsedMs, slowBackoff, slowCeiling)
67+
);
68+
}
69+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package tech.ydb.common.retry;
2+
3+
import tech.ydb.core.StatusCode;
4+
5+
/**
6+
*
7+
* @author Aleksandr Gorshenin
8+
*/
9+
class YdbRetryConfig implements RetryConfig {
10+
private final boolean idempotent;
11+
private final boolean retryNotFound;
12+
private final RetryPolicy immediatelly;
13+
private final RetryPolicy fast;
14+
private final RetryPolicy slow;
15+
16+
YdbRetryConfig(boolean idempotent, boolean notFound, RetryPolicy immediatelly, RetryPolicy fast, RetryPolicy slow) {
17+
this.idempotent = idempotent;
18+
this.retryNotFound = notFound;
19+
this.immediatelly = immediatelly;
20+
this.fast = fast;
21+
this.slow = slow;
22+
}
23+
24+
@Override
25+
public RetryPolicy isStatusRetryable(StatusCode code) {
26+
if (code == null) {
27+
return null;
28+
}
29+
30+
switch (code) {
31+
// Instant retry
32+
case BAD_SESSION:
33+
case SESSION_BUSY:
34+
return immediatelly;
35+
36+
// Fast backoff
37+
case ABORTED:
38+
case UNDETERMINED:
39+
return fast;
40+
41+
// Slow backoff
42+
case OVERLOADED:
43+
case CLIENT_RESOURCE_EXHAUSTED:
44+
return slow;
45+
46+
// Conditionally retry
47+
case CLIENT_CANCELLED:
48+
case CLIENT_INTERNAL_ERROR:
49+
case TRANSPORT_UNAVAILABLE:
50+
case UNAVAILABLE:
51+
return idempotent ? fast : null;
52+
53+
// Not found retry
54+
case NOT_FOUND:
55+
return retryNotFound ? fast : null;
56+
57+
// All other codes are not retryable
58+
default:
59+
return null;
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)