Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions load-tests/calendar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import http from 'k6/http';
import { check, sleep } from 'k6';
import {
BASE_URL,
VU_OPTIONS,
authHeaders,
createGuest,
getCalendarParams,
} from './common.js';

export const options = {
...VU_OPTIONS,
duration: '1m',
};

export default function () {
const { res: guestRes } = createGuest();

const guestOk = check(guestRes, {
'guest created': (r) => r.status === 201 || r.status === 200,
'guest has token': (r) => !!r.json('data.auth.accessToken'),
});

if (!guestOk) {
return;
}

const headers = authHeaders(guestRes.json('data.auth.accessToken'));
const { year, month, selectedDate } = getCalendarParams();

const statusRes = http.get(`${BASE_URL}/api/v1/users/status`, { headers });
check(statusRes, { 'user status 200': (r) => r.status === 200 });

const calRes = http.get(
`${BASE_URL}/api/v1/calendars/main?year=${year}&month=${month}&selectedDate=${selectedDate}`,
{ headers },
);
check(calRes, { 'calendar main 200': (r) => r.status === 200 });

const dateEventsRes = http.get(
`${BASE_URL}/api/v1/calendars/dates/${selectedDate}/events`,
{ headers },
);
check(dateEventsRes, { 'date events 200': (r) => r.status === 200 });

const labelsRes = http.get(`${BASE_URL}/api/v1/labels`, { headers });
check(labelsRes, { 'labels 200': (r) => r.status === 200 });

sleep(1);
}
40 changes: 40 additions & 0 deletions load-tests/common.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import http from 'k6/http';
import { uuidv4 } from 'https://jslib.k6.io/k6-utils/1.4.0/index.js';

export const BASE_URL = __ENV.BASE_URL || 'http://localhost:8080';

export const VU_OPTIONS = {
vus: 10,
duration: '30s',
thresholds: {
http_req_duration: ['p(95)<500'],
http_req_failed: ['rate<0.01'],
},
};

export function createGuest() {
const guestId = uuidv4();
const res = http.post(
`${BASE_URL}/api/v1/guests`,
JSON.stringify({ guestId }),
{ headers: { 'Content-Type': 'application/json' } },
);

return { res, guestId };
}

export function authHeaders(accessToken) {
return {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
};
}

export function getCalendarParams() {
const now = new Date();
return {
year: now.getFullYear(),
month: now.getMonth() + 1,
selectedDate: now.toISOString().slice(0, 10),
};
}
31 changes: 31 additions & 0 deletions load-tests/guest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import http from 'k6/http';
import { check, sleep } from 'k6';
import { BASE_URL, VU_OPTIONS, createGuest } from './common.js';

export const options = VU_OPTIONS;

export function setup() {
const res = http.get(`${BASE_URL}/health`);
if (res.status !== 200) {
throw new Error(
`백엔드에 연결할 수 없습니다 (${BASE_URL}/health → status=${res.status}). ` +
'Spring Boot가 실행 중인지 확인하세요.',
);
}
}

export default function () {
const { res } = createGuest();

check(res, {
'guest created or reconnected': (r) => {
if (r.status !== 201 && r.status !== 200) {
console.error(`guest 실패: status=${r.status}, body=${r.body}`);
}
return r.status === 201 || r.status === 200;
},
'guest response has access token': (r) => !!r.json('data.auth.accessToken'),
});

sleep(1);
}
16 changes: 16 additions & 0 deletions load-tests/health.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import http from 'k6/http';
import { check, sleep } from 'k6';
import { BASE_URL, VU_OPTIONS } from './common.js';

export const options = VU_OPTIONS;

export default function () {
const res = http.get(`${BASE_URL}/health`);

check(res, {
'health status is 200': (r) => r.status === 200,
'health overall UP': (r) => r.json('status') === 'UP',
});

sleep(1);
}
4 changes: 4 additions & 0 deletions src/main/resources/application-local.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ spring:
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 25
# 로컬 환경에 부 DB가 없으면 주 DB와 동일한 곳을 바라본다.
# 부 DB 라우팅 동작 자체를 로컬에서 검증하고 싶다면 DB_REPLICA_URL을 별도로 지정한다.
read:
url: ${DB_REPLICA_URL:${DB_URL:jdbc:postgresql://localhost:5432/tryna}}
username: ${DB_REPLICA_USERNAME:${DB_USERNAME:postgres}}
password: ${DB_REPLICA_PASSWORD:${DB_PASSWORD:postgres}}
driver-class-name: org.postgresql.Driver
hikari:
maximum-pool-size: 25
jpa:
hibernate:
# Flyway가 먼저 마이그레이션을 적용한 뒤 실행된다. 엔티티 변경 시 update가 로컬 스키마를
Expand Down
Loading