각 실시간 통신을 스프링 환경에서 구현하는 방법과 테스트 로직을 앞서 구현하였다. 이제는 각 통신 방법을 시나리오에 따라 테스트하고 결과를 분석해보겠다.
테스트 시나리오를 구성할 때 몇가지 상황을 고려하였다.
- 서비스가 시작되고 사용자들이 진입하는 상황 : 정상 진입 확인
- 사용자 최고치(최대 처리량)에서 서비스 요청 상황 : 최대 부하 확인
- 사용자들의 요청이 완료되고 사용자 수가 줄어드면서의 상황 : 회복성 확인
총 테스트 시간을 1분으로 설정했고 위 3가지 상황을 고려하여 3가지 구간으로 나눠서 시나리오를 구성했다.
부하 테스트를 확인하기 위해 고려했던 테스트 툴은 Jmeter, k6 총 3가지였다.
테스트를 위해 처음 선택했던 툴은 Jmeter였다. 단순한 GUI에서 테스트를 하는 것이 편리해보였고 러닝 커브가 적다고 생각했다. 실제로 Jmeter를 통해 단일 상황에 대한 테스트는 정상 작동을 확인했다.
Polling에 대한 점진적 사용자 증가 테스트
하지만 가장 큰 문제에 직면하였는데 그것은 WebSocket이였다.
- JMeter는 기본적으로 WebSocket 프로토콜을 직접적으로 지원하지 않는다. 외부 플러그인을 추가하여 테스트해야 하는데 해당 플러그인이 공식 플러그인이 아니라 신뢰성이 부족했다.
- 또한 WebSocket 커넥션이 연결되고 통신되는 실시간 과정에 대한 분석이 아쉽다.
두번째로 고려하고 테스트 툴로 선택한건 k6였다. k6를 선택한 이유는 다음과 같다.
- 간단한 스크립트 작성을 통한 테스트 시나리오 수행 가능
- WebSocket을 기본적으로 지원하여 추가 플러그인 없이 연결 설정과 관리 가능 → STOMP는 WebSocket연결을 통해 테스트 가능
- k6에서 기본적으로 WebSocket 관련 테스트 기능을 지원하는 것을 알 수 있다 !
추가적으로 프로메테우스를 추가하여 Spring Server를 모니터링하였다. CPU 사용량과 메모리를 통신이 수행되는 동안 분석하기 위한 것이었다.
function shortPollingTest() {
http.get(`http://localhost:8080/cur/${randomInt}`);
}
export default function () {
shortPollingTest();
sleep(3);
}
// Connection 초기화 함수
function initializeLongPollingConnection() {
if (longPollingConnection) {
return longPollingConnection;
}
const connection = http.get(`${baseUrl}/location/long/${randomInt}`, {
headers: {Accept: 'text/event-stream'},
tags: {type: 'longPolling'}
});
check(connection, {'Long Polling Connection Status is 200': (r) => r.status === 200});
return connection;
}
// 커넥션을 초기화(정보 가져 오기) + 자신의 위치를 알리고(event 발생) + 그룹원의 위치 정보 받아오기
export function longPollingTest() {
initializeLongPollingConnection();
const notifyResponse = http.post(`${baseUrl}/location/long/${randomInt}/notify`);
check(notifyResponse, {'Long Polling Notify Status is 200': (r) => r.status === 200});
const url = `${baseUrl}/location/long/${randomInt}`;
const response = http.get(url);
check(response, {'Long Polling Post Status is 200': (r) => r.status === 200});
}
function initializeWebSocketConnection() {
if (websocketConnection) {
return websocketConnection;
}
const connection = ws.connect(wsUrl, function (socket) {
socket.on('open', function () {
socket.send('Hello from k6 WebSocket!');
});
socket.on('message', function (message) {
});
socket.on('close', function () {
});
});
websocketConnection = connection;
return connection;
}
export function websocketTest() {
initializeWebSocketConnection();
ws.connect(wsUrl, function (socket) {
socket.on('open', function () {
socket.send('Hello from k6!');
});
});
}
function initializeStompClient(callback) {
if (stompClient) {
callback(stompClient); // 이미 stompClient가 존재하면 콜백 호출
return;
}
ws.connect(stompUrl, {}, function (socket) {
socket.on('open', function () {
console.log('WebSocket connection opened');
// STOMP CONNECT 메시지를 전송합니다.
socket.send('CONNECT\naccept-version:1.0,1.1,2.0\n\n\x00\n');
// STOMP SUBSCRIBE 메시지를 전송합니다.
const subscribeMessage = `SUBSCRIBE\nid:${uuid()}\ndestination:/sub/location/${randomInt}\n\n\x00\n`;
socket.send(subscribeMessage);
// WebSocket 연결이 열렸을 때의 콜백 함수 호출
callback(socket);
});
socket.on('message', function (message) {});
socket.on('close', function () {});
stompClient = socket; // WebSocket 객체를 stompClient에 할당
});
}
export function stompTest() {
initializeStompClient(function (socket) {
const stompMessage = `SEND\ndestination:/pub/share/${randomInt}\n\n${JSON.stringify({ content: 'Hi from k6 STOMP!' })}\x00\n`;
// WebSocket 연결이 열리면 STOMP 메시지를 전송합니다.
socket.send(stompMessage);
});
}
import {check, sleep} from 'k6';
import http from 'k6/http';
import ws from 'k6/ws';
let longPollingConnection = null;
let sseConnection = null;
let websocketConnection = null;
let stompClient = null;
export let options = {
stages: [
{duration: '20s', target: 300},
{duration: '30s', target: 10000},
{duration: '10s', target: 0},
],
};
const baseUrl = 'http://localhost:8080';
const wsUrl = 'ws://localhost:8080/ws';
const stompUrl = 'ws://localhost:8080/location';
const randomInt = getRandomInt();
function getRandomInt() {
return Math.floor(Math.random() * 10) + 1;
}
function shortPollingTest() {
http.get(`http://localhost:8080/cur/${randomInt}`);
}
function initializeLongPollingConnection() {
if (longPollingConnection) {
return longPollingConnection;
}
const connection = http.get(`${baseUrl}/location/long/${randomInt}`, {
headers: {Accept: 'text/event-stream'},
tags: {type: 'longPolling'}
});
check(connection, {'Long Polling Connection Status is 200': (r) => r.status === 200});
return connection;
}
function initializeSSEConnection() {
if (sseConnection) {
return sseConnection;
}
const connection = http.get(`${baseUrl}/location/sse/connect`, {
headers: {Accept: 'text/event-stream'},
tags: {type: 'sse'}
});
check(connection, {'SSE Connection Status is 200': (r) => r.status === 200});
return connection;
}
function initializeWebSocketConnection() {
if (websocketConnection) {
return websocketConnection;
}
const connection = ws.connect(wsUrl, function (socket) {
socket.on('open', function () {
socket.send('Hello from k6 WebSocket!');
});
socket.on('message', function (message) {
});
socket.on('close', function () {
});
});
websocketConnection = connection;
return connection;
}
function initializeStompClient(callback) {
if (stompClient) {
callback(stompClient); // 이미 stompClient가 존재하면 콜백 호출
return;
}
ws.connect(stompUrl, {}, function (socket) {
socket.on('open', function () {
console.log('WebSocket connection opened');
// STOMP CONNECT 메시지를 전송합니다.
socket.send('CONNECT\naccept-version:1.0,1.1,2.0\n\n\x00\n');
// STOMP SUBSCRIBE 메시지를 전송합니다.
const subscribeMessage = `SUBSCRIBE\nid:${uuid()}\ndestination:/sub/location/${randomInt}\n\n\x00\n`;
socket.send(subscribeMessage);
// WebSocket 연결이 열렸을 때의 콜백 함수 호출
callback(socket);
});
socket.on('message', function (message) {});
socket.on('close', function () {});
stompClient = socket; // WebSocket 객체를 stompClient에 할당
});
}
function uuid() {
const pattern = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
return pattern.replace(/[xy]/g, function(c) {
const r = Math.random() * 16 | 0;
const v = c === 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
export default function () {
shortPollingTest();
longPollingTest();
sseTest();
websocketTest();
stompTest();
sleep(3);
}
export function longPollingTest() {
initializeLongPollingConnection();
const notifyResponse = http.post(`${baseUrl}/location/long/${randomInt}/notify`);
check(notifyResponse, {'Long Polling Notify Status is 200': (r) => r.status === 200});
const url = `${baseUrl}/location/long/${randomInt}`;
const response = http.get(url);
check(response, {'Long Polling Post Status is 200': (r) => r.status === 200});
}
export function sseTest() {
initializeSSEConnection();
const postResponse = http.post(`${baseUrl}/location/sse/share`);
check(postResponse, {'SSE Share Status is 200': (r) => r.status === 200});
}
export function websocketTest() {
initializeWebSocketConnection();
ws.connect(wsUrl, function (socket) {
socket.on('open', function () {
socket.send('Hello from k6!');
});
});
}
export function stompTest() {
initializeStompClient(function (socket) {
const stompMessage = `SEND\ndestination:/pub/share/${randomInt}\n\n${JSON.stringify({ content: 'Hi from k6 STOMP!' })}\x00\n`;
// WebSocket 연결이 열리면 STOMP 메시지를 전송합니다.
socket.send(stompMessage);
});
}
1,2,3 테스트 시나리오에 있어 2(사용자가 최대로 진입하고 통신하는 시점)에서 사용자 수를 1000, 5000, 10000명으로 설정하고 3가지 경우를 각 통신 방법으로 테스트하였다.
data_received..............: 3.0 MB 48 kB/s
data_sent..................: 822 kB 13 kB/s
http_req_blocked...........: avg=64.42µs min=0s med=5µs max=7ms p(90)=257.3µs p(95)=550µs
http_req_connecting........: avg=46.11µs min=0s med=0s max=6.85ms p(90)=207.3µs p(95)=428.14µs
http_req_duration..........: avg=2.42ms min=441µs med=1.51ms max=24.75ms p(90)=5.09ms p(95)=7.15ms
http_req_failed............: 100.00% ✓ 9658 ✗ 0
http_req_receiving.........: avg=67.93µs min=6µs med=44µs max=13.41ms p(90)=106µs p(95)=148µs
http_req_sending...........: avg=29.44µs min=2µs med=20µs max=2.11ms p(90)=54µs p(95)=76µs
http_req_tls_handshaking...: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...........: avg=2.32ms min=415µs med=1.43ms max=24.5ms p(90)=4.94ms p(95)=6.92ms
http_reqs..................: 9658 154.226693/s
iteration_duration.........: avg=3s min=3s med=3s max=3.02s p(90)=3s p(95)=3s
iterations.................: 9658 154.226693/s
vus........................: 16 min=15 max=998
vus_max....................: 1000 min=1000 max=1000
data_received..............: 12 MB 187 kB/s
data_sent..................: 3.3 MB 52 kB/s
http_req_blocked...........: avg=63.12µs min=0s med=3µs max=48.35ms p(90)=236µs p(95)=366µs
http_req_connecting........: avg=46.79µs min=0s med=0s max=47.81ms p(90)=191µs p(95)=284µs
http_req_duration..........: avg=8.01ms min=418µs med=1.07ms max=299.64ms p(90)=14.78ms p(95)=42.13ms
http_req_failed............: 100.00% ✓ 38255 ✗ 0
http_req_receiving.........: avg=110.55µs min=4µs med=23µs max=118.48ms p(90)=82µs p(95)=164µs
http_req_sending...........: avg=27.46µs min=1µs med=11µs max=74.37ms p(90)=43µs p(95)=64µs
http_req_tls_handshaking...: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...........: avg=7.87ms min=394µs med=1.01ms max=299.58ms p(90)=14.51ms p(95)=41.5ms
http_reqs..................: 38255 607.681958/s
iteration_duration.........: avg=3s min=3s med=3s max=3.29s p(90)=3.01s p(95)=3.04s
iterations.................: 38255 607.681958/s
vus........................: 2 min=2 max=4988
vus_max....................: 5000 min=5000 max=5000
data_received..............: 19 MB 237 kB/s
data_sent..................: 5.5 MB 67 kB/s
http_req_blocked...........: avg=64.85µs min=0s med=4µs max=162.66ms p(90)=185µs p(95)=288µs
http_req_connecting........: avg=36.13µs min=0s med=0s max=16.3ms p(90)=142µs p(95)=214µs
http_req_duration..........: avg=422.63ms min=392µs med=43.5ms max=3.6s p(90)=1.47s p(95)=1.94s
http_req_failed............: 100.00% ✓ 63081 ✗ 0
http_req_receiving.........: avg=504.35µs min=4µs med=28µs max=574.14ms p(90)=182µs p(95)=652µs
http_req_sending...........: avg=133.4µs min=1µs med=14µs max=183.77ms p(90)=64µs p(95)=134µs
http_req_tls_handshaking...: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...........: avg=421.99ms min=370µs med=42.99ms max=3.6s p(90)=1.46s p(95)=1.94s
http_reqs..................: 63081 770.071737/s
iteration_duration.........: avg=3.42s min=3s med=3.04s max=6.6s p(90)=4.47s p(95)=4.94s
iterations.................: 63081 770.071737/s
vus........................: 252 min=9 max=9999
vus_max....................: 10000 min=10000 max=10000
✗ Long Polling Connection Status is 200
↳ 66% — ✓ 1465 / ✗ 739
✓ Long Polling Post Status is 200
✓ Long Polling Notify Status is 200
checks.........................: 88.82% ✓ 5873 ✗ 739
data_received..................: 814 kB 9.7 kB/s
data_sent......................: 748 kB 8.9 kB/s
http_req_blocked...............: avg=100.35µs min=0s med=2µs max=13.23ms p(90)=471.8µs p(95)=727.89µs
http_req_connecting............: avg=77.43µs min=0s med=0s max=8.14ms p(90)=374µs p(95)=566µs
http_req_duration..............: avg=4.91s min=380µs med=5.21s max=11.14s p(90)=10.96s p(95)=10.98s
{ expected_response:true }...: avg=4.76s min=380µs med=2.02s max=11.14s p(90)=10.97s p(95)=10.98s
http_req_failed................: 11.17% ✓ 739 ✗ 5873
http_req_receiving.............: avg=250.88µs min=3µs med=17µs max=168.69ms p(90)=102.9µs p(95)=333.34µs
http_req_sending...............: avg=26.77µs min=1µs med=7µs max=4.82ms p(90)=75µs p(95)=109µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=4.91s min=358µs med=5.2s max=11.14s p(90)=10.96s p(95)=10.98s
http_reqs......................: 6612 78.426891/s
iteration_duration.............: avg=17.74s min=3.01s med=21.84s max=25.04s p(90)=22.96s p(95)=24.26s
iterations.....................: 2204 26.142297/s
vus............................: 6 min=6 max=1000
vus_max........................: 1000 min=1000 max=1000
✗ Long Polling Connection Status is 200
↳ 67% — ✓ 6512 / ✗ 3164
✗ Long Polling Post Status is 200
↳ 99% — ✓ 9675 / ✗ 1
✓ Long Polling Notify Status is 200
checks.........................: 89.09% ✓ 25863 ✗ 3165
data_received..................: 3.6 MB 43 kB/s
data_sent......................: 3.3 MB 40 kB/s
http_req_blocked...............: avg=100.22µs min=0s med=2µs max=119.83ms p(90)=361µs p(95)=510µs
http_req_connecting............: avg=76.03µs min=0s med=0s max=119.76ms p(90)=279µs p(95)=397µs
http_req_duration..............: avg=4.73s min=373µs med=3.56s max=11.44s p(90)=10.75s p(95)=10.9s
{ expected_response:true }...: avg=4.64s min=373µs med=345.14ms max=11.44s p(90)=10.78s p(95)=10.93s
http_req_failed................: 10.90% ✓ 3165 ✗ 25863
http_req_receiving.............: avg=546.6µs min=3µs med=19µs max=301.16ms p(90)=94µs p(95)=377µs
http_req_sending...............: avg=44.11µs min=1µs med=7µs max=18.33ms p(90)=63µs p(95)=93µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=4.73s min=362µs med=3.56s max=11.44s p(90)=10.75s p(95)=10.9s
http_reqs......................: 29028 349.110016/s
iteration_duration.............: avg=17.19s min=3s med=21.79s max=25.16s p(90)=22.51s p(95)=24.32s
iterations.....................: 9676 116.370005/s
vus............................: 336 min=12 max=5000
vus_max........................: 5000 min=5000 max=5000
checks.........................: 66.96% ✓ 38419 ✗ 18949
data_received..................: 6.9 MB 78 kB/s
data_sent......................: 6.7 MB 75 kB/s
http_req_blocked...............: avg=75.59µs min=0s med=2µs max=99.86ms p(90)=257µs p(95)=366µs
http_req_connecting............: avg=56.52µs min=0s med=0s max=97.54ms p(90)=203µs p(95)=283µs
http_req_duration..............: avg=3.32s min=402µs med=3.08s max=13.36s p(90)=5.67s p(95)=8.06s
{ expected_response:true }...: avg=2.54s min=402µs med=2.4s max=12.65s p(90)=4.76s p(95)=5.37s
http_req_failed................: 33.03% ✓ 18949 ✗ 38419
http_req_receiving.............: avg=1.15ms min=3µs med=39µs max=2.05s p(90)=217µs p(95)=643.64µs
http_req_sending...............: avg=53.32µs min=1µs med=11µs max=64.86ms p(90)=52µs p(95)=89µs
http_req_tls_handshaking.......: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...............: avg=3.32s min=367µs med=3.08s max=13.36s p(90)=5.67s p(95)=8.06s
http_reqs......................: 57368 643.211303/s
iteration_duration.............: avg=12.96s min=3.01s med=13.08s max=25.48s p(90)=17.07s p(95)=19.92s
iterations.....................: 19122 214.396293/s
vus............................: 1 min=1 max=10000
vus_max........................: 10000 min=10000 max=10000
data_received......: 2.5 MB 28 kB/s
data_sent..........: 233 kB 2.6 kB/s
vus................: 5 min=5 max=1000
vus_max............: 1000 min=1000 max=1000
ws_connecting......: avg=4.38ms min=904.5µs med=3.54ms max=111.04ms p(90)=7.52ms p(95)=8.59ms
ws_msgs_received...: 50083 556.458673/s
ws_msgs_sent.......: 1000 11.11073/s
ws_sessions........: 1000 11.11073/s
data_received.........: 66 MB 734 kB/s
data_sent.............: 1.3 MB 15 kB/s
iteration_duration....: avg=16.21s min=3.05s med=17.16s max=31.34s p(90)=26.62s p(95)=27.43s
iterations............: 196 2.177476/s
vus...................: 126 min=12 max=5000
vus_max...............: 5000 min=5000 max=5000
ws_connecting.........: avg=1.36s min=595.91µs med=17.64ms max=10.14s p(90)=5.59s p(95)=5.89s
ws_msgs_received......: 1377197 15300.071189/s
ws_msgs_sent..........: 5792 64.346649/s
ws_session_duration...: avg=6.36s min=2.14ms med=5.56s max=21.74s p(90)=14.01s p(95)=15.95s
ws_sessions...........: 5792 64.346649/s
data_received.........: 65 MB 717 kB/s
data_sent.............: 2.6 MB 28 kB/s
iteration_duration....: avg=18.16s min=3.03s med=14.36s max=58.08s p(90)=38.85s p(95)=44.15s
iterations............: 136 1.509431/s
vus...................: 3007 min=9 max=10000
vus_max...............: 10000 min=10000 max=10000
ws_connecting.........: avg=11.86s min=590.66µs med=3.78s max=43.12s p(90)=25.55s p(95)=25.77s
ws_msgs_received......: 1336749 14836.253751/s
ws_msgs_sent..........: 8705 96.614689/s
ws_session_duration...: avg=18.31s min=1.71ms med=14.66s max=54.13s p(90)=44.02s p(95)=48.3s
ws_sessions...........: 8771 97.347207/s
data_received......: 13 MB 139 kB/s
data_sent..........: 410 kB 4.6 kB/s
vus................: 3 min=3 max=1000
vus_max............: 1000 min=1000 max=1000
ws_connecting......: avg=4.89ms min=1.46ms med=4.5ms max=48.9ms p(90)=6.18ms p(95)=7.41ms
ws_msgs_received...: 52490 583.193678/s
ws_msgs_sent.......: 3000 33.331702/s
ws_sessions........: 1000 11.110567/s
data_received......: 303 MB 3.4 MB/s
data_sent..........: 2.1 MB 23 kB/s
vus................: 63 min=14 max=5000
vus_max............: 5000 min=5000 max=5000
ws_connecting......: avg=3.52ms min=488.83µs med=952.54µs max=94.02ms p(90)=7.42ms p(95)=14.68ms
ws_msgs_received...: 1260821 14006.731584/s
ws_msgs_sent.......: 15000 166.638225/s
ws_sessions........: 5000 55.546075/s
data_received.........: 823 MB 9.1 MB/s
data_sent.............: 3.7 MB 42 kB/s
iteration_duration....: avg=32.37s min=12.01s med=33s max=33.02s p(90)=33s p(95)=33s
iterations............: 878 9.754204/s
vus...................: 329 min=12 max=10000
vus_max...............: 10000 min=10000 max=10000
ws_connecting.........: avg=5.54s min=434.79µs med=354.49ms max=43.78s p(90)=30s p(95)=30s
ws_msgs_received......: 3409118 37873.839154/s
ws_msgs_sent..........: 24696 274.361
ws_session_duration...: avg=29.65s min=9.01s med=30s max=30.05s p(90)=30s p(95)=30s
ws_sessions...........: 9844 109.362619/s
최고 접속자의 수를 1000, 5000, 10000명으로 설정하고 각 실시간 통신에 대한 시나리오 테스트를 수행했다. 테스트 결과를 3가지 측면에서 분석하였다.
우선 Polling과 Long Polling을 10000명의 사용자 지표에 따른 지표를 바탕으로 비교해보았다.
iteration_duration: 한 번의 반복(iteration)에 걸리는 시간을 측정한 것
interations : 테스트 시간 동안 반복된 횟수 (전체 횟수 , 초당 횟수)
Polling :
iteration_duration.........: p(90)=4.47s p(95)=4.94s
iterations.................: 63081 770.071737/s
Long Polling :
iteration_duration.............: p(90)=17.07s p(95)=19.92s
iterations.....................: 19122 214.396293/s
위 지표를 보면 Polling이 총 수행 횟수가 더 많고 한번의 주기 시간은 짧은 것을 알 수 있다.
- 서버로부터 데이터가 오지 않더라도 API를 응답하는 Polling의 특성 상 호출 횟수가 더 많은 것이다.
- 반면 데이터가 오기 까지 연결을 지속하는 Long Polling은 평균 수행 시간이 더 긴 것을 알 수 있는데 Long Polling 자체에 설정한 TimeOut 값에 따라 결과는 달라질 것이다.
다음은 Spring 자체의 Heap 사용량에 대한 비교이다. 지표를 분석해보면 Long Polling이 Polling 방식에 비해 Heap 사용량이 더 많은 것을 알 수 있다.
- 이에 대한 이유는 Long Polling 방식을 구현하는데 사용한 DeferredResult 때문이다.
- Polling 방식의 경우에는 결과값을 반환하고 더 이상 서버에서 유지하지 않는다. 하지만 Long Polling의 방식에는 DeferredResult 객체에 setResult() 메서드를 통한 값이 삽입되기 전까지 데이터가 유지되어야 하기 때문에 Heap 사용량이 더 많을 수밖에 없다.
두번째로 Polling 방식과 WebSocket 방식의 비교이다. 두 방식에 대해 k6에서 표로 제공하는 지표는 차이가 있다. 통신 방식에 차이가 있기 때문에 서로 다른 지표에 대한 비교는 어려웠다. 그래서 prometheus의 지표를 중심으로 비교했다.
- 두 방식에 대해 보통적으로 말하는 비교라면 WebSocket이 connection의 횟수가 적어 자원 사용량이 적을 것이라는 것이다 !!!!!!
- 자원 사용량을 중심으로 비교해보겠다.
System : 운영 체제 전체에서 모든 프로세스가 차지하는 CPU 사용량
Process : 특정 프로세스가 차지하는 CPU 사용량
- Polling
WebSocket이 평균적으로 Polling 방식과 비교하여 CPU 사용량이 적은 것을 알 수 있다.
- 매번 connection을 생성하고 종료하는 과정이 삭제되어 전체적인 CPU 사용량에도 영향을 준 것을 알 수 있다.
하지만 Spring 자체의 Heap 사용량은 WebSocket이 더 높게 측정되는 것을 알 수 있다.
- 이는 WebSocketSession 객체 때문이다. WebSocket Session을 계속 유지하며 지속적으로 Heap에 존재하게 되므로 커넥션을 유지하지 않는 ShortPolling 보다 WebSocket이 Heap 사용량이 더 많다.
마지막으로 WebSocket과 STOMP에 대한 비교이다. k6상에서 두 방식에 대해 동일한 지표를 제공한다. 각 지표로 확인할 수 있는 정보는 다음과 같다.
- 반복 작업:
iteration_duration
과iterations
는 테스트 반복 주기와 성능- 사용자 부하:
vus
와vus_max
는 가상의 사용자 부하 (테스트 상에서는 동일)- WebSocket 성능:
ws_connecting
과ws_session_duration
은 WebSocket 연결과 세션의 성능- 메시지 처리:
ws_msgs_received
와ws_msgs_sent
는 WebSocket을 통해 처리된 메시지 수
WebSocket
ws_msgs_received......: 1336749 14836.253751/s
ws_msgs_sent..........: 8705 96.614689/s
iterations............: 136 1.509431/s
STOMP
ws_msgs_received......: 3409118 37873.839154/s
ws_msgs_sent..........: 24696 274.361
iterations............: 878 9.754204/s
STOMP가 WebSocket과 비교해서 3가지 지표에서 더 높다는 것을 알 수 있다.
- 이는 동일한 테스트 시간동안 더 많은 메시지를 처리한 것이고 STOMP가 WebSocket과 비교하여 처리량이 더 높다는 것을 알 수 있다.
- 이에 대해 메시지 브로커를 통한 처리가 큰 이유라고 생각한다. 그룹 별로 나눠지 세션에 메시지를 분배하고 통신하는 과정을 WebSocket을 사용할 때는 사용자가 직접 구현해줬고 이에 대한 성능이 최적화되어 있는 메시지 브로커보다는 효율이 떨어진다 판단했다.
WebSocket :
ws_connecting.........: avg=11.86s min=590.66µs med=3.78s max=43.12s
ws_sessions...........: 8771 97.347207/s
Stomp :
ws_connecting.........: avg=5.54s min=434.79µs med=354.49ms max=43.78s
ws_sessions...........: 9844 109.362619/s
평균적으로 STOMP가 WebSocket에 비해 연결 설정에 더 적은 시간을 소비하는 것을 알 수 있다.
가장 주의 깊게 본 지표는 ws_sessions이다. 해당 지표는 테스트가 수행되는 동안 성공적으로 열린 websocket session 수이다.
- 해당 지표에서 10000의 사용자를 대상으로 STOMP가 더 많은 websocket session을 포함한 것을 알 수 있다. 즉 부하에 더 강하다는 것이다.