Skip to content

Commit

Permalink
test: run read write splitting performance test
Browse files Browse the repository at this point in the history
  • Loading branch information
karenc-bq committed Oct 22, 2024
1 parent 7515629 commit 683fb25
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 47 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/aurora_performance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: Aurora Performance Tests

on:
workflow_dispatch:
push:
branches:
- test/rwperf

jobs:
run-integration-tests:
Expand Down
72 changes: 25 additions & 47 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,32 +228,23 @@ 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;
data.maxFailureDetectionTimeMillis = max;
data.avgFailureDetectionTimeMillis = avg;
}

describe("performance", () => {
describe.skip("performance", () => {
beforeEach(async () => {
enhancedFailureMonitoringPerfDataList = [];
env = await TestEnvironment.getCurrent();
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}]`
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import { logger } from "../../../../common/logutils";
import { TestEnvironment } from "./utils/test_environment";
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 { features, instanceCount } from "./config";
import { PerfStat } from "./utils/perf_stat";
import { PerfTestUtility } from "./utils/perf_util";
import { ConnectTimePlugin } from "../../../../common/lib/plugins/connect_time_plugin";
import { ExecuteTimePlugin } from "../../../../common/lib/plugins/execute_time_plugin";

const itIf =
features.includes(TestEnvironmentFeatures.FAILOVER_SUPPORTED) &&
features.includes(TestEnvironmentFeatures.PERFORMANCE) &&
features.includes(TestEnvironmentFeatures.NETWORK_OUTAGES_ENABLED) &&
instanceCount >= 5
? it
: it.skip;

const REPEAT_TIMES: number = process.env.REPEAT_TIMES ? Number(process.env.REPEAT_TIMES) : 10;

let env: TestEnvironment;
let driver;
let initClientFunc: (props: any) => any;

let setReadOnlyPerfDataList: PerfStatSwitchConnection[] = [];

describe("rwperformance", () => {
beforeEach(async () => {
setReadOnlyPerfDataList = [];
env = await TestEnvironment.getCurrent();
driver = DriverHelper.getDriverForDatabaseEngine(env.engine);
initClientFunc = DriverHelper.getClient(driver);
logger.info(`Test started: ${expect.getState().currentTestName}`);
env = await TestEnvironment.getCurrent();
await ProxyHelper.enableAllConnectivity();
});

afterEach(async () => {
await TestEnvironment.verifyClusterStatus();
logger.info(`Test finished: ${expect.getState().currentTestName}`);
}, 1320000);

itIf(
"switch reader writer connection",
async () => {
const noPluginsConfig = PerfTestUtility.initDefaultConfig(env, env.databaseInfo.clusterEndpoint, env.databaseInfo.clusterEndpointPort);
const noPluginsData = await measurePerformance(noPluginsConfig);

const rwPluginsConfig = initReadWritePluginConfig(env.databaseInfo.clusterEndpoint, env.databaseInfo.clusterEndpointPort);
const rwPluginsData = await measurePerformance(rwPluginsConfig);
const switchToReaderMinOverhead = rwPluginsData.switchToReaderMin - noPluginsData.switchToReaderMin;
const switchToReaderMaxOverhead = rwPluginsData.switchToReaderMax - noPluginsData.switchToReaderMax;
const switchToReaderAvgOverhead = rwPluginsData.switchToReaderAvg - noPluginsData.switchToReaderAvg;

const switchToWriterMinOverhead = rwPluginsData.switchToWriterMin - noPluginsData.switchToWriterMin;
const switchToWriterMaxOverhead = rwPluginsData.switchToWriterMax - noPluginsData.switchToWriterMax;
const switchToWriterAvgOverhead = rwPluginsData.switchToWriterAvg - noPluginsData.switchToWriterAvg;

const readerData = new PerfStatSwitchConnection();
readerData.connectionSwitch = "Switch to reader";
readerData.minOverheadTime = switchToReaderMinOverhead;
readerData.maxOverheadTime = switchToReaderMaxOverhead;
readerData.avgOverheadTime = switchToReaderAvgOverhead;

const writerData = new PerfStatSwitchConnection();
writerData.connectionSwitch = "Switch to writer";
writerData.minOverheadTime = switchToWriterMinOverhead;
writerData.maxOverheadTime = switchToWriterMaxOverhead;
writerData.avgOverheadTime = switchToWriterAvgOverhead;

setReadOnlyPerfDataList.push(readerData, writerData);
PerfTestUtility.writePerfDataToFile(
setReadOnlyPerfDataList,
`ReadWriteSplittingPerformanceResults_${env.engine}_Instances_${env.instances.length}_SwitchReaderWriterConnection.xlsx`,
"SwitchReaderWriterConnection"
);
},
13200000
);
});

function initReadWritePluginConfig(host: string, port: number) {
const config = PerfTestUtility.initDefaultConfig(env, host, port);
config["plugins"] = "readWriteSplitting,connectTime,executionTime";
return config;
}

async function measurePerformance(config: any): Promise<Result> {
let switchToReaderStartTime;
let switchToWriterStartTime;
const elapsedSwitchToReaderTimeMillis: bigint[] = [];
const elapsedSwitchToWriterTimeMillis: bigint[] = [];

for (let i = 0; i < REPEAT_TIMES; i++) {
ConnectTimePlugin.resetConnectTime();
ExecuteTimePlugin.resetExecuteTime();

const client = initClientFunc(config);
try {
await PerfTestUtility.connectWithRetry(client);
client.on("error", (err: any) => {
logger.debug(err.message);
});

// Calculate time required to switch to a new reader connection.
switchToReaderStartTime = getTimeInNanos();
client.setReadOnly(true);

let connectTime = ConnectTimePlugin.getTotalConnectTime();
let executionTime = ExecuteTimePlugin.getTotalExecuteTime();
elapsedSwitchToReaderTimeMillis.push(getTimeInNanos() - switchToReaderStartTime - connectTime - executionTime);

// Calculate time required to switch to an existing writer connection.
ConnectTimePlugin.resetConnectTime();
ExecuteTimePlugin.resetExecuteTime();

switchToWriterStartTime = getTimeInNanos();
client.setReadOnly(false);

connectTime = ConnectTimePlugin.getTotalConnectTime();
executionTime = ExecuteTimePlugin.getTotalExecuteTime();
elapsedSwitchToWriterTimeMillis.push(getTimeInNanos() - switchToWriterStartTime - connectTime - executionTime);
} finally {
await client.end();
}
}

const data = new Result();
let [min, max, sum] = calculateStats(elapsedSwitchToReaderTimeMillis);
let avg = sum / BigInt(elapsedSwitchToReaderTimeMillis.length);
data.switchToReaderMin = min;
data.switchToReaderMax = max;
data.switchToReaderAvg = avg;

[min, max, sum] = calculateStats(elapsedSwitchToWriterTimeMillis);
avg = sum / BigInt(elapsedSwitchToWriterTimeMillis.length);
data.switchToWriterMin = min;
data.switchToWriterMax = max;
data.switchToWriterAvg = avg;

return data;
}

function getTimeInNanos(): bigint {
return process.hrtime.bigint();
}

function calculateStats(data: bigint[]): [bigint, bigint, bigint] {
return data.reduce(
([min, max, sum], e) => {
return [e < min ? e : min, e > max ? e : max, sum + e];
},
[data[0], data[0], 0n]
);
}

class Result {
switchToReaderMin: bigint = 0n;
switchToReaderMax: bigint = 0n;
switchToReaderAvg: bigint = 0n;

switchToWriterMin: bigint = 0n;
switchToWriterMax: bigint = 0n;
switchToWriterAvg: bigint = 0n;
}

class PerfStatSwitchConnection implements PerfStat {
connectionSwitch?: string;
minOverheadTime?: bigint;
maxOverheadTime?: bigint;
avgOverheadTime?: bigint;

writeHeader() {
return [["ConnectionSwitch", "MinOverheadTime", "MaxOverheadTime", "AvgOverheadTime"]];
}

writeData(): (string | bigint | number | undefined)[] {
return [this.connectionSwitch, this.minOverheadTime, this.maxOverheadTime, this.avgOverheadTime];
}

toString(): string {
return (
`[connectionSwitch=${this.connectionSwitch}, ` +
`minOverheadTime=${this.minOverheadTime}, ` +
`maxOverheadTime=${this.maxOverheadTime}, ` +
`avgOverheadTime=${this.avgOverheadTime}]`
);
}
}
21 changes: 21 additions & 0 deletions tests/integration/container/tests/utils/perf_stat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License").
You may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

export interface PerfStat {
writeHeader(): string[][];

writeData(): (string | bigint | number | undefined)[];
}
Loading

0 comments on commit 683fb25

Please sign in to comment.