Skip to content

Commit 438a360

Browse files
committed
Initial commit
Initial Commit
1 parent 9a7dac1 commit 438a360

File tree

9 files changed

+531
-8
lines changed

9 files changed

+531
-8
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
.DS_Store

README.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,29 @@
1-
## My Project
1+
#Amazon Keyspaces Java Driver Helpers
2+
This repository contains driver policies, examples, and best practices when using the DataStax Java Driver with Amazon Keyspaces (for Apache Cassandra).
23

3-
TODO: Fill this README out!
4+
## Retry Policies
5+
The DataStax java driver will attempt to retry idempotent request transparently to the application. If you are seeing NoHostAvailableException when using Amazon Keyspaces, replacing the default retry policy with the ones provided in this repository will be beneficial.
46

5-
Be sure to:
7+
Implementing a driver retry policy is not a replacement for an application level retry. Users of Apache Cassandra or Amazon Keyspaces should implement an application level retry mechanism for request that satisfy the applications business requirements. Additionally, adding complex logic, sleeps or blocking calls in a Driver retry policy should be used with caution.
68

7-
* Change the title in this README
8-
* Edit your repository description on GitHub
9+
### AmazonKeyspacesRetryPolicy
10+
The Amazon Keyspaces Retry Policy is an alternative to the DefaultRetryPolicy for the Cassandra Driver. The main difference from the DefaultRetryPolicy, is the AmazonKeyspacesRetryPolicy will retry request a configurable number of times. By default, we take a conservative approach of 3 retry attempts. This driver retry policy will not throw a NoHostAvailableException. Instead, this retry policy will pass back the original exception sent back from the service.
911

10-
## Security
12+
The following code shows how to include the AmazonKeyspacesRetryPolicy to existing configuration
13+
14+
```
15+
#Set idempotence for all operations you application can change on request
16+
basic.request.default-idempotence = true
17+
advanced.retry-policy {
18+
class = com.aws.ssa.keyspaces.retry.AmazonKeyspacesRetryPolicy
19+
max-attempts = 3
20+
}
21+
```
22+
23+
# Security
1124

1225
See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
1326

14-
## License
27+
# License
1528

1629
This library is licensed under the MIT-0 License. See the LICENSE file.
17-

