Skip to content

Commit 76de811

Browse files
SK-2538: Create mock server and k6 for perf testing
1 parent 090bf90 commit 76de811

8 files changed

Lines changed: 1143 additions & 0 deletions

File tree

‎load-test/k6/chained-test.js‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import http from 'k6/http';
2+
import { check } from 'k6';
3+
import { Trend, Counter } from 'k6/metrics';
4+
5+
// Custom metrics — show up as separate series in Grafana
6+
const insertDuration = new Trend('sdk_insert_duration', true); // true = percentiles
7+
const detokDuration = new Trend('sdk_detokenize_duration', true);
8+
const insertErrors = new Counter('sdk_insert_errors');
9+
const detokErrors = new Counter('sdk_detokenize_errors');
10+
11+
export const options = {
12+
stages: [
13+
{ duration: '1m', target: 0 },
14+
{ duration: '5m', target: 100 },
15+
{ duration: '5m', target: 500 },
16+
{ duration: '5m', target: 1000 },
17+
{ duration: '5m', target: 500 },
18+
],
19+
thresholds: {
20+
http_req_duration: ['p99<3000'],
21+
http_req_failed: ['rate<0.01'],
22+
},
23+
};
24+
25+
const BASE_URL = __ENV.WRAPPER_URL || 'http://localhost:8080';
26+
27+
export default function () {
28+
// ── Step 1: Insert ────────────────────────────────────────────────────────
29+
const insertRes = http.post(
30+
`${BASE_URL}/insert`,
31+
JSON.stringify({
32+
table: 'cards',
33+
records: [{ card_number: '4111111111111111', name: 'Jane Doe' }],
34+
}),
35+
{ headers: { 'Content-Type': 'application/json' } }
36+
);
37+
38+
insertDuration.add(insertRes.timings.duration);
39+
const insertOk = check(insertRes, { 'insert status 200': (r) => r.status === 200 });
40+
if (!insertOk) {
41+
insertErrors.add(1);
42+
return;
43+
}
44+
45+
// Extract the token the SDK returned from the insert response
46+
const token = JSON.parse(insertRes.body).tokens?.card_number;
47+
if (!token) return;
48+
49+
// ── Step 2: Detokenize using the token from step 1 ────────────────────────
50+
const detokRes = http.post(
51+
`${BASE_URL}/detokenize`,
52+
JSON.stringify({ tokens: [token] }),
53+
{ headers: { 'Content-Type': 'application/json' } }
54+
);
55+
56+
detokDuration.add(detokRes.timings.duration);
57+
const detokOk = check(detokRes, {
58+
'detok status 200': (r) => r.status === 200,
59+
'value is correct': (r) => JSON.parse(r.body).records?.[0]?.value === '4111111111111111',
60+
});
61+
if (!detokOk) detokErrors.add(1);
62+
}

‎load-test/k6/detokenize-test.js‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import http from 'k6/http';
2+
import { check } from 'k6';
3+
4+
export const options = {
5+
stages: [
6+
{ duration: '1m', target: 0 },
7+
{ duration: '5m', target: 100 },
8+
{ duration: '5m', target: 500 },
9+
{ duration: '5m', target: 1000 },
10+
{ duration: '5m', target: 500 },
11+
],
12+
thresholds: {
13+
http_req_duration: ['p99<2000'],
14+
http_req_failed: ['rate<0.01'],
15+
},
16+
};
17+
18+
const BASE_URL = __ENV.WRAPPER_URL || 'http://localhost:8080';
19+
20+
// Static tokens are fine — mock server doesn't validate them
21+
const TOKENS = ['tok-mock-0', 'tok-mock-1', 'tok-mock-2'];
22+
23+
// Bulk detokenize — 3 tokens per request
24+
export default function () {
25+
const res = http.post(
26+
`${BASE_URL}/detokenize`,
27+
JSON.stringify({ tokens: TOKENS }),
28+
{ headers: { 'Content-Type': 'application/json' } }
29+
);
30+
31+
check(res, {
32+
'status 200': (r) => r.status === 200,
33+
'got 3 values back': (r) => JSON.parse(r.body).records?.length === 3,
34+
'latency < 300ms': (r) => r.timings.duration < 300,
35+
});
36+
}

‎load-test/k6/insert-test.js‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import http from 'k6/http';
2+
import { check } from 'k6';
3+
4+
export const options = {
5+
stages: [
6+
{ duration: '1m', target: 0 }, // Baseline — cold memory snapshot
7+
{ duration: '5m', target: 100 }, // Light load
8+
{ duration: '5m', target: 500 }, // Medium load
9+
{ duration: '5m', target: 1000 }, // High load
10+
{ duration: '5m', target: 500 }, // Ramp down
11+
],
12+
thresholds: {
13+
http_req_duration: ['p99<2000'],
14+
http_req_failed: ['rate<0.01'],
15+
},
16+
};
17+
18+
const BASE_URL = __ENV.WRAPPER_URL || 'http://localhost:8080';
19+
20+
// Single insert — records array has 1 item
21+
export default function () {
22+
const res = http.post(
23+
`${BASE_URL}/insert`,
24+
JSON.stringify({
25+
table: 'cards',
26+
records: [{ card_number: '4111111111111111', name: 'Jane Doe' }],
27+
}),
28+
{ headers: { 'Content-Type': 'application/json' } }
29+
);
30+
31+
check(res, {
32+
'status 200': (r) => r.status === 200,
33+
'has skyflow_id': (r) => JSON.parse(r.body).skyflow_id !== undefined,
34+
'latency < 500ms': (r) => r.timings.duration < 500,
35+
});
36+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

‎load-test/mock-server/Dockerfile‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM node:20-alpine
2+
WORKDIR /app
3+
COPY package.json .
4+
RUN npm install --omit=dev
5+
COPY server.js .
6+
EXPOSE 8181
7+
CMD ["node", "server.js"]

0 commit comments

Comments
 (0)