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

feat: Session state transfer redesign #821

Merged
merged 1 commit into from
Jan 10, 2024
Merged
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions docs/using-the-jdbc-driver/SessionState.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Session States

## What is a session state?

Every connection is associated with a connection session on the server and a group of related session settings like the autoCommit flag or the transaction isolation level. The following session settings are tracked by the AWS JDBC Driver and together they form a session state:
- autoCommit (`setAutoCommit`, `getAutoCommit`)
- readOnly (`isReadOnly`, `setReadOnly`)
- transaction isolation level (`setTransactionIsolation`, `getTransactionIsolation`)
- holdability (`setHoldability`, `getHoldability`)
- network timeout (`setNetworkTimeout`, `getNetworkTimeout`)
- catalog (`setCatalog`, `getCatalog`)
- schema (`setSchema`, `getSchema`)
- types mapping (`setTypeMap`, `getTypeMap`)

Since the AWS JDBC Driver can transparently switch physical connection to a server (for instance, during a cluster failover), it's important to re-apply a current session state to a new connection during such switch.

## Tracking Session States Changes
<div style="center"><img src="../images/session_state_switch_connection.jpg" alt="diagram for the session state transfer"/></div>

The diagram above shows the process of switching one database connection `A` to a new connection `B`. After connection `A` is established, it's returned to the user application. A user application may use this connection to query data from the database as well as to change some session settings. For example, if the user application calls `setReadOnly` on a connection, the AWS JDBC Driver intercepts this call and stores a new session setting for the `readOnly` setting. At the same time, the driver verifies if the original session setting is known or not. If the original setting is not known, the driver will make an additional `getReadOnly` call and store the result as a pristine value in order to save the original session setting. Later, the driver may need the pristine value to restore the connection session state to its original state.

## Restore to the Original Session State

Before closing an existing connection, the AWS JDBC Driver may try to reset all changes to the session state made by the user application. Some application frameworks and connection pools, like the Spring Framework or HikariCP, intercept calls to `close()` and may perform additional connection configuration. Since the AWS JDBC Driver might change the internal physical connection to a server, a new physical connection's settings may become unexpected to the user application and may cause errors. It is also important to mention that calling `close()` on a connection while using connection pooling doesn't close the connection or stop communicating to a server. Instead the connection is returned to a pool of available connections. Cleaning up a session state before returning a connection to a pool is necessary to avoid side effects and errors when a connection is retrieved from a pool to be reused.

Before closing a connection, the AWS JDBC Driver sets its session state settings with the pristine values that have been previously stored in the driver. If a pristine value isn't available, it means that there have been no changes to that particular setting made by the user application, and that it's safe to assume that this setting is in its original/unchanged state.

Session state reset could be disabled by using `resetSessionStateOnClose` configuration parameter.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Session state reset could be disabled by using `resetSessionStateOnClose` configuration parameter.
Session state reset can be disabled by using the `resetSessionStateOnClose` configuration parameter.


## Transfer Session State to a new Connection

When the driver needs to switch to a new connection, it opens a new connection and transfers a session state to it. All current session state values are applied to the new connection. Pristine values for a new connection are also fetched and stored if needed. When a new connection is configured, it replaces the current internal connection.

Session transfer cab be disabled by using the `transferSessionStateOnSwitch` configuration parameter.

## Session State Custom handlers

It's possible to extend or replace existing logic of resetting session state and transferring session state with custom handlers. Use the following methods on `software.amazon.jdbc.Driver` class to set and reset custom handlers:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
It's possible to extend or replace existing logic of resetting session state and transferring session state with custom handlers. Use the following methods on `software.amazon.jdbc.Driver` class to set and reset custom handlers:
It's possible to extend or replace the existing logic of resetting and/or transferring the session state with custom handlers. Use the following methods in the `software.amazon.jdbc.Driver` class to set and reset custom handlers:

