Skip to content

Commit 4885da5

Browse files
authored
feat: implement autoscaling and the leastConnections strategy (#451)
1 parent 61a8f2c commit 4885da5

36 files changed

+1426
-205
lines changed

benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/ConnectionPluginManagerBenchmarks.java

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,16 @@
2222
import static org.mockito.Mockito.when;
2323
import static org.mockito.MockitoAnnotations.openMocks;
2424

25+
import java.sql.Connection;
26+
import java.sql.ResultSet;
27+
import java.sql.SQLException;
28+
import java.sql.Statement;
29+
import java.util.ArrayList;
30+
import java.util.Collections;
31+
import java.util.EnumSet;
32+
import java.util.List;
33+
import java.util.Properties;
34+
import java.util.concurrent.TimeUnit;
2535
import org.mockito.Mock;
2636
import org.openjdk.jmh.annotations.Benchmark;
2737
import org.openjdk.jmh.annotations.BenchmarkMode;
@@ -50,22 +60,11 @@
5060
import software.amazon.jdbc.PluginManagerService;
5161
import software.amazon.jdbc.PluginService;
5262
import software.amazon.jdbc.PropertyDefinition;
63+
import software.amazon.jdbc.benchmarks.testplugin.BenchmarkPluginFactory;
5364
import software.amazon.jdbc.dialect.Dialect;
5465
import software.amazon.jdbc.profile.DriverConfigurationProfiles;
55-
import software.amazon.jdbc.benchmarks.testplugin.BenchmarkPluginFactory;
5666
import software.amazon.jdbc.wrapper.ConnectionWrapper;
5767

58-
import java.sql.Connection;
59-
import java.sql.ResultSet;
60-
import java.sql.SQLException;
61-
import java.sql.Statement;
62-
import java.util.ArrayList;
63-
import java.util.Collections;
64-
import java.util.EnumSet;
65-
import java.util.List;
66-
import java.util.Properties;
67-
import java.util.concurrent.TimeUnit;
68-
6968
@State(Scope.Benchmark)
7069
@Fork(3)
7170
@Warmup(iterations = 3)
@@ -108,8 +107,8 @@ public void setUpIteration() throws Exception {
108107

109108
when(mockConnectionProvider.connect(anyString(), any(Properties.class))).thenReturn(
110109
mockConnection);
111-
when(mockConnectionProvider.connect(anyString(), any(Dialect.class), any(HostSpec.class), any(Properties.class)))
112-
.thenReturn(mockConnection);
110+
when(mockConnectionProvider.connect(anyString(), any(Dialect.class), any(HostSpec.class),
111+
any(Properties.class))).thenReturn(mockConnection);
113112
when(mockConnection.createStatement()).thenReturn(mockStatement);
114113
when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet);
115114
when(mockResultSet.next()).thenReturn(true, true, false);

benchmarks/src/jmh/java/software/amazon/jdbc/benchmarks/PluginBenchmarks.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ public void setUpIteration() throws Exception {
107107
.thenReturn(mockStatement);
108108
when(mockConnectionProvider.connect(anyString(), any(Properties.class))).thenReturn(
109109
mockConnection);
110-
when(mockConnectionProvider.connect(anyString(), any(Dialect.class), any(HostSpec.class), any(Properties.class)))
111-
.thenReturn(mockConnection);
110+
when(mockConnectionProvider.connect(anyString(), any(Dialect.class), any(HostSpec.class),
111+
any(Properties.class))).thenReturn(mockConnection);
112112
when(mockConnection.createStatement()).thenReturn(mockStatement);
113113
when(mockStatement.executeQuery(anyString())).thenReturn(mockResultSet);
114114
when(mockResultSet.next()).thenReturn(true, true, false);

docs/using-the-jdbc-driver/using-plugins/UsingTheReadWriteSplittingPlugin.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,28 @@ The wrapper driver currently uses [Hikari](https://github.com/brettwooldridge/Hi
3939
- username
4040
- password
4141

42-
You can optionally pass in a `HikariPoolMapping` function as a second parameter to the `HikariPooledConnectionProvider`. This allows you to decide when new connection pools should be created by defining what is included in the pool map key. A new pool will be created each time a new connection is requested with a unique key. By default, a new pool will be created for each unique instance-user combination. If you would like to define a different key system, you should pass in a `HikariPoolMapping` function defining this logic. Note that the user will always be automatically included in the key for security reasons. Please see [ReadWriteSplittingPostgresExample.java](../../../examples/AWSDriverExample/src/main/java/software/amazon/ReadWriteSplittingPostgresExample.java) for an example of how to configure the pool map key.
42+
You can optionally pass in a `HikariPoolMapping` function as a second parameter to the `HikariPooledConnectionProvider`. This allows you to decide when new connection pools should be created by defining what is included in the pool map key. A new pool will be created each time a new connection is requested with a unique key. By default, a new pool will be created for each unique instance-user combination. If you would like to define a different key system, you should pass in a `HikariPoolMapping` function defining this logic. A simple example is show below. Please see [ReadWriteSplittingPostgresExample.java](../../../examples/AWSDriverExample/src/main/java/software/amazon/ReadWriteSplittingPostgresExample.java) for the full example.
43+
44+
> :warning: If you do not include the username in your HikariPoolMapping function, connection pools may be shared between different users.
45+
46+
```java
47+
props.setProperty("somePropertyValue", "1"); // used in getPoolKey
48+
final HikariPooledConnectionProvider connProvider =
49+
new HikariPooledConnectionProvider(
50+
ReadWriteSplittingPostgresExample::getHikariConfig,
51+
ReadWriteSplittingPostgresExample::getPoolKey
52+
);
53+
ConnectionProviderManager.setConnectionProvider(connProvider);
54+
55+
private static String getPoolKey(HostSpec hostSpec, Properties props) {
56+
// Include the URL, user, and somePropertyValue in the connection pool key so that a new
57+
// connection pool will be opened for each different instance-user-somePropertyValue
58+
// combination.
59+
final String user = props.getProperty(PropertyDefinition.USER.name);
60+
final String somePropertyValue = props.getProperty("somePropertyValue");
61+
return hostSpec.getUrl() + user + somePropertyValue;
62+
}
63+
```
4364

4465
2. Call `ConnectionProviderManager.setConnectionProvider`, passing in the `HikariPooledConnectionProvider` you created in step 1.
4566

examples/AWSDriverExample/src/main/java/software/amazon/ReadWriteSplittingPostgresExample.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,10 +164,11 @@ private static HikariConfig getHikariConfig(HostSpec hostSpec, Properties props)
164164
// This method is an optional parameter to `ConnectionProviderManager.setConnectionProvider`.
165165
// It can be omitted if you do not require it.
166166
private static String getPoolKey(HostSpec hostSpec, Properties props) {
167-
// Include the URL and somePropertyValue in the connection pool key so that a new connection
168-
// pool will be opened for each different instance-user-somePropertyValue combination.
169-
// (Note that the user will automatically be added to the key).
167+
// Include the URL, user, and somePropertyValue in the connection pool key so that a new
168+
// connection pool will be opened for each different instance-user-somePropertyValue
169+
// combination.
170+
final String user = props.getProperty(PropertyDefinition.USER.name);
170171
final String somePropertyValue = props.getProperty("somePropertyValue");
171-
return hostSpec.getUrl() + somePropertyValue;
172+
return hostSpec.getUrl() + user + somePropertyValue;
172173
}
173174
}

wrapper/build.gradle.kts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,3 +408,27 @@ tasks.register<Test>("test-aurora-mysql-performance") {
408408
systemProperty("test-no-mariadb-engine", "true")
409409
}
410410
}
411+
412+
// Autoscaling
413+
414+
tasks.register<Test>("test-autoscaling-only") {
415+
group = "verification"
416+
filter.includeTestsMatching("integration.refactored.host.TestRunner.runTests")
417+
doFirst {
418+
systemProperty("test-autoscaling-only", "true")
419+
systemProperty("test-no-docker", "true")
420+
systemProperty("test-no-performance", "true")
421+
systemProperty("test-no-graalvm", "true")
422+
}
423+
}
424+
425+
tasks.register<Test>("debug-autoscaling-only") {
426+
group = "verification"
427+
filter.includeTestsMatching("integration.refactored.host.TestRunner.debugTests")
428+
doFirst {
429+
systemProperty("test-autoscaling-only", "true")
430+
systemProperty("test-no-docker", "true")
431+
systemProperty("test-no-performance", "true")
432+
systemProperty("test-no-graalvm", "true")
433+
}
434+
}

wrapper/src/main/java/software/amazon/jdbc/ConnectionProvider.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ boolean acceptsUrl(
6363
* @throws SQLException if an error occurred while returning the hosts
6464
* @throws UnsupportedOperationException if the strategy is unsupported by the provider
6565
*/
66-
HostSpec getHostSpecByStrategy(@NonNull List<HostSpec> hosts, @NonNull HostRole role,
67-
@NonNull String strategy) throws SQLException, UnsupportedOperationException;
66+
HostSpec getHostSpecByStrategy(
67+
@NonNull List<HostSpec> hosts, @NonNull HostRole role, @NonNull String strategy)
68+
throws SQLException, UnsupportedOperationException;
6869

6970
/**
7071
* Called once per connection that needs to be created.

0 commit comments

Comments
 (0)