From b6d1a4652248771aed28b43914332e1ec3a5c137 Mon Sep 17 00:00:00 2001 From: Thuy Dao Date: Wed, 6 May 2026 10:01:23 +1000 Subject: [PATCH 1/7] Add scalability tests Co-authored-by: Copilot --- scalability/2_curiosity.js | 73 +++++++++++++++++ scalability/3_compliance_early.js | 1 - scalability/4_compliance_mid.js | 81 +++++++++++++++++++ scalability/5_compliance_peak.js | 96 ++++++++++++++++++++++ scalability/6_compliance_tail.js | 92 +++++++++++++++++++++ scalability/7_auditing_bau.js | 126 +++++++++++++++++++++++++++++ scalability/8_research.js | 74 +++++++++++++++++ scalability/bin/runk6.sh | 6 ++ scalability/data/photo.js | 17 +++- scalability/workflows/querier.js | 44 ++++++++-- scalability/workflows/submitter.js | 5 ++ 11 files changed, 604 insertions(+), 11 deletions(-) create mode 100644 scalability/2_curiosity.js create mode 100644 scalability/4_compliance_mid.js create mode 100644 scalability/5_compliance_peak.js create mode 100644 scalability/6_compliance_tail.js create mode 100644 scalability/7_auditing_bau.js create mode 100644 scalability/8_research.js diff --git a/scalability/2_curiosity.js b/scalability/2_curiosity.js new file mode 100644 index 0000000..ae02ec5 --- /dev/null +++ b/scalability/2_curiosity.js @@ -0,0 +1,73 @@ +import { sleep } from "k6"; +import exec from "k6/execution"; +import { submitter } from "./workflows/submitter.js"; +import { queryUsersList, queryUserById } from "./workflows/querier.js"; + +const BASE_URL = __ENV.TEST_HOST; + +export let options = { + scenarios: { + submitters: { + executor: "constant-vus", + vus: 10, + duration: "2m", + exec: "submitRequests", + }, + auditors: { + executor: "ramping-arrival-rate", + startTime: "2m", + startRate: 100, + timeUnit: "1s", + preAllocatedVUs: 200, + maxVUs: 500, + stages: [ + // generate ~60k requests in the 2-minute sustained burst (500 req/s × 120s) + { duration: "30s", target: 500 }, // ramp to 500 req/s + { duration: "2m", target: 500 }, // sustain ~60k requests + { duration: "30s", target: 10 }, // cool down + ], + exec: "auditorRead", + }, + }, +}; + +export function setup() { + let customers = []; + let userIds = []; + for (let i = 0; i < 5; i++) customers.push(crypto.randomUUID()); + for (let i = 0; i < 50; i++) userIds.push(crypto.randomUUID()); + return { + customers: customers, + portalCustomer: customers[0], + userIds: userIds, + tag: "curiosity", + generator: "normal", + urgentRatio: 0.05, + }; +} + +export function submitRequests(data) { + // Propagate all user IDs to ensure each has data before auditor starts + // Use modulo to loop back to the start if iterations exceed data length + let idx = exec.scenario.iterationInTest % data.userIds.length; + let scoped = Object.assign({}, data, { + customers: [data.portalCustomer], + userIds: [data.userIds[idx]], + }); + submitter(scoped); + sleep(5); +} + +export function auditorRead(data) { + let host = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL; + let customer = data.portalCustomer; + + // Query list of users for the customer + queryUsersList(host, customer); + sleep(1); + + // Query individual user results + let userId = data.userIds[Math.floor(Math.random() * data.userIds.length)]; + queryUserById(host, customer, userId); + sleep(1); +} diff --git a/scalability/3_compliance_early.js b/scalability/3_compliance_early.js index bd977b3..cedceea 100644 --- a/scalability/3_compliance_early.js +++ b/scalability/3_compliance_early.js @@ -55,7 +55,6 @@ export function setup() { tag: "compliance_early", generator: "normal", urgentRatio: 0.05, - photoComplexity: 4, }; } diff --git a/scalability/4_compliance_mid.js b/scalability/4_compliance_mid.js new file mode 100644 index 0000000..c83e32f --- /dev/null +++ b/scalability/4_compliance_mid.js @@ -0,0 +1,81 @@ +import { sleep } from "k6"; +import { submitter } from "./workflows/submitter.js"; +import { + queryRequestsList, + queryStats, + queryBatchUserResults, +} from "./workflows/querier.js"; + +const BASE_URL = __ENV.TEST_HOST; + +export let options = { + scenarios: { + submitters: { + executor: "ramping-vus", + startVUs: 0, + stages: [ + { duration: "2m", target: 5 }, // ramp up to 5 VUs, at ~10s/iteration -> 6 iterations per minute per VU, so 6 * 5 * 2 = 60 iterations (POST + poll GET) in the first 2 minute + { duration: "3m", target: 50 }, // ramp up to 50 VUs, so 6 * 50 * 3 = 900 iterations in the next 3 minutes + { duration: "2m", target: 10 }, // ramp down to 10 VUs, so 6 * 10 * 2 = 120 iterations in the next 2 minutes + { duration: "3m", target: 2 }, // ramp down to 2 VUs, so 6 * 2 * 3 = 36 iterations in the last 3 minutes + ], + exec: "submitRequests", + }, + readers: { + executor: "ramping-vus", + startVUs: 0, + startTime: "1m", + stages: [ + { duration: "2m", target: 3 }, + { duration: "3m", target: 15 }, + { duration: "2m", target: 15 }, + { duration: "3m", target: 3 }, + ], + exec: "readResults", + }, + auditors: { + executor: "constant-vus", + startTime: "1m", + vus: 5, + duration: "10m", + exec: "auditBatch", + }, + }, +}; + +export function setup() { + let customers = []; + let userIds = []; + for (let i = 0; i < 15; i++) customers.push(crypto.randomUUID()); + for (let i = 0; i < 60; i++) userIds.push(crypto.randomUUID()); + return { + customers: customers, + userIds: userIds, + tag: "compliance_mid", + generator: "normal", + urgentRatio: 0.15, + }; +} + +export function submitRequests(data) { + submitter(data); + sleep(5); +} + +export function readResults(data) { + let host = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL; + let customer = + data.customers[Math.floor(Math.random() * data.customers.length)]; + + queryRequestsList(host, customer); + sleep(1); + queryStats(host, customer); + sleep(5); +} + +export function auditBatch(data) { + let host = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL; + let batch = data.customers.slice(0, 5); + queryBatchUserResults(host, batch); + sleep(5); +} diff --git a/scalability/5_compliance_peak.js b/scalability/5_compliance_peak.js new file mode 100644 index 0000000..37fcf73 --- /dev/null +++ b/scalability/5_compliance_peak.js @@ -0,0 +1,96 @@ +import { sleep } from "k6"; +import { submitter } from "./workflows/submitter.js"; +import { + pollPendingRequests, + queryBatchUserResults, + queryUserResults, + queryRequestsList, +} from "./workflows/querier.js"; + +const BASE_URL = __ENV.TEST_HOST; + +export let options = { + scenarios: { + submitters: { + executor: "ramping-vus", + startVUs: 0, + stages: [ + { duration: "1m", target: 5 }, // ramp up to 5 VUs, at ~10s/iteration -> 6 iterations per minute per VU, so 6 * 5 * 1 = 30 iterations (POST + poll GET) in the first minute + { duration: "2m", target: 40 }, // ramp up to 40 VUs, so 6 * 40 * 2 = 480 iterations in the next 2 minutes + { duration: "2m", target: 40 }, // stay at 40 VUs, so 6 * 40 * 2 = 480 iterations in the next 2 minutes + { duration: "1m", target: 2 }, // ramp down to 2 VUs, so 6 * 2 * 1 = 12 iterations in the last minute + ], + exec: "submitRequests", + }, + readers: { + executor: "ramping-vus", + startVUs: 0, + startTime: "1m", + stages: [ + { duration: "1m", target: 5 }, + { duration: "4m", target: 60 }, + { duration: "4m", target: 60 }, + { duration: "1m", target: 10 }, + ], + exec: "readResults", + }, + auditors: { + executor: "ramping-vus", + startVUs: 0, + startTime: "1m", + stages: [ + { duration: "2m", target: 5 }, + { duration: "4m", target: 15 }, + { duration: "3m", target: 15 }, + { duration: "1m", target: 1 }, + ], + exec: "auditBatch", + }, + }, +}; + +export function setup() { + let customers = []; + let userIds = []; + for (let i = 0; i < 20; i++) customers.push(crypto.randomUUID()); + for (let i = 0; i < 100; i++) userIds.push(crypto.randomUUID()); + return { + customers: customers, + userIds: userIds, + tag: "compliance_peak", + generator: "heavy", + urgentRatio: 0.3, + }; +} + +export function submitRequests(data) { + submitter(data); + sleep(10); +} + +export function readResults(data) { + let host = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL; + let customer = + data.customers[Math.floor(Math.random() * data.customers.length)]; + + queryRequestsList(host, customer); + sleep(10); +} + +export function auditBatch(data) { + let host = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL; + + // Batch queries across all clients + queryBatchUserResults(host, data.customers); + sleep(1); + + // Per-user queries + let customer = + data.customers[Math.floor(Math.random() * data.customers.length)]; + for (let i = 0; i < data.customers.length; i++) { + customer = data.customers[i]; + queryUserResults(host, customer, data.userIds); + sleep(1); + } + sleep(1); +} diff --git a/scalability/6_compliance_tail.js b/scalability/6_compliance_tail.js new file mode 100644 index 0000000..d7badb4 --- /dev/null +++ b/scalability/6_compliance_tail.js @@ -0,0 +1,92 @@ +import { sleep } from "k6"; +import { submitter } from "./workflows/submitter.js"; +import { + pollPendingRequests, + queryBatchUserResults, + queryUserResults, +} from "./workflows/querier.js"; +import { randomIntBetween } from "https://jslib.k6.io/k6-utils/1.2.0/index.js"; + +const BASE_URL = __ENV.TEST_HOST; + +export let options = { + scenarios: { + submitters: { + executor: "ramping-vus", + startVUs: 0, + stages: [ + { duration: "2m", target: 10 }, + { duration: "2m", target: 40 }, + { duration: "2m", target: 20 }, + { duration: "2m", target: 35 }, + { duration: "2m", target: 5 }, + ], + exec: "submitRequests", + }, + readers: { + executor: "constant-vus", + vus: 20, + startTime: "1m", + duration: "10m", + exec: "readResults", + }, + auditors: { + executor: "constant-vus", + vus: 8, + startTime: "1m", + duration: "10m", + exec: "auditBatch", + }, + }, +}; + +export function setup() { + let customers = []; + let userIds = []; + for (let i = 0; i < 20; i++) customers.push(crypto.randomUUID()); + for (let i = 0; i < 80; i++) userIds.push(crypto.randomUUID()); + return { + customers: customers, + userIds: userIds, + tag: "compliance_tail", + generator: "heavy", + urgentRatio: 0.1, + }; +} + +export function submitRequests(data) { + submitter(data); + sleep(5); +} + +export function readResults(data) { + let host = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL; + let customer = + data.customers[Math.floor(Math.random() * data.customers.length)]; + + pollPendingRequests(host, customer); + sleep(5); +} + +export function auditBatch(data) { + let host = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL; + + // Moderate batch queries + let batchSize = randomIntBetween(4, data.customers.length); + let startIdx = Math.floor( + Math.random() * (data.customers.length - batchSize), + ); + let batch = data.customers.slice(startIdx, startIdx + batchSize); + queryBatchUserResults(host, batch); + sleep(1); + + // Per-user queries (moderate) + let customer = + data.customers[Math.floor(Math.random() * data.customers.length)]; + let userBatch = data.userIds.slice( + 0, + randomIntBetween(3, data.userIds.length), + ); + queryUserResults(host, customer, userBatch); + sleep(5); +} diff --git a/scalability/7_auditing_bau.js b/scalability/7_auditing_bau.js new file mode 100644 index 0000000..8bf1689 --- /dev/null +++ b/scalability/7_auditing_bau.js @@ -0,0 +1,126 @@ +import { sleep } from "k6"; +import { submitter } from "./workflows/submitter.js"; +import { + queryRequestsList, + queryStats, + queryBatchUserResults, + queryUserResults, +} from "./workflows/querier.js"; +import { randomIntBetween } from "https://jslib.k6.io/k6-utils/1.2.0/index.js"; + +const BASE_URL = __ENV.TEST_HOST; + +export let options = { + scenarios: { + normal_submitters: { + executor: "constant-vus", + vus: 10, + duration: "10m", + exec: "submitNormal", + }, + backlog_submitters: { + executor: "ramping-vus", + startVUs: 0, + stages: [ + { duration: "2m", target: 5 }, + { duration: "3m", target: 25 }, + { duration: "3m", target: 25 }, + { duration: "2m", target: 5 }, + ], + exec: "submitBacklog", + }, + moderate_readers: { + executor: "constant-vus", + vus: 10, + startTime: "1m", + duration: "10m", + exec: "readResults", + }, + heavy_auditors: { + executor: "ramping-vus", + startVUs: 0, + startTime: "1m", + stages: [ + { duration: "2m", target: 5 }, + { duration: "3m", target: 25 }, + { duration: "3m", target: 25 }, + { duration: "2m", target: 5 }, + ], + exec: "heavyAudit", + }, + }, +}; + +export function setup() { + let normalCustomers = []; + let backlogCustomers = []; + let userIds = []; + for (let i = 0; i < 16; i++) normalCustomers.push(crypto.randomUUID()); + for (let i = 0; i < 4; i++) backlogCustomers.push(crypto.randomUUID()); + for (let i = 0; i < 100; i++) userIds.push(crypto.randomUUID()); + return { + customers: normalCustomers.concat(backlogCustomers), + normalCustomers: normalCustomers, + backlogCustomers: backlogCustomers, + userIds: userIds, + tag: "auditing_bau", + generator: "normal", + urgentRatio: 0.02, + }; +} + +export function submitNormal(data) { + let normalData = Object.assign({}, data, { + customers: data.normalCustomers, + tag: "auditing_bau_normal", + generator: "normal", + urgentRatio: 0.02, + }); + submitter(normalData); + sleep(5); +} + +export function submitBacklog(data) { + let backlogData = Object.assign({}, data, { + customers: data.backlogCustomers, + tag: "auditing_bau_backlog", + generator: "heavy", + urgentRatio: 0.05, + }); + submitter(backlogData); + sleep(5); +} + +export function readResults(data) { + let host = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL; + let customer = + data.customers[Math.floor(Math.random() * data.customers.length)]; + + queryRequestsList(host, customer); + sleep(1); + queryStats(host, customer); + sleep(5); +} + +export function heavyAudit(data) { + let host = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL; + + // Large batch queries across all clients + let batchSize = randomIntBetween(8, data.customers.length); + let startIdx = Math.floor( + Math.random() * Math.max(1, data.customers.length - batchSize), + ); + let batch = data.customers.slice(startIdx, startIdx + batchSize); + queryBatchUserResults(host, batch); + sleep(1); + + // Large per-user queries + let customer = + data.customers[Math.floor(Math.random() * data.customers.length)]; + let userBatch = data.userIds.slice( + 0, + randomIntBetween(5, data.userIds.length), + ); + queryUserResults(host, customer, userBatch); + sleep(5); +} diff --git a/scalability/8_research.js b/scalability/8_research.js new file mode 100644 index 0000000..2366a33 --- /dev/null +++ b/scalability/8_research.js @@ -0,0 +1,74 @@ +import { sleep } from "k6"; +import { submitter } from "./workflows/submitter.js"; +import { + queryRequestsList, + queryStats, + queryRequestById, +} from "./workflows/querier.js"; + +const BASE_URL = __ENV.TEST_HOST; + +export let options = { + scenarios: { + normal_submitters: { + executor: "constant-vus", + vus: 10, + duration: "5m", + exec: "submitRequests", + }, + researcher: { + executor: "constant-vus", + vus: 4, + startTime: "1m", + duration: "5m", + exec: "researchScan", + }, + }, +}; + +export function setup() { + let customers = []; + let userIds = []; + for (let i = 0; i < 15; i++) customers.push(crypto.randomUUID()); + for (let i = 0; i < 100; i++) userIds.push(crypto.randomUUID()); + return { + customers: customers, + userIds: userIds, + tag: "research", + generator: "normal", + urgentRatio: 0.05, + }; +} + +export function submitRequests(data) { + submitter(data); + sleep(5); +} + +export function researchScan(data) { + let host = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL; + + for (let i = 0; i < data.customers.length; i++) { + let customer = data.customers[i]; + + // Get all requests for this customer + let res = queryRequestsList(host, customer); + sleep(1); + + // Get stats + queryStats(host, customer); + sleep(1); + + // Query each request result for this customer + if (res.status === 200 && Array.isArray(res.json())) { + let requests = res.json(); + for (let j = 0; j < requests.length; j++) { + queryRequestById(host, customer, requests[j].id); + sleep(0.5); + } + } + + sleep(1); + } + sleep(5); +} diff --git a/scalability/bin/runk6.sh b/scalability/bin/runk6.sh index 32ca7a4..5600e79 100755 --- a/scalability/bin/runk6.sh +++ b/scalability/bin/runk6.sh @@ -12,4 +12,10 @@ mkdir -p logs # k6run 0_debug k6run 1_normal +k6run 2_compliance k6run 3_compliance_early +k6run 4_compliance_mid +k6run 5_compliance_peak +k6run 6_compliance_tail +k6run 7_auditing_bau +k6run 8_research diff --git a/scalability/data/photo.js b/scalability/data/photo.js index fafbe3b..679cfb1 100644 --- a/scalability/data/photo.js +++ b/scalability/data/photo.js @@ -75,12 +75,12 @@ export function getPhotos(generator) { count = 1; break; case "heavy": - complexity = randomIntBetween(13, 16); + complexity = randomIntBetween(13, 20); count = 1; break; case "normal": default: - complexity = randomIntBetween(8, 16); + complexity = randomIntBetween(8, 20); count = randomIntBetween(1, 2); break; } @@ -93,8 +93,21 @@ export function getPhotos(generator) { totalAge += photo.age; } + // Expected processing delay based on engine benchmarks + let expectedDelay; + if (complexity <= 10) { + expectedDelay = 1; + } else if (complexity <= 12) { + expectedDelay = 2; + } else if (complexity <= 16) { + expectedDelay = 5; + } else { + expectedDelay = 18; + } + return { payloads: payloads, checksum: "0x" + totalAge.toString(16), + expectedDelay: expectedDelay, }; } diff --git a/scalability/workflows/querier.js b/scalability/workflows/querier.js index 515931a..0dab777 100644 --- a/scalability/workflows/querier.js +++ b/scalability/workflows/querier.js @@ -37,6 +37,20 @@ export function queryUsersList(hostUrl, customer) { return res; } +export function queryUserById(hostUrl, customer, userId) { + let url = hostUrl + "/analysis/" + customer + "/users/" + userId; + let res = timedGet(url, { endpoint: "/users", type: "detail" }); + try { + let success = check(res, checkUser); + if (!success) { + errors.add(1, { endpoint: "/users/" + userId }); + } + } catch (e) { + errors.add(1, { endpoint: "/users/" + userId }); + } + return res; +} + export function queryRequestsList(hostUrl, customer, params) { let url = hostUrl + "/analysis/" + customer + "/requests"; let res = timedGet(url, { endpoint: "/requests", type: "list" }); @@ -79,6 +93,17 @@ export function queryStats(hostUrl, customer) { return res; } +export function pollPendingRequests(hostUrl, customer) { + let res = queryRequestsList(hostUrl, customer, { status: "pending" }); + if (res.status === 200 && Array.isArray(res.json())) { + let pending = res.json(); + for (let i = 0; i < pending.length; i++) { + queryRequestById(hostUrl, customer, pending[i].id); + sleep(0.5); + } + } +} + export function queryBatchUserResults(hostUrl, customers) { for (let i = 0; i < customers.length; i++) { let customer = customers[i]; @@ -86,13 +111,16 @@ export function queryBatchUserResults(hostUrl, customers) { sleep(0.5); - let res = queryRequestsList(hostUrl, customer, { status: "pending" }); - if (res.status === 200 && Array.isArray(res.json())) { - let pending = res.json(); - for (let i = 0; i < pending.length; i++) { - queryRequestById(hostUrl, customer, pending[i].id); - sleep(0.5); - } - } + pollPendingRequests(hostUrl, customer); + + sleep(0.5); + } +} + +export function queryUserResults(hostUrl, customer, userIds) { + for (let i = 0; i < userIds.length; i++) { + let userId = userIds[i]; + queryUserById(hostUrl, customer, userId); + sleep(0.5); } } diff --git a/scalability/workflows/submitter.js b/scalability/workflows/submitter.js index 78f3b74..bfa7d8c 100644 --- a/scalability/workflows/submitter.js +++ b/scalability/workflows/submitter.js @@ -56,6 +56,11 @@ export function submitter(data, timeout = 60) { let requestId = res.json().id; + // Wait for expected processing time before polling + let initialWait = photoData.expectedDelay || 5; + sleep(initialWait); + timeout -= initialWait; + // Check if completed while (timeout > 0) { let analysis = http.get(url + "/" + requestId, { From b62f0a5937120097c92b137d32357afec69fb29f Mon Sep 17 00:00:00 2001 From: Thuy Dao Date: Sun, 10 May 2026 15:50:38 +1000 Subject: [PATCH 2/7] Reduce users in compliance peak --- scalability/5_compliance_peak.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scalability/5_compliance_peak.js b/scalability/5_compliance_peak.js index 37fcf73..6e122f4 100644 --- a/scalability/5_compliance_peak.js +++ b/scalability/5_compliance_peak.js @@ -53,7 +53,7 @@ export function setup() { let customers = []; let userIds = []; for (let i = 0; i < 20; i++) customers.push(crypto.randomUUID()); - for (let i = 0; i < 100; i++) userIds.push(crypto.randomUUID()); + for (let i = 0; i < 70; i++) userIds.push(crypto.randomUUID()); return { customers: customers, userIds: userIds, From c05d7c8506e1c33511ef6f16ba258b1404464e74 Mon Sep 17 00:00:00 2001 From: Thuy Dao Date: Sun, 10 May 2026 15:57:53 +1000 Subject: [PATCH 3/7] remove expected delay based on engine benchmarks Co-authored-by: Copilot --- scalability/data/photo.js | 13 ------------- scalability/workflows/submitter.js | 5 ++--- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/scalability/data/photo.js b/scalability/data/photo.js index 679cfb1..deaca72 100644 --- a/scalability/data/photo.js +++ b/scalability/data/photo.js @@ -93,21 +93,8 @@ export function getPhotos(generator) { totalAge += photo.age; } - // Expected processing delay based on engine benchmarks - let expectedDelay; - if (complexity <= 10) { - expectedDelay = 1; - } else if (complexity <= 12) { - expectedDelay = 2; - } else if (complexity <= 16) { - expectedDelay = 5; - } else { - expectedDelay = 18; - } - return { payloads: payloads, checksum: "0x" + totalAge.toString(16), - expectedDelay: expectedDelay, }; } diff --git a/scalability/workflows/submitter.js b/scalability/workflows/submitter.js index bfa7d8c..065d54f 100644 --- a/scalability/workflows/submitter.js +++ b/scalability/workflows/submitter.js @@ -57,9 +57,8 @@ export function submitter(data, timeout = 60) { let requestId = res.json().id; // Wait for expected processing time before polling - let initialWait = photoData.expectedDelay || 5; - sleep(initialWait); - timeout -= initialWait; + sleep(5); + timeout -= 5; // Check if completed while (timeout > 0) { From aaf05dab4af2738eadc6b744dd68210c7d6bf7ae Mon Sep 17 00:00:00 2001 From: Thuy Dao Date: Thu, 14 May 2026 22:46:59 +1000 Subject: [PATCH 4/7] Fix missing param handling and customer query Co-authored-by: Copilot --- scalability/5_compliance_peak.js | 6 ++---- scalability/workflows/querier.js | 10 ++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/scalability/5_compliance_peak.js b/scalability/5_compliance_peak.js index 6e122f4..0845b16 100644 --- a/scalability/5_compliance_peak.js +++ b/scalability/5_compliance_peak.js @@ -84,11 +84,9 @@ export function auditBatch(data) { queryBatchUserResults(host, data.customers); sleep(1); - // Per-user queries - let customer = - data.customers[Math.floor(Math.random() * data.customers.length)]; + // Per-user queries across all customers for (let i = 0; i < data.customers.length; i++) { - customer = data.customers[i]; + const customer = data.customers[i]; queryUserResults(host, customer, data.userIds); sleep(1); } diff --git a/scalability/workflows/querier.js b/scalability/workflows/querier.js index 0dab777..42c15ba 100644 --- a/scalability/workflows/querier.js +++ b/scalability/workflows/querier.js @@ -13,7 +13,13 @@ const queriesTotal = new Counter("queries_total"); const queryDelay = new Trend("query_delay"); const errors = new Counter("errors"); -function timedGet(url, tags) { +function timedGet(url, tags, params) { + if (params) { + let query = Object.entries(params) + .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) + .join("&"); + url = url + "?" + query; + } let start = Date.now(); let res = http.get(url, { headers: { Accept: "application/json" } }); let elapsed = Date.now() - start; @@ -53,7 +59,7 @@ export function queryUserById(hostUrl, customer, userId) { export function queryRequestsList(hostUrl, customer, params) { let url = hostUrl + "/analysis/" + customer + "/requests"; - let res = timedGet(url, { endpoint: "/requests", type: "list" }); + let res = timedGet(url, { endpoint: "/requests", type: "list" }, params); try { let success = check(res, checkAnalysisList); if (!success) { From 2c28b35037605f1e2ef163d3e14b793a6e97e765 Mon Sep 17 00:00:00 2001 From: Thuy Dao Date: Thu, 14 May 2026 23:12:22 +1000 Subject: [PATCH 5/7] add guard against batch index --- scalability/6_compliance_tail.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scalability/6_compliance_tail.js b/scalability/6_compliance_tail.js index d7badb4..6e9be9e 100644 --- a/scalability/6_compliance_tail.js +++ b/scalability/6_compliance_tail.js @@ -74,7 +74,7 @@ export function auditBatch(data) { // Moderate batch queries let batchSize = randomIntBetween(4, data.customers.length); let startIdx = Math.floor( - Math.random() * (data.customers.length - batchSize), + Math.random() * Math.max(1, data.customers.length - batchSize), ); let batch = data.customers.slice(startIdx, startIdx + batchSize); queryBatchUserResults(host, batch); From f0eb439f4230c271e4b3d32d1832f78aad02a3aa Mon Sep 17 00:00:00 2001 From: Thuy Dao Date: Fri, 15 May 2026 16:51:37 +1000 Subject: [PATCH 6/7] remove randomness of no. user ids and customers, queryUserResults only check results of existing user id Co-authored-by: Copilot --- scalability/2_curiosity.js | 24 ++++++++------------- scalability/5_compliance_peak.js | 9 +++++--- scalability/6_compliance_tail.js | 8 ++----- scalability/7_auditing_bau.js | 8 ++----- scalability/8_research.js | 7 +++---- scalability/bin/runk6.sh | 2 +- scalability/workflows/querier.js | 36 ++++++++++++++++++++------------ 7 files changed, 46 insertions(+), 48 deletions(-) diff --git a/scalability/2_curiosity.js b/scalability/2_curiosity.js index ae02ec5..1a4d4c0 100644 --- a/scalability/2_curiosity.js +++ b/scalability/2_curiosity.js @@ -21,7 +21,7 @@ export let options = { preAllocatedVUs: 200, maxVUs: 500, stages: [ - // generate ~60k requests in the 2-minute sustained burst (500 req/s × 120s) + // generate ~60k requests in the 2-minute sustained burst (500 req/s × 120s) { duration: "30s", target: 500 }, // ramp to 500 req/s { duration: "2m", target: 500 }, // sustain ~60k requests { duration: "30s", target: 10 }, // cool down @@ -38,7 +38,6 @@ export function setup() { for (let i = 0; i < 50; i++) userIds.push(crypto.randomUUID()); return { customers: customers, - portalCustomer: customers[0], userIds: userIds, tag: "curiosity", generator: "normal", @@ -47,27 +46,22 @@ export function setup() { } export function submitRequests(data) { - // Propagate all user IDs to ensure each has data before auditor starts - // Use modulo to loop back to the start if iterations exceed data length - let idx = exec.scenario.iterationInTest % data.userIds.length; - let scoped = Object.assign({}, data, { - customers: [data.portalCustomer], - userIds: [data.userIds[idx]], - }); - submitter(scoped); + submitter(data); sleep(5); } export function auditorRead(data) { let host = BASE_URL.endsWith("/") ? BASE_URL.slice(0, -1) : BASE_URL; - let customer = data.portalCustomer; + let customer = data.customers[0]; // Query list of users for the customer - queryUsersList(host, customer); + let res = queryUsersList(host, customer); sleep(1); - // Query individual user results - let userId = data.userIds[Math.floor(Math.random() * data.userIds.length)]; - queryUserById(host, customer, userId); + // Query a random user discovered from the list endpoint + if (res.users && res.users.length > 0) { + let user = res.users[Math.floor(Math.random() * res.users.length)]; + queryUserById(host, customer, user.id); + } sleep(1); } diff --git a/scalability/5_compliance_peak.js b/scalability/5_compliance_peak.js index 0845b16..d0e0228 100644 --- a/scalability/5_compliance_peak.js +++ b/scalability/5_compliance_peak.js @@ -53,7 +53,7 @@ export function setup() { let customers = []; let userIds = []; for (let i = 0; i < 20; i++) customers.push(crypto.randomUUID()); - for (let i = 0; i < 70; i++) userIds.push(crypto.randomUUID()); + for (let i = 0; i < 90; i++) userIds.push(crypto.randomUUID()); return { customers: customers, userIds: userIds, @@ -73,8 +73,11 @@ export function readResults(data) { let customer = data.customers[Math.floor(Math.random() * data.customers.length)]; + // List requests, then re-query any that are still pending queryRequestsList(host, customer); - sleep(10); + sleep(1); + pollPendingRequests(host, customer); + sleep(5); } export function auditBatch(data) { @@ -87,7 +90,7 @@ export function auditBatch(data) { // Per-user queries across all customers for (let i = 0; i < data.customers.length; i++) { const customer = data.customers[i]; - queryUserResults(host, customer, data.userIds); + queryUserResults(host, customer); sleep(1); } sleep(1); diff --git a/scalability/6_compliance_tail.js b/scalability/6_compliance_tail.js index 6e9be9e..2514dc4 100644 --- a/scalability/6_compliance_tail.js +++ b/scalability/6_compliance_tail.js @@ -80,13 +80,9 @@ export function auditBatch(data) { queryBatchUserResults(host, batch); sleep(1); - // Per-user queries (moderate) + // Per-user queries max 50 users let customer = data.customers[Math.floor(Math.random() * data.customers.length)]; - let userBatch = data.userIds.slice( - 0, - randomIntBetween(3, data.userIds.length), - ); - queryUserResults(host, customer, userBatch); + queryUserResults(host, customer, 50); sleep(5); } diff --git a/scalability/7_auditing_bau.js b/scalability/7_auditing_bau.js index 8bf1689..5e9b28d 100644 --- a/scalability/7_auditing_bau.js +++ b/scalability/7_auditing_bau.js @@ -114,13 +114,9 @@ export function heavyAudit(data) { queryBatchUserResults(host, batch); sleep(1); - // Large per-user queries + // Large per-user queries max 50 users let customer = data.customers[Math.floor(Math.random() * data.customers.length)]; - let userBatch = data.userIds.slice( - 0, - randomIntBetween(5, data.userIds.length), - ); - queryUserResults(host, customer, userBatch); + queryUserResults(host, customer, 50); sleep(5); } diff --git a/scalability/8_research.js b/scalability/8_research.js index 2366a33..84d5ffe 100644 --- a/scalability/8_research.js +++ b/scalability/8_research.js @@ -60,10 +60,9 @@ export function researchScan(data) { sleep(1); // Query each request result for this customer - if (res.status === 200 && Array.isArray(res.json())) { - let requests = res.json(); - for (let j = 0; j < requests.length; j++) { - queryRequestById(host, customer, requests[j].id); + if (res.items) { + for (let j = 0; j < res.items.length; j++) { + queryRequestById(host, customer, res.items[j].id); sleep(0.5); } } diff --git a/scalability/bin/runk6.sh b/scalability/bin/runk6.sh index 5600e79..d82748a 100755 --- a/scalability/bin/runk6.sh +++ b/scalability/bin/runk6.sh @@ -12,7 +12,7 @@ mkdir -p logs # k6run 0_debug k6run 1_normal -k6run 2_compliance +k6run 2_curiosity k6run 3_compliance_early k6run 4_compliance_mid k6run 5_compliance_peak diff --git a/scalability/workflows/querier.js b/scalability/workflows/querier.js index 42c15ba..6ed7b0e 100644 --- a/scalability/workflows/querier.js +++ b/scalability/workflows/querier.js @@ -32,15 +32,18 @@ export function queryUsersList(hostUrl, customer) { let url = hostUrl + "/analysis/" + customer + "/users"; let res = timedGet(url, { endpoint: "/users", type: "list" }); + let users = null; try { let success = check(res, checkUserList); - if (!success) { + if (success) { + users = res.json(); + } else { errors.add(1, { endpoint: "/users" }); } } catch (e) { errors.add(1, { endpoint: "/users" }); } - return res; + return { res: res, users: users }; } export function queryUserById(hostUrl, customer, userId) { @@ -60,15 +63,18 @@ export function queryUserById(hostUrl, customer, userId) { export function queryRequestsList(hostUrl, customer, params) { let url = hostUrl + "/analysis/" + customer + "/requests"; let res = timedGet(url, { endpoint: "/requests", type: "list" }, params); + let items = null; try { let success = check(res, checkAnalysisList); - if (!success) { + if (success) { + items = res.json(); + } else { errors.add(1, { endpoint: "/requests" }); } } catch (e) { errors.add(1, { endpoint: "/requests" }); } - return res; + return { res: res, items: items }; } export function queryRequestById(hostUrl, customer, requestId) { @@ -100,11 +106,10 @@ export function queryStats(hostUrl, customer) { } export function pollPendingRequests(hostUrl, customer) { - let res = queryRequestsList(hostUrl, customer, { status: "pending" }); - if (res.status === 200 && Array.isArray(res.json())) { - let pending = res.json(); - for (let i = 0; i < pending.length; i++) { - queryRequestById(hostUrl, customer, pending[i].id); + let result = queryRequestsList(hostUrl, customer, { status: "pending" }); + if (result.items) { + for (let i = 0; i < result.items.length; i++) { + queryRequestById(hostUrl, customer, result.items[i].id); sleep(0.5); } } @@ -123,10 +128,15 @@ export function queryBatchUserResults(hostUrl, customers) { } } -export function queryUserResults(hostUrl, customer, userIds) { - for (let i = 0; i < userIds.length; i++) { - let userId = userIds[i]; - queryUserById(hostUrl, customer, userId); +export function queryUserResults(hostUrl, customer, maxUsers) { + let res = queryUsersList(hostUrl, customer); + let users = res.users; + if (!users) { + return; + } + let limit = maxUsers || users.length; + for (let i = 0; i < Math.min(users.length, limit); i++) { + queryUserById(hostUrl, customer, users[i].id); sleep(0.5); } } From f9e277bbbca879fea2186d9f7c2fce36d26339e0 Mon Sep 17 00:00:00 2001 From: Thuy Dao Date: Fri, 15 May 2026 16:56:22 +1000 Subject: [PATCH 7/7] remove unused import of exec from k6/execution --- scalability/2_curiosity.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scalability/2_curiosity.js b/scalability/2_curiosity.js index 1a4d4c0..9309d07 100644 --- a/scalability/2_curiosity.js +++ b/scalability/2_curiosity.js @@ -1,5 +1,4 @@ import { sleep } from "k6"; -import exec from "k6/execution"; import { submitter } from "./workflows/submitter.js"; import { queryUsersList, queryUserById } from "./workflows/querier.js";