- `setResetSessionStateOnCloseFunc`
- `resetResetSessionStateOnCloseFunc`
- `setTransferSessionStateOnSwitchFunc`
- `resetTransferSessionStateOnSwitchFunc`
3 changes: 3 additions & 0 deletions docs/using-the-jdbc-driver/UsingTheJdbcDriver.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ These parameters are applicable to any instance of the AWS JDBC Driver.
| `socketTimeout` | `Integer` | No | Socket timeout in milliseconds. | `null` |
| `tcpKeepAlive` | `Boolean` | No | Enable or disable TCP keep-alive probe. | `false` |
| `targetDriverAutoRegister` | `Boolean` | No | Allows the AWS JDBC Driver to register a target driver based on `wrapperTargetDriverDialect` configuration parameter or, if it's missed, on a connection url protocol. | `true` |
| `transferSessionStateOnSwitch` | `Boolean` | No | Enables transferring the session state to a new connection. | `true` |
| `resetSessionStateOnClose` | `Boolean` | No | Enables resetting the session state before closing connection. | `true` |
| `rollbackOnSwitch` | `Boolean` | No | Enables rolling back a current transaction, if any in effect, before switching to a new connection. | `true` |

## Plugins
The AWS JDBC Driver uses plugins to execute JDBC methods. You can think of a plugin as an extensible code module that adds extra logic around any JDBC method calls. The AWS JDBC Driver has a number of [built-in plugins](#list-of-available-plugins) available for use.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ In addition to the parameters that you can configure for the underlying driver,
| `failoverReaderConnectTimeoutMs` | Integer | No | Maximum allowed time in milliseconds to attempt to connect to a reader instance during a reader failover process. | `30000` |
| `failoverTimeoutMs` | Integer | No | Maximum allowed time in milliseconds to attempt reconnecting to a new writer or reader instance after a cluster failover is initiated. | `300000` |
| `failoverWriterReconnectIntervalMs` | Integer | No | Interval of time in milliseconds to wait between attempts to reconnect to a failed writer during a writer failover process. | `2000` |
| `keepSessionStateOnFailover` | Boolean | No | This parameter will allow connections to retain the session state after failover. When keepSessionStateOnFailover is set to false, connections will need to be reconfigured as seen in the example [here](./../../../examples/AWSDriverExample/src/main/java/software/amazon/PgFailoverSample.java). When this parameter is true, the autocommit and readOnly values will be kept. This parameter is only necessary when the session state must be retained and the connection cannot be manually reconfigured by the user. <br><br> **Please note:** this parameter will not be able to fully restore the connection session state, as it will only save the autocommit and readOnly values. | `false` |
| ~~`keepSessionStateOnFailover`~~ | Boolean | No | This parameter is no longer available. If specified, it will be ignored by the driver. See [Session State](../SessionState.md) for more details. | `false` |
| ~~`enableFailoverStrictReader`~~ | Boolean | No | This parameter is no longer available and, if specified, it will be ignored by the driver. See `failoverMode` (`reader-or-writer` or `strict-reader`) for more details. | |

## Host Pattern
Expand Down
30 changes: 30 additions & 0 deletions wrapper/src/main/java/software/amazon/jdbc/Driver.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,12 @@
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import software.amazon.jdbc.profile.ConfigurationProfile;
import software.amazon.jdbc.profile.DriverConfigurationProfiles;
import software.amazon.jdbc.states.ResetSessionStateOnCloseCallable;
import software.amazon.jdbc.states.TransferSessionStateOnSwitchCallable;
import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect;
import software.amazon.jdbc.targetdriverdialect.TargetDriverDialectManager;
import software.amazon.jdbc.util.ConnectionUrlParser;
Expand All @@ -52,6 +55,9 @@ public class Driver implements java.sql.Driver {
private static final Logger LOGGER = Logger.getLogger("software.amazon.jdbc.Driver");
private static @Nullable Driver registeredDriver;

private static ResetSessionStateOnCloseCallable resetSessionStateOnCloseCallable = null;
private static TransferSessionStateOnSwitchCallable transferSessionStateOnSwitchCallable = null;

sergiyvamz marked this conversation as resolved.
Show resolved Hide resolved
static {
try {
register();
Expand Down Expand Up @@ -232,4 +238,28 @@ public boolean jdbcCompliant() {
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return PARENT_LOGGER;
}

public static void setResetSessionStateOnCloseFunc(final @NonNull ResetSessionStateOnCloseCallable func) {
resetSessionStateOnCloseCallable = func;
}

public static void resetResetSessionStateOnCloseFunc() {
resetSessionStateOnCloseCallable = null;
}

public static ResetSessionStateOnCloseCallable getResetSessionStateOnCloseFunc() {
return resetSessionStateOnCloseCallable;
}

public static void setTransferSessionStateOnSwitchFunc(final @NonNull TransferSessionStateOnSwitchCallable func) {
transferSessionStateOnSwitchCallable = func;
}

public static void resetTransferSessionStateOnSwitchFunc() {
transferSessionStateOnSwitchCallable = null;
}

public static TransferSessionStateOnSwitchCallable getTransferSessionStateOnSwitchFunc() {
return transferSessionStateOnSwitchCallable;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,5 @@

public interface PluginManagerService {

void setReadOnly(boolean readOnly);

void setInTransaction(boolean inTransaction);
}
32 changes: 15 additions & 17 deletions wrapper/src/main/java/software/amazon/jdbc/PluginService.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
import software.amazon.jdbc.dialect.Dialect;
import software.amazon.jdbc.exceptions.ExceptionHandler;
import software.amazon.jdbc.hostavailability.HostAvailability;
import software.amazon.jdbc.states.SessionDirtyFlag;
import software.amazon.jdbc.states.SessionStateService;
import software.amazon.jdbc.targetdriverdialect.TargetDriverDialect;
import software.amazon.jdbc.util.telemetry.TelemetryFactory;

Expand All @@ -43,24 +43,25 @@ public interface PluginService extends ExceptionHandler {
void setCurrentConnection(final @NonNull Connection connection, final @NonNull HostSpec hostSpec)
throws SQLException;

/**
* Set a new internal connection. While setting a new connection, a notification may be sent to all plugins.
* See {@link ConnectionPlugin#notifyConnectionChanged(EnumSet)} for more details. A plugin mentioned
* in parameter skipNotificationForThisPlugin won't be receiving such notification.
*
* @param connection the new internal connection.
* @param hostSpec the host details for a new internal connection.
* @param skipNotificationForThisPlugin A reference to a plugin that doesn't need to receive notification
* about connection change. Usually, a plugin that initiates connection change
* doesn't need to receive such notification and uses a pointer to
* itself as a call parameter.
* @return a set of notification options about this connection switch.
*/
EnumSet<NodeChangeOptions> setCurrentConnection(
final @NonNull Connection connection,
final @NonNull HostSpec hostSpec,
@Nullable ConnectionPlugin skipNotificationForThisPlugin)
throws SQLException;

EnumSet<SessionDirtyFlag> getCurrentConnectionState();

void setCurrentConnectionState(SessionDirtyFlag flag);

void resetCurrentConnectionState(SessionDirtyFlag flag);

void resetCurrentConnectionStates();

boolean getAutoCommit();

void setAutoCommit(final boolean autoCommit);

List<HostSpec> getHosts();

HostSpec getInitialConnectionHostSpec();
Expand Down Expand Up @@ -111,10 +112,6 @@ HostSpec getHostSpecByStrategy(HostRole role, String strategy)

void setAvailability(Set<String> hostAliases, HostAvailability availability);

boolean isExplicitReadOnly();

boolean isReadOnly();

boolean isInTransaction();

HostListProvider getHostListProvider();
Expand Down Expand Up @@ -186,4 +183,5 @@ HostSpec getHostSpecByStrategy(HostRole role, String strategy)

String getTargetName();

@NonNull SessionStateService getSessionStateService();
}
Loading
Loading