pom.xml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>org.example</groupId>
8+
<artifactId>amazon-keyspaces-helpers</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
11+
<properties>
12+
<maven.compiler.source>1.8</maven.compiler.source>
13+
<maven.compiler.target>1.8</maven.compiler.target>
14+
15+
<aws.java.sdk.version>2.13.7</aws.java.sdk.version>
16+
17+
</properties>
18+
19+
20+
<dependencies>
21+
22+
<dependency>
23+
<groupId>com.datastax.oss</groupId>
24+
<artifactId>java-driver-core</artifactId>
25+
<version>4.10.0</version>
26+
<scope>provided</scope>
27+
</dependency>
28+
<dependency>
29+
<groupId>junit</groupId>
30+
<artifactId>junit</artifactId>
31+
<version>4.13.2</version>
32+
<scope>test</scope>
33+
</dependency>
34+
<dependency>
35+
<groupId>software.aws.mcs</groupId>
36+
<artifactId>aws-sigv4-auth-cassandra-java-driver-plugin</artifactId>
37+
<version>4.0.4</version>
38+
<scope>test</scope>
39+
</dependency>
40+
<dependency>
41+
<groupId>ch.qos.logback</groupId>
42+
<artifactId>logback-classic</artifactId>
43+
<version>1.2.3</version>
44+
<scope>test</scope>
45+
</dependency>
46+
<dependency>
47+
<groupId>io.dropwizard.metrics</groupId>
48+
<artifactId>metrics-core</artifactId>
49+
<version>4.1.0</version>
50+
<scope>test</scope>
51+
</dependency>
52+
</dependencies>
53+
54+
<build>
55+
56+
<plugins>
57+
<plugin>
58+
<artifactId>maven-assembly-plugin</artifactId>
59+
<executions>
60+
<execution>
61+
<phase>package</phase>
62+
<goals>
63+
<goal>single</goal>
64+
</goals>
65+
</execution>
66+
</executions>
67+
<configuration>
68+
<descriptorRefs>
69+
<descriptorRef>jar-with-dependencies</descriptorRef>
70+
</descriptorRefs>
71+
</configuration>
72+
</plugin>
73+
</plugins>
74+
75+
</build>
76+
</project>
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
package com.aws.ssa.keyspaces.retry;
2+
3+
import com.datastax.oss.driver.api.core.ConsistencyLevel;
4+
import com.datastax.oss.driver.api.core.config.DriverExecutionProfile;
5+
import com.datastax.oss.driver.api.core.context.DriverContext;
6+
import com.datastax.oss.driver.api.core.retry.RetryDecision;
7+
import com.datastax.oss.driver.api.core.retry.RetryPolicy;
8+
import com.datastax.oss.driver.api.core.servererrors.*;
9+
import com.datastax.oss.driver.api.core.session.Request;
10+
import com.datastax.oss.driver.shaded.guava.common.annotations.VisibleForTesting;
11+
import edu.umd.cs.findbugs.annotations.NonNull;
12+
import net.jcip.annotations.ThreadSafe;
13+
import org.slf4j.Logger;
14+
import org.slf4j.LoggerFactory;
15+
16+
17+
/**
18+
* This is a conservative retry policy adapted for the Amazon Keyspaces Service.
19+
* It allows for a configurable number of attempts, but by default the number of attempts is {@value KeyspacesRetryOption#DEFAULT_KEYSPACES_RETRY_MAX_ATTEMPTS}
20+
* <p>
21+
* This policy will either reattempt request on the same host or rethrow the exception to the calling thread. The main difference between
22+
* this policy from the original {@link com.datastax.oss.driver.internal.core.retry.DefaultRetryPolicy} is that the {@link AmazonKeyspacesRetryPolicy} will call {@link RetryDecision#RETRY_SAME} instead of {@link RetryDecision#RETRY_NEXT}
23+
* <p>
24+
* In Amazon Keyspaces, it's likely that {@link WriteTimeoutException} or {@link ReadTimeoutException} is the result of exceeding current table
25+
* capacity. Learn more about Amazon Keyspaces capacity here: @see <a href="https://docs.aws.amazon.com/keyspaces/latest/devguide/ReadWriteCapacityMode.html">Amazon Keyspaces CapacityModes</a>.
26+
* In most cases you should allow for small number of retries, and handle the exception in your application threads.
27+
*
28+
* <p>To activate this policy, modify the {@code advanced.retry-policy} section in the driver
29+
* configuration, for example:
30+
*
31+
* <pre>
32+
* datastax-java-driver {
33+
* advanced.retry-policy {
34+
* class = com.aws.ssa.keyspaces.retry.AmazonKeyspacesRetryPolicy
35+
* max-attempts = 2
36+
* }
37+
* }
38+
* </pre>
39+
*/
40+
41+
@ThreadSafe
42+
public class AmazonKeyspacesRetryPolicy implements RetryPolicy {
43+
44+
45+
private static final Logger LOG = LoggerFactory.getLogger(AmazonKeyspacesRetryPolicy.class);
46+
@VisibleForTesting
47+
public static final String RETRYING_ON_READ_TIMEOUT = "[{}] Retrying on read timeout on same host (consistency: {}, required responses: {}, received responses: {}, data retrieved: {}, retries: {})";
48+
@VisibleForTesting
49+
public static final String RETRYING_ON_WRITE_TIMEOUT = "[{}] Retrying on write timeout on same host (consistency: {}, write type: {}, required acknowledgments: {}, received acknowledgments: {}, retries: {})";
50+
@VisibleForTesting
51+
public static final String RETRYING_ON_UNAVAILABLE = "[{}] Retrying on unavailable exception on next host (consistency: {}, required replica: {}, alive replica: {}, retries: {})";
52+
@VisibleForTesting
53+
public static final String RETRYING_ON_ABORTED = "[{}] Retrying on aborted request on next host (retries: {})";
54+
@VisibleForTesting
55+
public static final String RETRYING_ON_ERROR = "[{}] Retrying on node error on next host (retries: {})";
56+
57+
private final String logPrefix;
58+
59+
private final Integer maxRetryCount;
60+
61+
62+
public AmazonKeyspacesRetryPolicy(DriverContext context) {
63+
this(context, context.getConfig().getDefaultProfile().getName());
64+
}
65+
66+
public AmazonKeyspacesRetryPolicy(DriverContext context, Integer maxRetryCount) {
67+
68+
String profileName = context.getConfig().getDefaultProfile().getName();
69+
70+
this.maxRetryCount = maxRetryCount;
71+
72+
this.logPrefix = (context != null ? context.getSessionName() : null) + "|" + profileName;
73+
}
74+
75+
public AmazonKeyspacesRetryPolicy(DriverContext context, String profileName) {
76+
DriverExecutionProfile retryExecutionProfile = context.getConfig().getProfile(profileName);
77+
78+
maxRetryCount = retryExecutionProfile.getInt(KeyspacesRetryOption.KEYSPACES_RETRY_MAX_ATTEMPTS, KeyspacesRetryOption.DEFAULT_KEYSPACES_RETRY_MAX_ATTEMPTS);
79+
80+
this.logPrefix = (context != null ? context.getSessionName() : null) + "|" + profileName;
81+
}
82+
83+
84+
protected RetryDecision determineRetryDecision(int retryCount) {
85+
if (retryCount > maxRetryCount) {
86+
return RetryDecision.RETHROW;
87+
} else {
88+
return RetryDecision.RETRY_SAME;
89+
}
90+
}
91+
92+
/**
93+
* {@inheritDoc}
94+
*
95+
* <p>This implementation triggers a maximum of configured retry (to the same connection)
96+
*
97+
* <p>Otherwise, the exception is rethrown.
98+
*/
99+
@Override
100+
public RetryDecision onReadTimeout(
101+
@NonNull Request request,
102+
@NonNull ConsistencyLevel cl,
103+
int blockFor,
104+
int received,
105+
boolean dataPresent,
106+
int retryCount) {
107+
108+
109+
RetryDecision decision = determineRetryDecision(retryCount);
110+
111+
if (LOG.isTraceEnabled() && (decision == RetryDecision.RETRY_SAME || decision == RetryDecision.RETHROW)) {
112+
LOG.trace(RETRYING_ON_READ_TIMEOUT, logPrefix, cl, blockFor, received, false, retryCount);
113+
}
114+
115+
return decision;
116+
117+
}
118+
119+
120+
/**
121+
* {@inheritDoc}
122+
*
123+
* <p>This implementation triggers a maximum of configured retry (to the same connection)
124+
*
125+
* <p>Otherwise, the exception is rethrown.
126+
*/
127+
@Override
128+
public RetryDecision onWriteTimeout(
129+
@NonNull Request request,
130+
@NonNull ConsistencyLevel cl,
131+
@NonNull WriteType writeType,
132+
int blockFor,
133+
int received,
134+
int retryCount) {
135+
136+
137+
RetryDecision decision = determineRetryDecision(retryCount);
138+
139+
if (LOG.isTraceEnabled() && (decision == RetryDecision.RETRY_SAME || decision == RetryDecision.RETHROW)) {
140+
LOG.trace(RETRYING_ON_WRITE_TIMEOUT, logPrefix, cl, blockFor, received, false, retryCount);
141+
}
142+
143+
return decision;
144+
}
145+
146+
/**
147+
* {@inheritDoc}
148+
*
149+
* <p>This implementation triggers a maximum of configured retry (to the same connection)
150+
*
151+
* <p>Otherwise, the exception is rethrown.
152+
*/
153+
@Override
154+
public RetryDecision onUnavailable(
155+
@NonNull Request request,
156+
@NonNull ConsistencyLevel cl,
157+
int required,
158+
int alive,
159+
int retryCount) {
160+
161+
RetryDecision decision = determineRetryDecision(retryCount);
162+
163+
if (LOG.isTraceEnabled() && (decision == RetryDecision.RETRY_SAME || decision == RetryDecision.RETHROW)) {
164+
LOG.trace(RETRYING_ON_UNAVAILABLE, logPrefix, cl, required, alive, retryCount);
165+
}
166+
167+
return decision;
168+
}
169+
170+
/**
171+
* {@inheritDoc}
172+
*
173+
* <p>This implementation triggers a maximum of configured retry (to the same connection)
174+
*/
175+
@Override
176+
public RetryDecision onRequestAborted(
177+
@NonNull Request request, @NonNull Throwable error, int retryCount) {
178+
RetryDecision decision = determineRetryDecision(retryCount);
179+
180+
if (LOG.isTraceEnabled() && (decision == RetryDecision.RETRY_SAME || decision == RetryDecision.RETHROW)) {
181+
LOG.trace(RETRYING_ON_ABORTED, logPrefix, retryCount, error);
182+
}
183+
184+
return decision;
185+
}
186+
187+
/**
188+
* {@inheritDoc}
189+
*
190+
* <p>This implementation triggers a maximum of configured retry (to the same connection)
191+
*/
192+
@Override
193+
public RetryDecision onErrorResponse(
194+
@NonNull Request request, @NonNull CoordinatorException error, int retryCount) {
195+
196+
197+
RetryDecision decision = determineRetryDecision(retryCount);
198+
199+
if (LOG.isTraceEnabled() && (decision == RetryDecision.RETRY_SAME || decision == RetryDecision.RETHROW)) {
200+
LOG.trace(RETRYING_ON_ERROR, logPrefix, retryCount, error);
201+
}
202+
203+
return decision;
204+
}
205+
206+
@Override
207+
public void close() {
208+
// nothing to do
209+
}
210+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.aws.ssa.keyspaces.retry;
2+
3+
import com.datastax.oss.driver.api.core.config.DriverOption;
4+
5+
public enum KeyspacesRetryOption implements DriverOption {
6+
7+
KEYSPACES_RETRY_MAX_ATTEMPTS("advanced.retry-policy.max-attempts");
8+
9+
public static final Integer DEFAULT_KEYSPACES_RETRY_MAX_ATTEMPTS = 3;
10+
11+
private final String path;
12+
13+
KeyspacesRetryOption(String path) {
14+
this.path = path;
15+
}
16+
17+
@Override
18+
public String getPath() {
19+
return path;
20+
}
21+
22+
}

0 commit comments

Comments
 (0)