Skip to content

Commit edd7c8c

Browse files
SattvikSattvik
Sattvik
authored and
Sattvik
committed
fix: stress tests
1 parent 40b635f commit edd7c8c

File tree

7 files changed

+265
-3
lines changed

7 files changed

+265
-3
lines changed

.github/workflows/stress-tests.yml

+6-1
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ jobs:
1919
run: |
2020
cd stress-tests
2121
npm install
22+
- name: Generate user jsons
23+
run: |
24+
cd stress-tests
25+
npm run generate-users
2226
- name: Run one million users test
2327
id: one-million-users
2428
run: |
2529
cd stress-tests
26-
npm run one-million-users
30+
npm run one-million-users | tee stress-tests.log
31+

stress-tests/.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,5 @@ coverage/
5151

5252
# Yarn Integrity file
5353
.yarn-integrity
54+
55+
users/

stress-tests/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"scripts": {
77
"build": "tsc",
88
"start": "node dist/index.js",
9+
"generate-users": "rm -rf users && mkdir -p users && ts-node src/oneMillionUsersImport/generateUsers.ts",
910
"one-million-users": "ts-node src/oneMillionUsers/index.ts"
1011
},
1112
"keywords": [],
@@ -17,6 +18,8 @@
1718
"typescript": "^5.3.3"
1819
},
1920
"dependencies": {
20-
"supertokens-node": "latest"
21+
"@types/uuid": "^10.0.0",
22+
"supertokens-node": "file:../../../supertokens-node",
23+
"uuid": "^11.1.0"
2124
}
2225
}

stress-tests/src/oneMillionUsers/createUsers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import ThirdParty from "supertokens-node/recipe/thirdparty";
44

55
import { workInBatches, measureTime } from "../common/utils";
66

7-
const TOTAL_USERS = 1000000;
7+
const TOTAL_USERS = 10000;
88

