Skip to content

Commit

Permalink
✨ 부하테스트를 위한 k6 script 작성 (#55)
Browse files Browse the repository at this point in the history
  • Loading branch information
yanggwangseong committed Dec 18, 2024
1 parent 1d975fb commit be16ca8
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 10 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: auto deploy
on:
push:
branches:
- feature/54-fake-dataset-seeding
- feature/55-load-test

jobs:
push_to_registry:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,4 @@ uploads/
Dockerfile.dev
docker-compose-dev.yml
docker-compose-dev.yml
grafana
52 changes: 52 additions & 0 deletions k6/scripts/articles-performance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { check, sleep } from "k6";
import http from "k6/http";
import { Trend } from "k6/metrics";

const dataReceivedTrend = new Trend("data_received_size", true);

export const options = {
scenarios: {
ramping_rps_test: {
executor: "ramping-arrival-rate",
startRate: 15,
timeUnit: "1s",
stages: [
{ duration: "1m", target: 25 },
{ duration: "1m", target: 35 },
{ duration: "1m", target: 45 },
],
preAllocatedVUs: 50,
maxVUs: 100,
},
},
thresholds: {
http_req_failed: ["rate<0.05"],
},
};

export default function () {
const BASE_URL = __ENV.BASE_URL || "http://localhost:4000";
const ACCESS_TOKEN = __ENV.ACCESS_TOKEN || "access_token";

const cursors = [12001, 23000, 30000, 40000, 50000];
const cursor = cursors[Math.floor(Math.random() * cursors.length)];
const limit = 10;

const articlesResponse = http.get(
`${BASE_URL}/articles?cursor=${cursor}&limit=${limit}`,
{
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`,
},
timeout: "60s",
},
);

dataReceivedTrend.add(articlesResponse.body.length);

check(articlesResponse, {
"articles status is 200": (r) => r.status === 200,
});

sleep(1);
}
54 changes: 54 additions & 0 deletions k6/scripts/participations-performance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { check, sleep } from "k6";
import http from "k6/http";
import { Trend } from "k6/metrics";

const dataReceivedTrend = new Trend("data_received_size", true);

export const options = {
scenarios: {
ramping_rps_test: {
executor: "ramping-arrival-rate",
startRate: 15,
timeUnit: "1s",
stages: [
{ duration: "1m", target: 20 },
{ duration: "1m", target: 25 },
{ duration: "1m", target: 30 },
],
preAllocatedVUs: 20,
maxVUs: 50,
},
},
thresholds: {
http_req_failed: ["rate<0.05"],
},
};

export default function () {
const BASE_URL = __ENV.BASE_URL || "http://localhost:4000";
const ACCESS_TOKEN = __ENV.ACCESS_TOKEN || "access_token";

const cursors = [12001, 23000, 30000, 40000, 50000];
const cursor = cursors[Math.floor(Math.random() * cursors.length)];
const limit = 10;

const articleIds = [23640, 12714, 11621, 43514];

const participationsResponse = http.get(
`${BASE_URL}/participations/articles/${articleIds[Math.floor(Math.random() * articleIds.length)]}?cursor=${cursor}&limit=${limit}`,
{
headers: {
Authorization: `Bearer ${ACCESS_TOKEN}`,
},
timeout: "60s",
},
);

dataReceivedTrend.add(participationsResponse.body.length);

check(participationsResponse, {
"participations status is 200": (r) => r.status === 200,
});

sleep(1);
}
48 changes: 48 additions & 0 deletions k6/scripts/signup-performance.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { check, sleep } from "k6";
import http from "k6/http";
import { Trend } from "k6/metrics";

const dataReceivedTrend = new Trend("data_received_size", true);

export const options = {
scenarios: {
ramping_rps_test: {
executor: "ramping-arrival-rate",
startRate: 15,
timeUnit: "1s",
stages: [
{ duration: "1m", target: 20 },
{ duration: "1m", target: 25 },
{ duration: "1m", target: 30 },
],
preAllocatedVUs: 20,
maxVUs: 50,
},
},
thresholds: {
http_req_failed: ["rate<0.05"],
},
};

export default function () {
const BASE_URL = __ENV.BASE_URL || "http://localhost:4000";

const timestamp = new Date().getTime();
const randomValue = Math.random().toString(36).substring(2, 15);
const uniqueId = `${timestamp}-${randomValue}`;

const signupResponse = http.post(`${BASE_URL}/sign-up`, {
email: `user-${uniqueId}@test.com`,
password: "123456",
name: `user-${uniqueId}`,
nickname: `nickname-${uniqueId}`,
});

dataReceivedTrend.add(signupResponse.body.length);

check(signupResponse, {
"signup status is 201": (r) => r.status === 201,
});

sleep(1);
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
"prepare": "husky",
"--------------------------------------------": "",
"seed": "cross-env NODE_ENV=production ts-node -r tsconfig-paths/register ./node_modules/typeorm-extension/bin/cli.cjs -d ./data-source.ts seed:run --name",
"seed:dev": "cross-env NODE_ENV=development ts-node -r tsconfig-paths/register ./node_modules/typeorm-extension/bin/cli.cjs -d ./data-source.ts seed:run --name"
"seed:dev": "cross-env NODE_ENV=development ts-node -r tsconfig-paths/register ./node_modules/typeorm-extension/bin/cli.cjs -d ./data-source.ts seed:run --name",
"-------------------------------------------": "",
"k6:run:articles": "docker-compose -f docker-compose-dev.yml run --rm k6 run /scripts/articles-performance.js",
"k6:run:participations": "docker-compose -f docker-compose-dev.yml run --rm k6 run /scripts/participations-performance.js"
},
"dependencies": {
"@nestjs-modules/mailer": "^2.0.2",
Expand Down
9 changes: 9 additions & 0 deletions src/common/typeorm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ export const TypeOrmModuleOptions = {
password: configService.get(ENV_DB_PASSWORD) || "test",
entities: [path.resolve(process.cwd(), "dist/**/*.entity.{js,ts}")],
synchronize: configService.get<boolean>(ENV_DB_SYNCHRONIZE) || true,
extra: {
connectionLimit: 30,
queueLimit: 0,
waitForConnections: true,
},
// 커넥션 풀 사이즈 설정
poolSize: 30,
connectTimeout: 120000,
logging: true,
};

return option;
Expand Down
7 changes: 2 additions & 5 deletions src/services/articles.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,9 @@ export class ArticlesService {
.innerJoin("article.category", "category")
.innerJoin("article.region", "region")
.innerJoin("article.district", "district")
.where(cursor ? "article.id < :cursor" : "1=1", { cursor })
.orderBy("article.id", "DESC")
.take(limit + 1);

if (cursor) {
query.where("article.id < :cursor", { cursor });
}
.limit(limit + 1);

const articles = await query.getRawMany();

Expand Down
2 changes: 1 addition & 1 deletion src/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class AuthService {
verificationCode,
);

await this.mailsService.sendVerificationEmail(
void this.mailsService.sendVerificationEmail(
newMember.email,
verificationCode,
);
Expand Down
7 changes: 5 additions & 2 deletions src/services/participations.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@ export class ParticipationsService {
.where("participation.articleId = :articleId", {
articleId,
})
.andWhere("participation.status = :status", {
status: ParticipationStatus.ACTIVE,
})
.orderBy("participation.id", "ASC")
.take(limit + 1);
.limit(limit + 1);

if (cursor) {
query.andWhere("participation.id > :cursor", { cursor });
}

const participations = await query.getMany();
const participations = await query.getRawMany();
const hasNextPage = participations.length > limit;
const results = hasNextPage
? participations.slice(0, -1)
Expand Down

0 comments on commit be16ca8

Please sign in to comment.