Skip to content

Commit

Permalink
Merge pull request #9 from Tiketeer/feat/DEV-190
Browse files Browse the repository at this point in the history
[DEV-190] 로드테스트 반복 스크립트 작성 (PLOCK)
  • Loading branch information
claycat authored Apr 22, 2024
2 parents 605b7cb + 9d51e4b commit 3b633ec
Show file tree
Hide file tree
Showing 9 changed files with 212 additions and 105 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@

@Service
public class CleanUpUseCase {
private MemberRepository memberRepository;
private PurchaseRepository purchaseRepository;
private TicketRepository ticketRepository;
private TicketingRepository ticketingRepository;
private final MemberRepository memberRepository;
private final PurchaseRepository purchaseRepository;
private final TicketRepository ticketRepository;
private final TicketingRepository ticketingRepository;

@Autowired
public CleanUpUseCase(MemberRepository memberRepository, PurchaseRepository purchaseRepository,
Expand Down
16 changes: 16 additions & 0 deletions stress-test/cleanup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash

# Endpoint and resource
url="localhost:4000/api/stress-test"

# Send DELETE request
response=$(curl -s -X DELETE "$url" -w "%{http_code}")

http_status=$(echo "$response" | tail -n1)

# Check if the DELETE was successful
if [ "$http_status" -eq 200 ]; then
echo "Delete successful."
else
echo "Failed to delete resource. HTTP Status: $http_status"
fi
19 changes: 19 additions & 0 deletions stress-test/docker-compose.stress-k6-only.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
version: "3.8"

services:
k6:
image: grafana/k6
volumes:
- ./output:/output
- ./k6-scripts:/scripts
command: >
run
-e VSR=${VSR}
-e TICKETS=${TICKETS}
-e ITERATION=${ITERATION}
-e LOCKTYPE=${LOCKTYPE}
/scripts/stress.js
extra_hosts:
- "host.docker.internal:host-gateway"

# TODO: 서버, DB 서비스 함께 올리기
235 changes: 134 additions & 101 deletions stress-test/k6-scripts/stress.js
Original file line number Diff line number Diff line change
@@ -1,140 +1,173 @@
import { uuidv4 } from "https://jslib.k6.io/k6-utils/1.4.0/index.js";
import {uuidv4} from "https://jslib.k6.io/k6-utils/1.4.0/index.js";
import http from "k6/http";

import { check } from "k6";
import {check} from "k6";

const setupVar = {
userNum: 300, // 구매자 수 (vUser),
ticketStock: 50, // 티켓 재고
ticketPrice: 1000,
setupTimeout: "120s",
userNum: __ENV.VSR || 10, // 구매자 수 (vUser),
ticketStock: __ENV.TICKETS || 50, // 티켓 재고
backoff: __ENV.BACKOFF || 0,
retry: __ENV.RETRY || 0,
waitTime: __ENV.WAITTIME || 0,
leaseTime: __ENV.LEASETIME || 0,
iteration: __ENV.ITERATION || 1,
lockType: __ENV.LOCKTYPE || 'plock',
ticketPrice: 1000,
setupTimeout: "120s",
};

export const options = {
setupTimeout: setupVar.setupTimeout,
scenarios: {
contacts: {
executor: "per-vu-iterations",
vus: setupVar.userNum,
iterations: 1,

setupTimeout: setupVar.setupTimeout,
scenarios: {
contacts: {
executor: "per-vu-iterations",
vus: setupVar.userNum,
iterations: 1,
},
},
},
};

const commonHeader = { "Content-Type": "application/json" };
const commonHeader = {"Content-Type": "application/json"};

const domain = "http://host.docker.internal:4000";

const commonPwd = "1q2w3e4r@@Q";

export function setup() {
// 멤버 생성 (1: seller, n: buyer)
const buyerEmailList = Array(setupVar.userNum)
.fill()
.map(() => `test-buyer-${uuidv4()}@test.com`);
// 멤버 생성 (1: seller, n: buyer)

const buyerEmailList = Array.from({length: setupVar.userNum}, () =>
`test-buyer-${uuidv4()}@test.com`
);

const sellerEmail = `test-seller-${uuidv4()}@test.com`;

wrapWithTimeLogging("유저 생성", () => {
createUsers({
emailList: [...buyerEmailList, sellerEmail],
const sellerEmail = `test-seller-${uuidv4()}@test.com`;

wrapWithTimeLogging("유저 생성", () => {
createUsers({
emailList: [...buyerEmailList, sellerEmail],
});
});
});

// 티케팅 생성
const now = new Date();
const saleStart = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
now.getHours() + 9,
now.getMinutes(),
now.getSeconds() + 5
);

const ticketingId = wrapWithTimeLogging("티케팅 생성", () => {
return createTicketing({
title: "Stress Test",
location: "서울",
category: "IT",
runningMinutes: 100,
price: setupVar.ticketPrice,
saleStart,
saleEnd: new Date(
new Date().setFullYear(now.getFullYear() + 1)
).toISOString(),
eventTime: new Date(
new Date().setFullYear(now.getFullYear() + 2)
).toISOString(),
stock: setupVar.ticketStock,
email: sellerEmail,

// 티케팅 생성
const now = new Date();
const saleStart = new Date(
now.getFullYear(),
now.getMonth(),
now.getDate(),
now.getHours() + 9,
now.getMinutes(),
now.getSeconds() + 5
);

const ticketingId = wrapWithTimeLogging("티케팅 생성", () => {
return createTicketing({
title: "Stress Test",
location: "서울",
category: "IT",
runningMinutes: 100,
price: setupVar.ticketPrice,
saleStart,
saleEnd: new Date(
new Date().setFullYear(now.getFullYear() + 1)
).toISOString(),
eventTime: new Date(
new Date().setFullYear(now.getFullYear() + 2)
).toISOString(),
stock: setupVar.ticketStock,
email: sellerEmail,
});
});
});

return { buyerEmailList, ticketingId };
return {buyerEmailList, ticketingId};
}

export default function ({ buyerEmailList, ticketingId }) {
const userCounter = __VU;
export default function ({buyerEmailList, ticketingId}) {
const userCounter = __VU;

const email = buyerEmailList[userCounter];
const email = buyerEmailList[userCounter - 1];

// TODO: 락 방법론 마다 분리된 EP 잘 찔러보기
puchase(ticketingId, 1, email);

// TODO: 락 방법론 마다 분리된 EP 잘 찔러보기
purchase(ticketingId, 1, email);
}

function createUsers(users) {
const response = http.post(domain + "/api/members", JSON.stringify(users), {
headers: commonHeader,
});
const response = http.post(domain + "/api/members", JSON.stringify(users), {
headers: commonHeader,
});

check(response, {
"status check after create user": (r) => r.status === 200,
});
check(response, {
"status check after create user": (r) => r.status === 200,
});
}

function createTicketing(ticketingMetadata) {
const response = http.post(
domain + "/api/ticketings",
JSON.stringify(ticketingMetadata),
{
headers: commonHeader,
cookies: { accessToken },
}
);

check(response, {
"status check after create ticketing": (r) => r.status === 201,
"ticketing id check after create ticketing": (r) => {
const ticketingId = r.json().data["ticketingId"];
return typeof ticketingId === "string" && ticketingId !== "";
},
});
const response = http.post(
domain + "/api/ticketings",
JSON.stringify(ticketingMetadata),
{
headers: commonHeader,
}
);


check(response, {
"status check after create ticketing": (r) => r.status === 201,
"ticketing id check after create ticketing": (r) => {
const ticketingId = r.json().data["ticketingId"];
return typeof ticketingId === "string" && ticketingId !== "";
},
});

return response.json("data")["ticketingId"];
return response.json("data")["ticketingId"];
}

function puchase(ticketingId, count, buyerEmail) {
const response = http.post(
domain + "/api/purchases",
JSON.stringify({
ticketingId,
count,
}),
{
headers: commonHeader,
}
);

check(response, {
"status check after create purchase": (r) => r.status === 201,
});
function purchase(ticketingId, count, buyerEmail) {

const response = http.post(
domain + "/api/purchases/p-lock",
JSON.stringify({
ticketingId,
count,
email: buyerEmail
}),
{
headers: commonHeader,
}
);


check(response, {
"status check after create purchase": (r) => r.status === 201,
});
}

function wrapWithTimeLogging(tag, callback) {
const start = new Date();
const result = callback();
const end = new Date();
console.log(`${tag}: 소요시간 - ${end.getTime() - start.getTime()}ms`);
return result;
const start = new Date();
const result = callback();
const end = new Date();
console.log(`${tag}: 소요시간 - ${end.getTime() - start.getTime()}ms`);
return result;
}

export function handleSummary(data) {

const filenameParts = [
setupVar.lockType,
`vus_${setupVar.userNum}`,
`tickets_${setupVar.ticketStock}`,
`backoff_${setupVar.backoff}`,
`retry_${setupVar.retry}`,
`waitTime_${setupVar.waitTime}`,
`leaseTime_${setupVar.leaseTime}`,
`${setupVar.iteration}`
];

const filename = `/output/${filenameParts.join('_')}.json`;

return {
[filename]: JSON.stringify(data)
};
}
Empty file added stress-test/output/.gitkeep
Empty file.
19 changes: 19 additions & 0 deletions stress-test/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash

declare -a vsrs=(30 60)
declare -a tickets=(30 60)

if [[ -z "$1" ]] || ! [[ "$1" =~ ^[0-9]+$ ]]; then
echo "Usage: $0 <number of iterations>"
exit 1
fi

for vsr in "${vsrs[@]}"; do
for ticket in "${tickets[@]}"; do
for ((i = 1; i <= $1; i++)); do
sh ./run_plock.sh ${vsr} ${ticket} ${i}
sleep 1
sh ./cleanup.sh
done
done
done
Empty file added stress-test/run_dlock.sh
Empty file.
Empty file added stress-test/run_olock.sh
Empty file.
20 changes: 20 additions & 0 deletions stress-test/run_plock.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#!/bin/bash
# ${1} : VSR
# ${2} : TICKETS
# ${3} : ITERATION
echo ${1} ${2} ${3}

echo "Run with VSR:${1} TICKETS:${2} ITERATION:${3}"

VSR=${1}
TICKETS=${2}
ITERATION=${3}
LOCKTYPE="dlock"

export VSR
export TICKETS
export ITERATION
export LOCKTYPE

docker compose -f docker-compose.stress-k6-only.yml up

0 comments on commit 3b633ec

Please sign in to comment.