채팅을 전송 시간 기준 관리자에게 알람이 전송되어 화면에 나타날 때까지의 시간을 측정했습니다.
-> 평균 1790ms
채팅을 전송 시간 기준 관리자에게 알람이 전송되어 화면에 나타날 때까지의 시간을 측정했습니다.
-> 평균 1050ms
채팅을 전송 시간 기준 관리자에게 알람이 전송되어 화면에 나타날 때까지의 시간을 측정했습니다.
-> 평균 50ms
첫 번째 단계에서는 가상 사용자(VU) 수가 10초에 걸쳐 점차 300명으로 증가한다. 이는 사용자가 천천히 시스템에 진입하는 램프업 기간을 시뮬레이션한다.
두 번째 단계에서는 사용자 수가 30초 동안 300명에서 10,000명으로 빠르게 증가한다. 이는 시스템이 매우 높은 부하로 테스트되는 스트레스 테스트 기간이다. 시스템은 이 시간 동안 최대 사용자 수를 처리해야 한다.
최종 단계에서는 10초 만에 사용자 수가 10,000명에서 0명으로 감소한다. 이는 테스트가 종료됨에 따라 사용자가 점차 시스템을 떠나는 것을 시뮬레이션한다.
import ws from 'k6/ws';
import { check } from 'k6';
import { sleep } from 'k6';
export const options = {
stages: [
{ duration: '10s', target: 300 },
{ duration: '30s', target: 10000 },
{ duration: '10s', target: 0 },
],
};
const chatRoomNo = 8;
export default function () {
const url = 'ws://localhost:8080/chat';
const res = ws.connect(url, null, function (socket) {
// Handle WebSocket connection opened
socket.on('open', () => {
console.log('WebSocket connection opened');
// 채팅 메시지 보내기
const message = {
type: 'CHAT',
sender: `won11111`, // Unique sender identifier
message: `Hello`, // Example message
roomId: chatRoomNo,
createdAt: new Date().toLocaleTimeString(),
};
socket.send(JSON.stringify(message));
// 일정 시간 후 WebSocket 닫음
//setTimeout(() => {
// console.log('Closing WebSocket connection');
// socket.close(); // 명시적으로 WebSocket 종료
//}, 2000); // 2초 후 연결 종료
});
// 서버로부터 받은 메시지 처리
socket.on('message', (data) => {
const messageData = JSON.parse(data);
console.log('Received message: ', messageData);
//if (messageData.type === 'CHAT') {
// console.log('Chat message received: ', messageData.message);
//} else {
// console.log('Other message type received: ', messageData);
//}
});
socket.on('close', () => {
console.log('WebSocket connection closed');
});
// WebSocket 오류 처리
socket.on('error', (e) => {
console.error('WebSocket error:', e);
});
});
// WebSocket 연결 상태 확인
check(res, { 'WebSocket connection status is 101': (r) => r && r.status === 101 });
// Additional sleep to ensure graceful closure
sleep(3); // Wait for a second after closing to ensure the server has time to process the close event
}
import ws from 'k6/ws';
import { check } from 'k6';
import { sleep } from 'k6';
// 부하 테스트 설정
export const options = {
stages: [
{ duration: '10s', target: 300 }, // 300명까지 증가
{ duration: '30s', target: 10000 }, // 10,000명으로 유지
{ duration: '10s', target: 0 }, // 종료
],
};
const chatRoomNo = 8; // 예시로 사용되는 chatRoom 번호
const userId = 'won11111'; // 테스트에서 사용할 사용자 ID
const destination = `/pub/chat/message/${chatRoomNo}`; // STOMP publish 대상
const subscribeDestination = `/sub/chat/${chatRoomNo}`; // STOMP 구독 대상
export default function () {
const url = 'ws://localhost:8080/stomp/chat'; // 서버 WebSocket URL
const res = ws.connect(url, function (socket) {
socket.on('open', () => {
console.log('WebSocket connection opened');
// STOMP CONNECT 프레임 전송
const connectFrame = 'CONNECT\naccept-version:1.2\nheart-beat:10000,10000\n\n\0';
socket.send(connectFrame);
// STOMP 연결 이후 SUBSCRIBE 프레임
socket.on('message', (data) => {
//console.log('Received:', data);
// STOMP CONNECTED 프레임을 받으면 SUBSCRIBE 시작
if (data.includes('CONNECTED')) {
const subscribeFrame = `SUBSCRIBE\ndestination:${subscribeDestination}\nid:sub-${chatRoomNo}\n\n\0`;
socket.send(subscribeFrame);
// 채팅 메시지 전송
const sendChatMessage = () => {
const message = {
sender: userId,
message: "Hello STOMP!",
//createdAt: new Date().toLocaleTimeString(),
};
const sendFrame = `SEND\ndestination:${destination}\ncontent-type:application/json\n\n${JSON.stringify(message)}\0`;
socket.send(sendFrame);
//console.log('Sent chat message:', JSON.stringify(message));
//sleep(1);
};
sendChatMessage();
}
// 서버로부터 받은 메시지 출력
if (data.includes('MESSAGE')) {
//console.log('Chat message received:', data);
}
});
});
// WebSocket 닫힘 처리
socket.on('close', () => {
console.log('WebSocket connection closed');
});
// WebSocket 오류 처리
socket.on('error', (e) => {
console.error('WebSocket error:', e);
});
});
// 연결 상태 확인
check(res, { 'WebSocket connection status is 101': (r) => r && r.status === 101 });
}
k6 run websocket-script.js
k6 run stomp-script.js
STOMP는 메시지 헤더, 프레임 형식, 명령어(Connect, Subscribe, Send 등) 등 다양한 형식화된 구조가 추가되어 WebSocket에 비해 더 많은 메모리와 CPU 리소스를 필요로 합니다. STOMP 메시지는 단순한 데이터 전송이 아니라 메시지 프레임 안에 메타데이터와 제어 정보가 포함됩니다. 따라서 STOMP 메시지를 수신하면 프레임을 파싱하고 처리하는 과정에서 더 많은 연산이 필요합니다. WebSocket은 프로토콜 수준에서 메시지 처리를 단순화하고 클라이언트와 서버가 직접적으로 데이터를 교환합니다. 메시지 포맷이 단순하므로 CPU나 메모리를 사용하는 양이 적습니다.
STOMP는 텍스트 기반 프로토콜로 메시지를 주고받을 때 헤더와 구조화된 프레임을 처리해야 하므로 메모리 할당과 파싱 비용이 증가하게 됩니다. 이 과정에서 JVM의 Heap Used가 더 커지고, 메시지를 처리하는 동안 CPU 부하도 증가할 수 있습니다. 그러나,
STOMP 프로토콜은 여러 클라이언트가 동일한 주제를 구독하는 상황에서 더 높은 처리량을 제공하여 메시지가 채팅방에서와 같이 여러 수신자에게 동시에 브로드캐스팅될 수 있습니다. 또한 STOMP는 복잡한 작업을 브로커에 오프로드하여 구현을 단순화하는 장점이 있습니다.
여러 클라이언트가 채팅방을 구독하는 상황을 예로 들면,
WebSocket을 사용하면 다음을 수행하기 위해 수동으로 구현해야 합니다.
- 어떤 클라이언트가 어떤 채팅방에 가입되어 있는지 관리
- 메시지가 올바른 클라이언트에게 전달되는지 확인
- 중복이나 손실을 방지하기 위해 메시지를 확인
반면, STOMP를 사용하면 이 로직의 대부분이 자동으로 처리됩니다.- 클라이언트는 간단한 'SUBSCRIBE' 프레임을 통해 채팅방을 구독할 수 있습니다.
- 메시지는 브로커에 의해 올바른 가입자에게 자동으로 라우팅됩니다.
- 승인은 ACK/NAK 프레임을 사용하여 처리되므로 메시지가 안정적으로 전달됩니다.
STOMP는 높은 처리량, 신뢰할 수 있는 메시지 전달 및 고급 메시징 기능이 필요한 애플리케이션에 더욱 편리하고 확장 가능한 솔루션을 제공합니다. 게시/구독, 메시지 확인, 구독 관리 등이 있습니다. STOMP는 복잡한 작업을 메시지 브로커에 오프로드함으로써 많은 엔터프라이즈 수준 사용 사례에서 더 나은 처리량을 제공할 수 있으므로 편의성과 성능이 모두 필요한 애플리케이션에 이상적인 선택이 될 수 있습니다.