Skip to content

Commit

Permalink
test: read write performance tests (#258)
Browse files Browse the repository at this point in the history
  • Loading branch information
karenc-bq authored Nov 6, 2024
1 parent fa2a89f commit 2593331
Show file tree
Hide file tree
Showing 9 changed files with 512 additions and 55 deletions.
1 change: 0 additions & 1 deletion .github/workflows/integration_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ env.TEMP_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ env.TEMP_AWS_SECRET_ACCESS_KEY }}
AWS_SESSION_TOKEN: ${{ env.TEMP_AWS_SESSION_TOKEN }}
NUM_INSTANCES: 5
AURORA_MYSQL_DB_ENGINE_VERSION: "default"
AURORA_PG_DB_ENGINE_VERSION: "default"

Expand Down
1 change: 0 additions & 1 deletion .github/workflows/integration_tests_latest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ jobs:
AWS_ACCESS_KEY_ID: ${{ env.TEMP_AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ env.TEMP_AWS_SECRET_ACCESS_KEY }}
AWS_SESSION_TOKEN: ${{ env.TEMP_AWS_SESSION_TOKEN }}
NUM_INSTANCES: 5
AURORA_MYSQL_DB_ENGINE_VERSION: "latest"
AURORA_PG_DB_ENGINE_VERSION: "latest"

Expand Down
26 changes: 26 additions & 0 deletions docs/development-guide/ReadWriteSplittingPerformanceResults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Read/Write Splitting Performance Results

When calling `AwsClient#setReadOnly`, the AWS Advanced NodeJS Wrapper will execute a query that sets the current session transaction state.
For a PostgreSQL database, the wrapper will execute either a `SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY` or a `SET SESSION CHARACTERISTICS AS TRANSACTION READ WRITE` query, and for a MySQL database,
the wrapper will execute either a `SET SESSION TRANSACTION READ ONLY` or a `SET SESSION TRANSACTION READ WRITE` query.

If the Read/Write Splitting Plugin is enabled, in addition to executing the queries mentioned above,
the wrapper will also switch the current underlying connection from a writer instance to a reader instance, and vice versa, depending on the requested readonly state.
To reduce the overhead of creating new connections each time `setReadOnly` is called, the wrapper keeps the previous connection opened for reusability.

The Read/Write Splitting Performance Test measures the overhead of this additional connection creation and caching workflow.
In this test, the wrapper first establishes the initial connection to a writer instance A using the cluster writer endpoint, then:

1. switches to a new reader B by calling `AwsClient#setReadOnly(true)`, represented by `Switch to reader` in the table below.
2. switches back to the initial writer A by calling `AwsClient#setReadOnly(false)`, represented by `Switch to reader (using cached connection)` in the table below.
3. switches back to reader B by calling `AwsClient#setReadOnly(true)`, represented by `Switch to reader (using cached connection)` in the table below.

The numbers in the table below are in nanoseconds, and do not account for network latency from operations that require network connections, such as executing queries or establishing new connections.

| ConnectionSwitch | MinOverheadTime (ns) | MaxOverheadTime (ns) | AvgOverheadTime (ns) |
| ------------------------------------------ | -------------------- | -------------------- | -------------------- |
| Switch to reader | 69597293 | 83459751 | 77004721 |
| Switch to writer (using cached connection) | 141619751 | 167699666 | 151332779 |
| Switch to reader (using cached connection) | 138624375 | 159564291 | 150139892 |

When using the Read/Write Splitting plugin, users should expect at least a 15 milliseconds delay when not using internal connection pools.
12 changes: 6 additions & 6 deletions pg/lib/abstract_pg_error_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,20 @@ export abstract class AbstractPgErrorHandler implements ErrorHandler {
// @ts-ignore
return this.getAccessErrorCodes().includes(e["code"]);
}
this.getAccessErrorMessages().forEach((message) => {
if (e.message.includes(message)) {
for (const accessErrorMessage of this.getAccessErrorMessages()) {
if (e.message.includes(accessErrorMessage)) {
return true;
}
});
}
return false;
}

isNetworkError(e: Error): boolean {
this.getNetworkErrors().forEach((message) => {
if (e.message.includes(message)) {
for (const networkError of this.getNetworkErrors()) {
if (e.message.includes(networkError)) {
return true;
}
});
}
return false;
}
}
70 changes: 24 additions & 46 deletions tests/integration/container/tests/performance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import { DriverHelper } from "./utils/driver_helper";
import { ProxyHelper } from "./utils/proxy_helper";
import { AuroraTestUtility } from "./utils/aurora_test_utility";
import { TestEnvironmentFeatures } from "./utils/test_environment_features";
import * as XLSX from "xlsx";
import { anything } from "ts-mockito";
import { WrapperProperties } from "../../../../common/lib/wrapper_property";
import { features } from "./config";
import { MonitorServiceImpl } from "../../../../common/lib/plugins/efm/monitor_service";
import { PerfStat } from "./utils/perf_stat";
import { PerfTestUtility } from "./utils/perf_util";

