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

fix: connection tracker update after failover to new connection #356

Open
wants to merge 5 commits into
base: main
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
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export class ConnectionStringHostListProvider implements StaticHostListProvider
return Promise.resolve(this.hostList);
}

getHostRole(client: AwsClient): Promise<HostRole> {
getHostRole(client: ClientWrapper): Promise<HostRole> {
throw new AwsWrapperError("ConnectionStringHostListProvider does not support getHostRole.");
}

Expand Down
2 changes: 1 addition & 1 deletion common/lib/host_list_provider/host_list_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export interface HostListProvider {

forceRefresh(client: ClientWrapper): Promise<HostInfo[]>;

getHostRole(client: AwsClient, dialect: DatabaseDialect): Promise<HostRole>;
getHostRole(client: ClientWrapper, dialect: DatabaseDialect): Promise<HostRole>;

identifyConnection(targetClient: ClientWrapper, dialect: DatabaseDialect): Promise<HostInfo | null>;

Expand Down
6 changes: 3 additions & 3 deletions common/lib/host_list_provider/rds_host_list_provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,13 +137,13 @@ export class RdsHostListProvider implements DynamicHostListProvider {
throw new AwsWrapperError("Could not retrieve targetClient.");
}

async getHostRole(client: AwsClient, dialect: DatabaseDialect): Promise<HostRole> {
async getHostRole(client: ClientWrapper, dialect: DatabaseDialect): Promise<HostRole> {
if (!this.isTopologyAwareDatabaseDialect(dialect)) {
throw new TypeError(Messages.get("RdsHostListProvider.incorrectDialect"));
}

if (client.targetClient) {
return dialect.getHostRole(client.targetClient);
if (client) {
return await dialect.getHostRole(client);
} else {
throw new AwsWrapperError(Messages.get("AwsClient targetClient not defined."));
}
Expand Down
1 change: 1 addition & 0 deletions common/lib/pg_client_wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import { ClientWrapper } from "./client_wrapper";
import { HostInfo } from "./host_info";
import { uniqueId } from "../logutils";
import { logTopology } from "./utils/utils";

/*
This an internal wrapper class for a target community driver client created by the NodePostgresPgDriverDialect.
Expand Down
2 changes: 1 addition & 1 deletion common/lib/plugin_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import { ClientWrapper } from "./client_wrapper";
import { logger } from "../logutils";
import { Messages } from "./utils/messages";
import { DatabaseDialectCodes } from "./database_dialect/database_dialect_codes";
import { getWriter } from "./utils/utils";
import { getWriter, logTopology } from "./utils/utils";
import { TelemetryFactory } from "./utils/telemetry/telemetry_factory";
import { DriverDialect } from "./driver_dialect/driver_dialect";
import { ConfigurationProfile } from "./profile/configuration_profile";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { FailoverError } from "../../utils/errors";
import { HostChangeOptions } from "../../host_change_options";
import { HostRole } from "../../host_role";
import { OpenedConnectionTracker } from "./opened_connection_tracker";
import { logger } from "../../../logutils";
import { logTopology } from "../../utils/utils";

export class AuroraConnectionTrackerPlugin extends AbstractConnectionPlugin implements CanReleaseResources {
private static readonly subscribedMethods = new Set<string>(["notifyHostListChanged"].concat(SubscribedMethodHelper.NETWORK_BOUND_METHODS));
Expand Down Expand Up @@ -100,7 +102,6 @@ export class AuroraConnectionTrackerPlugin extends AbstractConnectionPlugin impl

private async checkWriterChanged(): Promise<void> {
const hostInfoAfterFailover = this.getWriter(this.pluginService.getHosts());

if (this.currentWriter === null) {
this.currentWriter = hostInfoAfterFailover;
this.needUpdateCurrentWriter = false;
Expand All @@ -127,10 +128,12 @@ export class AuroraConnectionTrackerPlugin extends AbstractConnectionPlugin impl
async notifyHostListChanged(changes: Map<string, Set<HostChangeOptions>>): Promise<void> {
for (const [key, _] of changes.entries()) {
const hostChanges = changes.get(key);

if (hostChanges) {
if (hostChanges.has(HostChangeOptions.PROMOTED_TO_READER)) {
await this.tracker.invalidateAllConnectionsMultipleHosts(key);
}

if (hostChanges.has(HostChangeOptions.PROMOTED_TO_WRITER)) {
this.needUpdateCurrentWriter = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { logger } from "../../../logutils";
import { MapUtils } from "../../utils/map_utils";
import { Messages } from "../../utils/messages";
import { PluginService } from "../../plugin_service";
import { logTopology } from "../../utils/utils";

export class OpenedConnectionTracker {
static readonly openedConnections: Map<string, Array<WeakRef<ClientWrapper>>> = new Map<string, Array<WeakRef<ClientWrapper>>>();
Expand Down
1 change: 1 addition & 0 deletions common/lib/plugins/failover/failover_plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class FailoverPlugin extends AbstractConnectionPlugin {
private static readonly TELEMETRY_WRITER_FAILOVER = "failover to writer instance";
private static readonly TELEMETRY_READER_FAILOVER = "failover to replica";
private static readonly METHOD_END = "end";

private static readonly subscribedMethods: Set<string> = new Set([
"initHostProvider",
"connect",
Expand Down
15 changes: 15 additions & 0 deletions common/lib/plugins/failover/reader_failover_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,21 @@ class ConnectionAttemptTask {
);
try {
this.targetClient = await this.pluginService.forceConnect(this.newHost, copy);

// ensure that new connection is a connection to a reader node
try {
// eslint-disable-next-line no-constant-condition
while (1) {
await this.pluginService.forceRefreshHostList();
if ((await this.pluginService.getHostRole(this.targetClient)) === HostRole.READER) {
break;
}
await sleep(1000);
}
} catch (error: any) {
logger.debug(Messages.get("ClusterAwareReaderFailoverHandler.errorGettingHostRole", error.message));
}

this.pluginService.setAvailability(this.newHost.allAliases, HostAvailability.AVAILABLE);
logger.info(Messages.get("ClusterAwareReaderFailoverHandler.successfulReaderConnection", this.newHost.host));
if (this.taskHandler.getSelectedConnectionAttemptTask(this.failoverTaskId) === -1) {
Expand Down
4 changes: 3 additions & 1 deletion common/lib/utils/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"ClusterAwareWriterFailoverHandler.standaloneHost": "[TaskB] Host %s is not yet connected to a cluster. The cluster is still being reconfigured.",
"ClusterAwareWriterFailoverHandler.taskBAttemptConnectionToNewWriter": "[TaskB] Trying to connect to a new writer: '%s'",
"ClusterAwareWriterFailoverHandler.alreadyWriter": "Current reader connection is actually a new writer connection.",
"ClusterAwareReaderFailoverHandler.errorGettingHostRole": "An error occurred while trying to determine the role of the reader candidate: ",
"Failover.TransactionResolutionUnknownError": "Transaction resolution unknown. Please re-configure session state if required and try restarting the transaction.",
"Failover.connectionChangedError": "The active SQL connection has changed due to a connection failure. Please re-configure session state if required.",
"Failover.parameterValue": "%s = %s",
Expand Down Expand Up @@ -193,5 +194,6 @@
"ConfigurationProfileBuilder.notFound": "Configuration profile '%s' not found.",
"ConfigurationProfileBuilder.profileNameRequired": "Profile name is required.",
"ConfigurationProfileBuilder.canNotUpdateKnownPreset": "Can't add or update a built-in preset configuration profile '%s'.",
"AwsClient.configurationProfileNotFound": "Configuration profile '%s' not found."
"AwsClient.configurationProfileNotFound": "Configuration profile '%s' not found.",
"AwsClient targetClient not defined.": "AwsClient targetClient not defined."
}
2 changes: 2 additions & 0 deletions pg/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import { RdsMultiAZPgDatabaseDialect } from "./dialect/rds_multi_az_pg_database_
import { HostInfo } from "../../common/lib/host_info";
import { TelemetryTraceLevel } from "../../common/lib/utils/telemetry/telemetry_trace_level";
import { NodePostgresDriverDialect } from "./dialect/node_postgres_driver_dialect";
import { logTopology } from "../../common/lib/utils/utils";
import { logger } from "../../common/logutils";

export class AwsPGClient extends AwsClient {
private static readonly knownDialectsByCode: Map<string, DatabaseDialect> = new Map([
Expand Down
2 changes: 1 addition & 1 deletion pg/lib/dialect/aurora_pg_database_dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export class AuroraPgDatabaseDialect extends PgDatabaseDialect implements Topolo

async getHostRole(targetClient: ClientWrapper): Promise<HostRole> {
const res = await targetClient.query(AuroraPgDatabaseDialect.IS_READER_QUERY);
return Promise.resolve(res.rows[0]["is_reader"] === "true" ? HostRole.READER : HostRole.WRITER);
return Promise.resolve(res.rows[0]["is_reader"] === true ? HostRole.READER : HostRole.WRITER);
}

async isDialect(targetClient: ClientWrapper): Promise<boolean> {
Expand Down
4 changes: 2 additions & 2 deletions tests/plugin_manager_benchmarks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ suite(
}),

add("initHostProviderWith10Plugins", async () => {
const pluginManagerWithPlugins = await initPluginManagerWithPlugins(10, instance(mockPluginService), propsWithPlugins);;
const pluginManagerWithPlugins = await initPluginManagerWithPlugins(10, instance(mockPluginService), propsWithPlugins);
return async () =>
await pluginManagerWithPlugins.initHostProvider(
new HostInfoBuilder({ hostAvailabilityStrategy: new SimpleHostAvailabilityStrategy() }).withHost("host").build(),
Expand Down Expand Up @@ -325,7 +325,7 @@ suite(
}),

add("notifyConnectionChangedWith10Plugins", async () => {
const pluginManagerWithPlugins = await initPluginManagerWithPlugins(10, instance(mockPluginService), propsWithPlugins);;
const pluginManagerWithPlugins = await initPluginManagerWithPlugins(10, instance(mockPluginService), propsWithPlugins);
return async () =>
await pluginManagerWithPlugins.notifyConnectionChanged(new Set<HostChangeOptions>([HostChangeOptions.INITIAL_CONNECTION]), null);
}),
Expand Down
Loading