99
const createEmailPasswordUsers = async () => {
1010
console.log(` Creating EmailPassword users...`);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import * as fs from 'fs';
2+
import { v4 as uuidv4 } from 'uuid';
3+
4+
const USERS_TO_GENERATE = 1000000;
5+
const USERS_PER_JSON = 10000;
6+
7+
const n = Math.floor(USERS_TO_GENERATE / USERS_PER_JSON);
8+
9+
const generatedEmails = new Set<string>();
10+
const generatedPhoneNumbers = new Set<string>();
11+
const generatedUserIds = new Set<string>();
12+
13+
interface LoginMethod {
14+
tenantIds: string[];
15+
email: string;
16+
recipeId: string;
17+
passwordHash?: string;
18+
hashingAlgorithm?: string;
19+
thirdPartyId?: string;
20+
thirdPartyUserId?: string;
21+
phoneNumber?: string;
22+
isVerified: boolean;
23+
isPrimary: boolean;
24+
timeJoinedInMSSinceEpoch: number;
25+
}
26+
27+
interface User {
28+
externalUserId: string;
29+
userRoles: Array<{
30+
role: string;
31+
tenantIds: string[];
32+
}>;
33+
loginMethods: LoginMethod[];
34+
}
35+
36+
function createEmailLoginMethod(email: string, tenantIds: string[]): LoginMethod {
37+
return {
38+
tenantIds,
39+
email,
40+
recipeId: "emailpassword",
41+
passwordHash: "$argon2d$v=19$m=12,t=3,p=1$aGI4enNvMmd0Zm0wMDAwMA$r6p7qbr6HD+8CD7sBi4HVw",
42+
hashingAlgorithm: "argon2",
43+
isVerified: true,
44+
isPrimary: false,
45+
timeJoinedInMSSinceEpoch: Math.floor(Math.random() * (Date.now() - (3 * 365 * 24 * 60 * 60 * 1000))) + (3 * 365 * 24 * 60 * 60 * 1000)
46+
};
47+
}
48+
49+
function createThirdPartyLoginMethod(email: string, tenantIds: string[]): LoginMethod {
50+
return {
51+
tenantIds,
52+
recipeId: "thirdparty",
53+
email,
54+
thirdPartyId: "google",
55+
thirdPartyUserId: String(hashCode(email)),
56+
isVerified: true,
57+
isPrimary: false,
58+
timeJoinedInMSSinceEpoch: Math.floor(Math.random() * (Date.now() - (3 * 365 * 24 * 60 * 60 * 1000))) + (3 * 365 * 24 * 60 * 60 * 1000)
59+
};
60+
}
61+
62+
function createPasswordlessLoginMethod(email: string, tenantIds: string[], phoneNumber: string): LoginMethod {
63+
return {
64+
tenantIds,
65+
email,
66+
recipeId: "passwordless",
67+
phoneNumber,
68+
isVerified: true,
69+
isPrimary: false,
70+
timeJoinedInMSSinceEpoch: Math.floor(Math.random() * (Date.now() - (3 * 365 * 24 * 60 * 60 * 1000))) + (3 * 365 * 24 * 60 * 60 * 1000)
71+
};
72+
}
73+
74+
function hashCode(str: string): number {
75+
let hash = 0;
76+
for (let i = 0; i < str.length; i++) {
77+
const char = str.charCodeAt(i);
78+
hash = ((hash << 5) - hash) + char;
79+
hash = hash & hash;
80+
}
81+
return hash;
82+
}
83+
84+
function generateRandomString(length: number, chars: string): string {
85+
let result = '';
86+
for (let i = 0; i < length; i++) {
87+
result += chars.charAt(Math.floor(Math.random() * chars.length));
88+
}
89+
return result;
90+
}
91+
92+
function generateRandomEmail(): string {
93+
return `${generateRandomString(24, 'abcdefghijklmnopqrstuvwxyz')}@example.com`;
94+
}
95+
96+
function generateRandomPhoneNumber(): string {
97+
return `+91${generateRandomString(10, '0123456789')}`;
98+
}
99+
100+
function genUser(): User {
101+
const user: User = {
102+
externalUserId: '',
103+
userRoles: [
104+
{ role: "role1", tenantIds: ["public"] },
105+
{ role: "role2", tenantIds: ["public"] }
106+
],
107+
loginMethods: []
108+
};
109+
110+
let userId = uuidv4();
111+
while (generatedUserIds.has(userId)) {
112+
userId = uuidv4();
113+
}
114+
generatedUserIds.add(userId);
115+
user.externalUserId = userId;
116+
117+
const tenantIds = ["public"];
118+
119+
let email = generateRandomEmail();
120+
while (generatedEmails.has(email)) {
121+
email = generateRandomEmail();
122+
}
123+
generatedEmails.add(email);
124+
125+
const loginMethods: LoginMethod[] = [];
126+
127+
// Always add email login method
128+
loginMethods.push(createEmailLoginMethod(email, tenantIds));
129+
130+
// 50% chance to add third party login
131+
if (Math.random() < 0.5) {
132+
loginMethods.push(createThirdPartyLoginMethod(email, tenantIds));
133+
}
134+
135+
// 50% chance to add passwordless login
136+
if (Math.random() < 0.5) {
137+
let phoneNumber = generateRandomPhoneNumber();
138+
while (generatedPhoneNumbers.has(phoneNumber)) {
139+
phoneNumber = generateRandomPhoneNumber();
140+
}
141+
generatedPhoneNumbers.add(phoneNumber);
142+
loginMethods.push(createPasswordlessLoginMethod(email, tenantIds, phoneNumber));
143+
}
144+
145+
// If no methods were added, randomly add one
146+
if (loginMethods.length === 0) {
147+
const methodNumber = Math.floor(Math.random() * 3);
148+
if (methodNumber === 0) {
149+
loginMethods.push(createEmailLoginMethod(email, tenantIds));
150+
} else if (methodNumber === 1) {
151+
loginMethods.push(createThirdPartyLoginMethod(email, tenantIds));
152+
} else {
153+
let phoneNumber = generateRandomPhoneNumber();
154+
while (generatedPhoneNumbers.has(phoneNumber)) {
155+
phoneNumber = generateRandomPhoneNumber();
156+
}
157+
generatedPhoneNumbers.add(phoneNumber);
158+
loginMethods.push(createPasswordlessLoginMethod(email, tenantIds, phoneNumber));
159+
}
160+
}
161+
162+
loginMethods[Math.floor(Math.random() * loginMethods.length)].isPrimary = true;
163+
164+
user.loginMethods = loginMethods;
165+
return user;
166+
}
167+
168+
// Create users directory if it doesn't exist
169+
if (!fs.existsSync('users')) {
170+
fs.mkdirSync('users');
171+
}
172+
173+
for (let i = 0; i < n; i++) {
174+
console.log(`Generating ${USERS_PER_JSON} users for ${i}`);
175+
const users: User[] = [];
176+
for (let j = 0; j < USERS_PER_JSON; j++) {
177+
users.push(genUser());
178+
}
179+
fs.writeFileSync(`users/users-${i.toString().padStart(4, '0')}.json`, JSON.stringify({ users }, null, 2));
180+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import fs from 'fs';
2+
import { formatTime, measureTime } from '../common/utils';
3+
4+
export const importMillionUsers = async (deployment: any) => {
5+
console.log("\n\n0. Importing one million users");
6+
7+
// Create roles
8+
await fetch(`${deployment.core_url}/recipe/role`, {
9+
method: "PUT",
10+
headers: {
11+
"Content-Type": "application/json",
12+
"Api-Key": deployment.api_key
13+
},
14+
body: JSON.stringify({
15+
"role": "role1",
16+
"permissions": ["p1", "p2"]
17+
})
18+
});
19+
20+
await fetch(`${deployment.core_url}/recipe/role`, {
21+
method: "PUT",
22+
headers: {
23+
"Content-Type": "application/json",
24+
"Api-Key": deployment.api_key
25+
},
26+
body: JSON.stringify({
27+
"role": "role2",
28+
"permissions": ["p3", "p2"]
29+
})
30+
});
31+
32+
await measureTime("Loading users for bulk import", async () => {
33+
for (let i = 0; i < 100; i++) {
34+
const filename = `users/users-${i.toString().padStart(4, '0')}.json`;
35+
const fileData = fs.readFileSync(filename, 'utf8');
36+
const data = JSON.parse(fileData);
37+
38+
await fetch(`${deployment.core_url}/bulk-import/users`, {
39+
method: "POST",
40+
headers: {
41+
"Content-Type": "application/json",
42+
"Api-Key": deployment.api_key
43+
},
44+
body: JSON.stringify(data)
45+
});
46+
}
47+
})
48+
49+
let lastCount = 1000000;
50+
let st = Date.now();
51+
let lastTime = st;
52+
while (true) {
53+
await new Promise(resolve => setTimeout(resolve, 5000));
54+
const response = await fetch(`${deployment.core_url}/bulk-import/users/count`, {
55+
headers: {
56+
"Api-Key": deployment.api_key
57+
}
58+
});
59+
const count: any = await response.json();
60+
console.log(` Progress: Time=${formatTime(Date.now() - st)}, UsersLeft=${count.count}, Rate=${((lastCount - count.count) / (Date.now() - lastTime)).toFixed(1)}`);
61+
62+
if (count.count === 0) {
63+
break;
64+
}
65+
66+
lastCount = count.count;
67+
lastTime = Date.now();
68+
}
69+
}

stress-tests/src/oneMillionUsers/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { doAccountLinking } from "./accountLinking";
1212
import { createUserIdMappings } from "./createUserIdMappings";
1313
import { addRoles } from "./addRoles";
1414
import { createSessions } from "./createSessions";
15+
import { importMillionUsers } from "./importMillionUsers";
1516

1617
function stInit(connectionURI: string, apiKey: string) {
1718
SuperTokens.init({
@@ -59,6 +60,8 @@ async function main() {
5960
try {
6061
stInit(deployment.core_url, deployment.api_key);
6162
await setupLicense(deployment.core_url, deployment.api_key);
63+
// 0. Import one million users
64+
await importMillionUsers(deployment);
6265

6366
// 1. Create one million users
6467
const users = await createUsers();

0 commit comments

Comments
 (0)