const itIf =
features.includes(TestEnvironmentFeatures.FAILOVER_SUPPORTED) &&
Expand Down Expand Up @@ -68,7 +69,7 @@ let initClientFunc: (props: any) => any;
let auroraTestUtility: AuroraTestUtility;
let enhancedFailureMonitoringPerfDataList: PerfStatMonitoring[] = [];

async function initDefaultConfig(host: string, port: number): Promise<any> {
function initDefaultConfig(host: string, port: number): any {
let config: any = {
user: env.databaseInfo.username,
host: host,
Expand Down Expand Up @@ -109,7 +110,11 @@ async function testFailureDetectionTimeEfmEnabled() {
);
}
} finally {
doWritePerfDataToFile(`EnhancedMonitoringOnly_Db_${env.engine}_Instances_${env.instances.length}_Plugins_efm.xlsx`, "EfmOnly");
PerfTestUtility.writePerfDataToFile(
enhancedFailureMonitoringPerfDataList,
`EnhancedMonitoringOnly_Db_${env.engine}_Instances_${env.instances.length}_Plugins_efm.xlsx`,
"EfmOnly"
);
}
}

Expand All @@ -124,20 +129,12 @@ async function testFailureDetectionTimeFailoverAndEfmEnabled() {
);
}
} finally {
doWritePerfDataToFile(`FailoverWithEnhancedMonitoring_Db_${env.engine}_Instances_${env.instances.length}_Plugins_efm.xlsx`, "FailoverWithEfm");
}
}

function doWritePerfDataToFile(fileName: string, worksheetName: string) {
const rows = [];
for (let i = 0; i < enhancedFailureMonitoringPerfDataList.length; i++) {
rows.push(enhancedFailureMonitoringPerfDataList[i].writeData());
PerfTestUtility.writePerfDataToFile(
enhancedFailureMonitoringPerfDataList,
`FailoverWithEnhancedMonitoring_Db_${env.engine}_Instances_${env.instances.length}_Plugins_efm.xlsx`,
"FailoverWithEfm"
);
}
const workbook = XLSX.utils.book_new();
const worksheet = XLSX.utils.json_to_sheet(rows);
XLSX.utils.book_append_sheet(workbook, worksheet, worksheetName);
XLSX.utils.sheet_add_aoa(worksheet, enhancedFailureMonitoringPerfDataList[0].writeHeader(), { origin: "A1" });
XLSX.writeFile(workbook, __dirname + "/../reports/" + fileName);
}

async function executeFailureDetectionTimeEfmEnabled(
Expand Down Expand Up @@ -231,24 +228,15 @@ async function doMeasurePerformance(sleepDelayMillis: number, repeatTimes: numbe
}
}

let min;
let max;
let total = 0;
let iterations = 0;
for (let i = 0; i < repeatTimes; i++) {
if (!isNaN(elapsedTimeMillis[i])) {
iterations++;
total += elapsedTimeMillis[i];
if (!max || elapsedTimeMillis[i] > max) {
max = elapsedTimeMillis[i];
}
if (!min || elapsedTimeMillis[i] < min) {
min = elapsedTimeMillis[i];
}
}
}
const avg = Math.round(total / iterations);
logger.debug(`Calculated average failure detection time: ${total} / ${iterations} = ${avg}`);
const [min, max, total] = elapsedTimeMillis.reduce(
([min, max, sum], val) => {
return [Math.min(val, min), Math.max(val, max), sum + val];
},
[elapsedTimeMillis[0], elapsedTimeMillis[0], 0]
);

const avg = Math.round(total / elapsedTimeMillis.length);
logger.debug(`Calculated average failure detection time: ${total} / ${elapsedTimeMillis.length} = ${avg}`);

data.paramNetworkOutageDelayMillis = sleepDelayMillis;
data.minFailureDetectionTimeMillis = min;
Expand Down Expand Up @@ -289,22 +277,12 @@ describe("performance", () => {
);
});

abstract class PerfStatBase {
class PerfStatMonitoring implements PerfStat {
paramNetworkOutageDelayMillis?: number;
minFailureDetectionTimeMillis?: number;
maxFailureDetectionTimeMillis?: number;
avgFailureDetectionTimeMillis?: number;

writeHeader(): string[][] {
return [];
}

writeData(): (number | undefined)[] {
return [];
}
}

class PerfStatMonitoring extends PerfStatBase {
paramDetectionTime?: number;
paramDetectionInterval?: number;
paramDetectionCount?: number;
Expand Down Expand Up @@ -343,7 +321,7 @@ class PerfStatMonitoring extends PerfStatBase {
`paramNetworkOutageDelayMillis=${this.paramNetworkOutageDelayMillis}, ` +
`minFailureDetectionTimeMillis=${this.minFailureDetectionTimeMillis}, ` +
`maxFailureDetectionTimeMillis=${this.maxFailureDetectionTimeMillis} ` +
`avgFailureDetectionTimeMillis=${this.avgFailureDetectionTimeMillis}`
`avgFailureDetectionTimeMillis=${this.avgFailureDetectionTimeMillis}]`
);
}
}
Loading

0 comments on commit 2593331

Please sign in to comment.