403 V1 focused load test plan - 100-hours-a-week/KTB4-3rd-CLOUD GitHub Wiki

403 V1 ์ง‘์ค‘ ๋ถ€ํ•˜ํ…Œ์ŠคํŠธ ์‹คํ–‰ ๊ณ„ํš (4๊ฐœ ์งˆ๋ฌธ ํ•œ์ •)

0. ๋ฒ”์œ„์™€ ๊ธฐ์กด ๋ฌธ์„œ ๊ด€๊ณ„

์ด ๋ฌธ์„œ๋Š” ์•„๋ž˜ ๋„ค ๊ฐ€์ง€ ์งˆ๋ฌธ๋งŒ ์ˆ˜์น˜๋กœ ๋‹ตํ•˜๊ธฐ ์œ„ํ•œ ์‹คํ–‰ ๊ณ„ํš์ด๋‹ค. RPS ์‚ฐ์ • ๊ทผ๊ฑฐ์™€ ์ „์ฒด ๋ณ‘๋ชฉ ๋ชฉ๋ก์€ 402 ์„ค๊ณ„, ๊ณตํ†ต ํ† ํด๋กœ์ง€ยท๊ด€์ธกยท์ค‘๋‹จ ์กฐ๊ฑด์€ 402 ์‹คํ–‰์„ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ฅด๊ณ , ์—ฌ๊ธฐ์„œ๋Š” 402์˜ ๋ถ€๋ถ„์ง‘ํ•ฉ์— ์ƒˆ ๋น„๊ต ์ถ•๊ณผ ํŒ์ • ์‚ฐ์‹๋งŒ ์ถ”๊ฐ€ํ•œ๋‹ค.

์งˆ๋ฌธ ์ด ๋ฌธ์„œ ๋‹จ๊ณ„ 402 ๋งคํ•‘ 402์— ์—†์–ด์„œ ์ƒˆ๋กœ ๋„ฃ์€ ๊ฒƒ
WS ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜ ์ฆ๊ฐ€๊ฐ€ Spring ์ž์›์„ ์–ผ๋งˆ๋‚˜ ์ ์œ ํ•˜๋Š”๊ฐ€ F1-a T9 ์—ฐ๊ฒฐ๋‹น heapยทdirect ์‚ฐ์ถœ์‹, WS_PER_CONNECTION_BYTES ์กฐ๊ฑด
WS ๋™์‹œ ์—ฐ๊ฒฐ ์‹œ๋„์œจ ์ฆ๊ฐ€๊ฐ€ ์ž์›์„ ์–ผ๋งˆ๋‚˜ ์ ์œ ํ•˜๋Š”๊ฐ€ F1-b T9์— ํ˜ผ์žฌ ์—ฐ๊ฒฐ ์ˆ˜ ๊ณ ์ •ยท์—ฐ๊ฒฐ๋ฅ  ๋‹จ๋… ์ถ•, Nginx ์ง„์ž… ํ•œ๊ณ„ ๋ถ„๋ฆฌ
๊ทธ ์ ์œ ๊ฐ€ RESTยท๋‹ค๋ฅธ API ์„œ๋น„์Šค์— ์žฅ์• ๋ฅผ ์ฃผ๋Š”๊ฐ€ F1-c T11 ์ปจํ…Œ์ด๋„ˆ quota ๊ฒฉ๋ฆฌ ํ™•์ธ, CPU credit ๋ณด์ •
๋ฉ”์‹œ์ง€ ์œ ํ˜•๋ณ„(๊ธด ์ปจํ…์ŠคํŠธยท์ ์€ ์—ฐ๊ฒฐ โ†” ์งง์€ ๋ฉ”์‹œ์ง€ยท๋งŽ์€ ์—ฐ๊ฒฐ) CPUยท๋ฉ”๋ชจ๋ฆฌ ๋น„๊ต F2 T10 ์ผ๋ถ€ 2ร—2ร—2 ์š”์ธ ์„ค๊ณ„, payload๋ณ„ DATA_FILE, ๊ฑด์ˆ˜ยท๋ฐ”์ดํŠธ ๋น„์šฉ ๋ถ„๋ฆฌ
DB ์ ์œ ์— ๋น„๋™๊ธฐยท์บ์‹œ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ๊ฐ€ F3 T10ยทT6 ์ผ๋ถ€ chat_hold_ms ์ถ•, ํ’€ ํฌ๊ธฐ ์‚ฐ์‹(Little)
์‚ฌ์šฉ์ž ์ฆ๊ฐ€๋กœ ์ธํ•œ ์ธ๊ธฐ ํŒŸ ๊ฒฝํ•ฉยทํ’€ ์ ์œ ๊ฐ€ ๋‹ค๋ฅธ API์— ์ฃผ๋Š” ์˜ํ–ฅ F4 T8 ํ’€ timeout โ†” lock wait timeout ๋ถ„๋ฆฌ, ๋ณ‘ํ–‰ REST
๋‹จ์ˆœ ์กฐํšŒ RPS ์ฆ๊ฐ€์— ๋”ฐ๋ฅธ ์ปจํ…Œ์ด๋„ˆ ์„ฑ๋Šฅ ๋ณ€ํ™”(t3.medium) F5 T2ยทT3 ์ผ๋ถ€ RPS๋‹น CPU-ms, quota ์ด๋ก  ์ƒํ•œ, credit ๋ณด์ •

์ธก์ • ๋Œ€์ƒ์€ loadtest-target/compose.yaml์˜ ํ•ฉ์„ฑ ํ”ฝ์Šค์ฒ˜(perf/ws-lab/demo ๋นŒ๋“œ ์ด๋ฏธ์ง€)์ด๋ฉฐ, ๋ถ€ํ•˜ ๋ฐœ์ƒ๊ธฐ๋Š” loadtest-scenarios/์˜ k6 ์Šคํฌ๋ฆฝํŠธ๋ฅผ loadtest-target/k6.sh๋กœ ์‹คํ–‰ํ•œ๋‹ค. ์ ˆ๋Œ€๊ฐ’์€ ์‹ค์ œ moyeota/be๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ ๊ณก์„ ์˜ ๋ชจ์–‘๊ณผ ์ตœ์ดˆ ํฌํ™” ์ž์›์„ ๊ฒฐ๋ก ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ , ์ ˆ๋Œ€ RPS๋ฅผ V1 ์šฉ๋Ÿ‰ ๋ณด์ฆ๊ฐ’์œผ๋กœ ์“ฐ์ง€ ์•Š๋Š”๋‹ค.

1. ๊ณ ์ • ์กฐ๊ฑด

1.1 t3.medium ์ž์› ์˜ˆ์‚ฐ

t3.medium์€ 2 vCPU ยท 4 GiB๋‹ค. ์ปจํ…Œ์ด๋„ˆ ์˜ˆ์‚ฐ์€ docs/103-V1-IaC-Input-Spec.md 4.1์„ ๋”ฐ๋ฅด๋ฉฐ ํ•ฉ๊ณ„๊ฐ€ ํ˜ธ์ŠคํŠธ CPU๋ฅผ 100% ์†Œ์ง„ํ•˜๋„๋ก ๋ฐฐ๋ถ„๋˜์–ด ์žˆ๋‹ค.

์ปจํ…Œ์ด๋„ˆ CPU ๋ฉ”๋ชจ๋ฆฌ ๋น„๊ณ 
nginx 0.25 128 MB worker_processes 1, worker_connections 16384, nofile 32768
frontend(placeholder) 0.50 512 MB ์ƒ์‹œ 384 MB ์ ์œ 
backend(Spring) 0.75 768 MB -Xmx512m, nofile 16384, Tomcat threads 200 / max-connections 8192
mysql 0.50 768 MB buffer pool 384 MB, max_connections 151
ํ•ฉ๊ณ„ 2.00 2,176 MB

์ด ๋ฐฐ๋ถ„์—์„œ ๋‚˜์˜ค๋Š” ๊ตฌ์กฐ์  ์‚ฌ์‹ค์„ ํŒ์ •์˜ ๊ธฐ์ค€์„ ์œผ๋กœ ์‚ผ๋Š”๋‹ค.

  • REST์™€ WebSocket์€ ๊ฐ™์€ backend ์ปจํ…Œ์ด๋„ˆ์˜ 0.75 CPU ยท 512 MB heap ยท Hikari 10 ์ปค๋„ฅ์…˜์„ ๊ณต์œ ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ "WS๊ฐ€ API ์„œ๋น„์Šค์— ์žฅ์• ๋ฅผ ์ค€๋‹ค"๋Š” 1์ฐจ์ ์œผ๋กœ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€ ๊ฒฝํ•ฉ์œผ๋กœ ๋‚˜ํƒ€๋‚œ๋‹ค.
  • frontendยทmysqlยทnginx๋Š” ๋ณ„๋„ quota์ด๋ฏ€๋กœ backend ํฌํ™”๋งŒ์œผ๋กœ๋Š” ์ง์ ‘ ๋นผ์•—๊ธฐ์ง€ ์•Š๋Š”๋‹ค. ์ด๋“ค๊นŒ์ง€ ๋™์‹œ์— ์•…ํ™”๋˜๋ฉด ์›์ธ์€ ํ˜ธ์ŠคํŠธ CPUยท๋„คํŠธ์›Œํฌยท CPU credit ๋˜๋Š” ๋””์Šคํฌ๋‹ค. F1-c์—์„œ ์ด ๊ตฌ๋ถ„์„ ํŒ์ •์— ์“ด๋‹ค.
  • backend mem_limit 768m๊ณผ -Xmx512m ์‚ฌ์ด ์—ฌ์œ ๋Š” ์•ฝ 256 MB๋‹ค. Metaspace, ์Šค๋ ˆ๋“œ ์Šคํƒ, Tomcat NIO์˜ direct ByteBuffer๊ฐ€ ์ด ์•ˆ์—์„œ ๋Š˜์–ด๋‚˜๋ฏ€๋กœ heap OOM๋ณด๋‹ค ์ปจํ…Œ์ด๋„ˆ OOM-kill์ด ๋จผ์ € ์˜ฌ ์ˆ˜ ์žˆ๋‹ค. F1-a์—์„œ NMT๋กœ ํ™•์ธํ•œ๋‹ค.

1.2 t3 CPU credit

t3.medium์˜ baseline์€ 20%(= 2 vCPU ์ค‘ 0.4 vCPU, 24 credits/h)๋‹ค. backend ํ•˜๋‚˜๋งŒ 0.75 CPU๋ฅผ ์ง€์† ์‚ฌ์šฉํ•ด๋„ baseline์„ ๋„˜์œผ๋ฏ€๋กœ, 10๋ถ„ ์ด์ƒ ์ธก์ •์€ ํ•ญ์ƒ credit์„ ์†Œ๋ชจํ•œ๋‹ค.

  • ๋งค ๋‹จ๊ณ„ ์ „ํ›„๋กœ CPUCreditBalance, CPUSurplusCreditBalance, credit specification(standard/unlimited)์„ ๊ธฐ๋กํ•œ๋‹ค.
  • credit balance๊ฐ€ 0์— ๋‹ฟ์€ ๊ตฌ๊ฐ„์˜ ์„ฑ๋Šฅ ์ €ํ•˜๋Š” ์šฉ๋Ÿ‰ ํ•œ๊ณ„๊ฐ€ ์•„๋‹ˆ๋ผ credit ๊ณ ๊ฐˆ์ด๋‹ค. ํ•ด๋‹น ๊ตฌ๊ฐ„์€ C_knee ์‚ฐ์ •์—์„œ ์ œ์™ธํ•˜๊ณ  ์žฌ์ธก์ •ํ•œ๋‹ค.
  • ๋น„๊ต ์‹คํ—˜(F2 ์…€ ๊ฐ„, F1-c control ๋Œ€๋น„)์€ credit ์ƒํƒœ๊ฐ€ ์œ ์‚ฌํ•œ ๊ตฌ๊ฐ„๋ผ๋ฆฌ๋งŒ ๋น„๊ตํ•œ๋‹ค. ๊ฐ€๋Šฅํ•˜๋ฉด unlimited๋กœ ๊ณ ์ •ํ•˜๊ณ  ๊ทธ ์‚ฌ์‹ค์„ ๊ฒฐ๊ณผ์— ๋‚จ๊ธด๋‹ค.

1.3 ๊ณ ์ •ํ•˜๊ณ  ๊ธฐ๋กํ•  ๊ฐ’

loadtest-target/.local/target.env ์ „์ฒด ๊ฐ’์„ ๋‹จ๊ณ„๋ณ„๋กœ ๊ณ ์ •ํ•˜๊ณ , ๋ณ€๊ฒฝ ์‹œ RUN_ID์™€ ํ•จ๊ป˜ ๊ธฐ๋กํ•œ๋‹ค. ๊ธฐ๋ณธ๊ฐ’์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

BACKEND_CPUS=0.75  BACKEND_MEMORY=768m  JVM_XMX=512m
HIKARI_MAX_POOL_SIZE=10  HIKARI_CONNECTION_TIMEOUT_MS=1000
WS_PER_CONNECTION_BYTES=0
MYSQL_BUFFER_POOL=384M  MYSQL_MAX_CONNECTIONS=151
FRONTEND_RESERVE_MB=384  FRONTEND_SSR_WORK_MS=5

์ถ”๊ฐ€๋กœ BACKEND_IMAGE digest, ํ”ฝ์Šค์ฒ˜ lab_run ์„ค์ •(chat_hold_ms, lock_hold_ms, parties, seats), ๋กœ์ปฌ ๋ฐœ์ƒ๊ธฐ ์‚ฌ์–‘ยทulimit -nยท์—…๋กœ๋“œ ๋Œ€์—ญํญ, ์‹œ๊ฐ„๋Œ€, credit ์ƒํƒœ๋ฅผ ๋งค ์‹คํ–‰์— ๋‚จ๊ธด๋‹ค.

1.4 ํŒ์ • ์›์น™

402์™€ ๋™์ผํ•˜๊ฒŒ C_knee(SLOยท์ •ํ•ฉ์„ฑ์ด ์ฒ˜์Œ ๊นจ์ง€๋Š” ์ง€์† ๋ถ€ํ•˜), C_safe = C_knee ร— 0.7์„ ์‚ฌ์šฉํ•œ๋‹ค. ๊ฐ ๋‹จ๊ณ„๋Š” 5๋ถ„ warm-up โ†’ 10๋ถ„ measure, ํ•œ ๋ฒˆ์— ํ•œ ์ถ•๋งŒ ๋ณ€๊ฒฝํ•˜๊ณ , ์ž„๊ณ„ ๊ทผ์ฒ˜ ๋‹จ๊ณ„๋งŒ 3ํšŒ ๋ฐ˜๋ณตํ•ด ์ค‘์•™๊ฐ’๊ณผ ๋ณ€๋™ํญ์„ ๋‚จ๊ธด๋‹ค. ๋กœ์ปฌ ๋ฐœ์ƒ๊ธฐ ์ง€ํ‘œ(CPU 60% ๋ฏธ๋งŒ, swap 0, ์—…๋กœ๋“œ 70% ๋ฏธ๋งŒ, dropped iterations 0, ๋ชฉํ‘œ ์ „๋‹ฌ๋ฅ  95% ์ด์ƒ)๋ฅผ ๋งŒ์กฑํ•˜์ง€ ๋ชปํ•œ ์‹คํ–‰์€ ์„œ๋ฒ„ ํ•œ๊ณ„๋กœ ํ•ด์„ํ•˜์ง€ ์•Š๋Š”๋‹ค.

1.5 REST ๋ฐฐ๊ฒฝ ๋ถ€ํ•˜์˜ ๋ถ„๋ฆฌ ์›์น™

REST ๋ฐฐ๊ฒฝ ๋ถ€ํ•˜๋Š” ๋ชจ๋“  ๋ธ”๋ก์— ๊ณ„์† ๊น”์ง€ ์•Š๋Š”๋‹ค. ํ…Œ์ŠคํŠธ ๋ชฉ์ ์— ๋”ฐ๋ผ ๋‹ค์Œ์ฒ˜๋Ÿผ ๋ถ„๋ฆฌํ•œ๋‹ค.

๋‹จ๊ณ„ REST ๋ฐฐ๊ฒฝ ๋ถ€ํ•˜ ์ด์œ 
F1-a ์—ฐ๊ฒฐ๋‹น ์ž์› ๋‹จ๊ฐ€ ์—†์Œ WebSocket ์—ฐ๊ฒฐ ์ž์ฒด์˜ heapยทdirectยทCPU ๋น„์šฉ์„ ์ธก์ •
F1-b handshake ์‹œ๋„์œจ ์—†์Œ TLSยทNginxยท์—ฐ๊ฒฐ ์ˆ˜๋ฆฝ ๋น„์šฉ์„ ์ธก์ •
F1-c REST ๊ฐ„์„ญ ํ•„์š” WS๊ฐ€ REST p95ยท์˜ค๋ฅ˜์œจ์„ ์•…ํ™”์‹œํ‚ค๋Š”์ง€ ์ธก์ •
F2 echo ๋ฉ”์‹œ์ง€ ์—†์Œ ์—ฐ๊ฒฐ ์ˆ˜ยท๋ฉ”์‹œ์ง€ ๊ฑด์ˆ˜ยทpayload ๋ฐ”์ดํŠธ ๋น„์šฉ์„ ๋ถ„๋ฆฌ
F3 DB ์ €์žฅ ์„ ํƒ์ ์œผ๋กœ ํ•„์š” ์ €์žฅ ์ž์ฒด์™€ ๋ณ‘ํ–‰ REST ๊ฐ„์„ญ์„ ๊ฐ๊ฐ ์ธก์ •
F4 hot-row ๊ฐ„์„ญ ์ธก์ • ์‹œ ํ•„์š” hot-row ์ž์ฒด์™€ ๋‹ค๋ฅธ API๋กœ์˜ ์ „ํŒŒ๋ฅผ ๋ถ„๋ฆฌ
F5 ์กฐํšŒ RPS ์—†์Œ ์กฐํšŒ API ์ž์ฒด์˜ CPUยทDBยทquota ํ•œ๊ณ„๋ฅผ ์ธก์ •

http-mixed.js๋ฅผ ๋ชจ๋“  ๋ธ”๋ก์˜ ๊ณตํ†ต ๋ฐฐ๊ฒฝ์œผ๋กœ ์‚ฌ์šฉํ•˜๋ฉด ๊ด€์ธก๊ฐ’์€ ์ˆœ์ˆ˜ํ•œ WebSocket ๋น„์šฉ์ด ์•„๋‹ˆ๋ผ ๋‹ค์Œ์˜ ํ•ฉ์ด ๋œ๋‹ค.

๊ด€์ธก ์ž์› = REST ๊ธฐ๋ณธ ๋น„์šฉ + WebSocket ๋น„์šฉ + ๋‘ ๋ถ€ํ•˜ ์‚ฌ์ด์˜ ๊ฒฝํ•ฉ ๋น„์šฉ

๋”ฐ๋ผ์„œ F1-aยทF1-bยทF2์˜ ํšŒ๊ท€์—๋Š” REST๋ฅผ ๋„ฃ์ง€ ์•Š๋Š”๋‹ค. F1-cยทF3ยทF4์—์„œ๋Š” control(REST๋งŒ)๊ณผ treatment(REST + WS ๋˜๋Š” hot-row)๋ฅผ ๊ฐ™์€ ์กฐ๊ฑด์œผ๋กœ ๋น„๊ตํ•œ๋‹ค. CPUยทthread ๊ฒฝํ•ฉ์€ REQUEST_GROUP=light, HikariยทDB ๊ฒฝํ•ฉ์€ REQUEST_GROUP=detail, ์‹ค์ œ ์‚ฌ์šฉ์ž ํŒจํ„ด์€ http-mixed.js๋กœ ๋ณ„๋„ ์ธก์ •ํ•œ๋‹ค. ๋ฐฐ๊ฒฝ REST๋ฅผ ๊ณ ์ •ํ•œ ๊ฒฐ๊ณผ๋Š” ์ˆœ์ˆ˜ ๋‹จ๊ฐ€๊ฐ€ ์•„๋‹ˆ๋ผ REST 50 RPS๊ฐ€ ์‹คํ–‰ ์ค‘์ผ ๋•Œ์˜ ์กฐ๊ฑด๋ถ€ ์ถ”๊ฐ€ ๋น„์šฉ์œผ๋กœ๋งŒ ํ•ด์„ํ•œ๋‹ค.

2. ์ง€ํ‘œ์™€ ํŒŒ์ƒ๊ฐ’ ์ •์˜

2.1 ํ”ฝ์Šค์ฒ˜๊ฐ€ ์ง์ ‘ ๋…ธ์ถœํ•˜๋Š” ์ง€ํ‘œ

management.server.port=8081(ํ˜ธ์ŠคํŠธ 8080) โ†’ /actuator/prometheus.

์ง€ํ‘œ PromQL ์˜ˆ ์“ฐ์ด๋Š” ๋‹จ๊ณ„
WS ๋™์‹œ ์—ฐ๊ฒฐ lab_ws_connections F1, F2
WS ์„ธ์…˜ ์ƒํƒœ ๋ฐ”์ดํŠธ lab_ws_connection_bytes F1-a
WS ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ์œจ rate(lab_ws_messages_total[1m]) F2, F3
์ฑ„ํŒ… ์ปค๋ฐ‹ ์‹œ๊ฐ„ histogram_quantile(0.95, sum by (le) (rate(lab_chat_commit_seconds_bucket[1m]))) F3
์ฑ„ํŒ… ์ปค๋ฐ‹ยท์˜ค๋ฅ˜ ์ˆ˜ rate(lab_chat_committed_total[1m]), rate(lab_chat_errors_total[1m]) F3
์ขŒ์„ lock ์ ์œ  ์‹œ๊ฐ„ histogram_quantile(0.95, sum by (le) (rate(lab_seat_lock_seconds_bucket[1m]))) F4
์ขŒ์„ ํŠธ๋žœ์žญ์…˜ ์‹œ๊ฐ„ lab_seat_transaction_seconds_bucket F4
์ขŒ์„ ๊ฒฐ๊ณผ rate(lab_seat_approved_total[1m]), lab_seat_full_total, lab_seat_errors_total F4
ํ’€ ๋Œ€๊ธฐ hikaricp_connections_pending, hikaricp_connections_active F3, F4, F5
ํ’€ ํš๋“ ์‹œ๊ฐ„ histogram_quantile(0.95, sum by (le) (rate(hikaricp_connections_acquire_seconds_bucket[1m]))) F3, F4
Tomcat tomcat_threads_busy_threads, tomcat_threads_config_max_threads F1-c, F5
JVM jvm_memory_used_bytes{area="heap"}, jvm_gc_pause_seconds_*, jvm_buffer_memory_used_bytes{id="direct"} F1, F2
HTTP histogram_quantile(0.95, sum by (le,uri) (rate(http_server_requests_seconds_bucket[1m]))), rate(http_server_requests_seconds_count{status="503"}[1m]) F1-c, F4, F5

์ปจํ…Œ์ด๋„ˆยทํ˜ธ์ŠคํŠธ ์ง€ํ‘œ๋Š” cAdvisorยทNode Exporter๋ฅผ ์“ด๋‹ค. ์ปจํ…Œ์ด๋„ˆ ์ด๋ฆ„์€ compose project๊ฐ€ moyeota-loadtest-target์ด๋ฏ€๋กœ moyeota-loadtest-target-backend-1 ํ˜•ํƒœ๋‹ค.

rate(container_cpu_usage_seconds_total{name="moyeota-loadtest-target-backend-1"}[1m])
rate(container_cpu_cfs_throttled_seconds_total{name="moyeota-loadtest-target-backend-1"}[1m])
container_memory_working_set_bytes{name=~"moyeota-loadtest-target-.*"}

2.2 ํŒŒ์ƒ๊ฐ’ (์ด ๋ฌธ์„œ์˜ ๊ฒฐ๋ก  ๋‹จ์œ„)

๋‹จ๊ณ„๋ณ„๋กœ ์•„๋ž˜ ๊ฐ’์„ ๊ณ„์‚ฐํ•ด ํ‘œ๋กœ ๋‚จ๊ธด๋‹ค. ์›์‹œ ์ง€ํ‘œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ด ๊ฐ’๋“ค์ด V2 ๊ฒฐ์ •์˜ ๊ทผ๊ฑฐ๋‹ค.

์—ฐ๊ฒฐ๋‹น heap bytes   = ฮ”(full GC ์งํ›„ heap used) รท ฮ”(์—ฐ๊ฒฐ ์ˆ˜)
์—ฐ๊ฒฐ๋‹น direct bytes = ฮ”(direct buffer used) รท ฮ”(์—ฐ๊ฒฐ ์ˆ˜)
์ˆ˜์šฉ ๊ฐ€๋Šฅ ์—ฐ๊ฒฐ ์ƒํ•œ = (Xmx 512MB ร— ์•ˆ์ „์œจ 0.7 โˆ’ ๊ธฐ์ € heap) รท ์—ฐ๊ฒฐ๋‹น heap bytes
๋ฉ”์‹œ์ง€๋‹น CPU ms     = ฮ”(backend CPU core-seconds/s) ร— 1000 รท ฮ”(msg/s)
KB๋‹น CPU ms         = ฮ”(backend CPU core-seconds/s) ร— 1000 รท ฮ”(msg/s ร— payload KB)
์š”์ฒญ๋‹น CPU ms       = (backend CPU core-seconds/s) ร— 1000 รท ์‹ค์ œ RPS
quota ์ด๋ก  ์ƒํ•œ RPS = 750 รท ์š”์ฒญ๋‹น CPU ms        (0.75 CPU = 750 CPU-ms/s)
์Šค๋ ˆ๋“œ ๊ธฐ์ค€ ์ƒํ•œ RPS = Tomcat max threads(200) รท ํ‰๊ท  ์‘๋‹ต์‹œ๊ฐ„(s)
ํ’€ ๊ธฐ์ค€ ์ƒํ•œ TPS    = HIKARI_MAX_POOL_SIZE(10) รท ์ปค๋„ฅ์…˜ ์ ์œ ์‹œ๊ฐ„(s)

3. F0. ์ค€๋น„์™€ ๋ฐœ์ƒ๊ธฐ ๊ฒ€์ฆ

  1. ํ”ฝ์Šค์ฒ˜ ๋ฐฐํฌ์™€ ์ƒํƒœ ํ™•์ธ: loadtest-target/deploy.sh up ํ›„ /api/probe, /api/lab/db-probe, up{...} exporter ์ƒํƒœ, Grafana ๋Œ€์‹œ๋ณด๋“œ.
  2. ๋‹จ๊ณ„๋ณ„ ํ”ฝ์Šค์ฒ˜ run ์ƒ์„ฑ. chat_hold_msยทlock_hold_ms๋Š” run ๋‹จ์œ„ ์„ค์ •์ด๋ผ ์ถ•๋งˆ๋‹ค ๋ณ„๋„ run์ด ํ•„์š”ํ•˜๋‹ค.
# F2ยทF3์šฉ (์ฑ„ํŒ… ์ €์žฅ ํŠธ๋žœ์žญ์…˜ ์ ์œ ์‹œ๊ฐ„ 0/50/200ms)
for h in 0 50 200; do
  curl -sk -X POST https://PUBLIC_DOMAIN/api/lab/runs \
    -H 'Content-Type: application/json' \
    -d "{\"id\":\"f3-h$h\",\"parties\":10,\"seats\":1000,\"chatHoldMs\":$h,\"lockHoldMs\":0}"
done

# F4์šฉ (lock ์ ์œ  0/100/500ms, ์ขŒ์„ ์ถฉ๋ถ„ โ†’ 409 full๋กœ lock ๋น„์šฉ์ด ๊ฐ€๋ ค์ง€์ง€ ์•Š๊ฒŒ)
for h in 0 100 500; do
  curl -sk -X POST https://PUBLIC_DOMAIN/api/lab/runs \
    -H 'Content-Type: application/json' \
    -d "{\"id\":\"f4-L$h\",\"parties\":10,\"seats\":1000,\"chatHoldMs\":0,\"lockHoldMs\":$h}"
done

# F4 ์ •ํ•ฉ์„ฑ ์ „์šฉ (์ •์› 4์„, ์ดˆ๊ณผ ์Šน์ธยท์Œ์ˆ˜ ์ขŒ์„ ๊ฒ€์ฆ)
curl -sk -X POST https://PUBLIC_DOMAIN/api/lab/runs \
  -H 'Content-Type: application/json' \
  -d '{"id":"f4-cap4","parties":2,"seats":4,"chatHoldMs":0,"lockHoldMs":100}'
  1. DATA_FILE ์„ธํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค. WS payload ํฌ๊ธฐ์™€ runId ์ฟผ๋ฆฌ๋Š” ๋ฐ์ดํ„ฐ ํŒŒ์ผ์— ์žˆ์œผ๋ฏ€๋กœ ์…€๋งˆ๋‹ค ํŒŒ์ผ์„ ๋ถ„๋ฆฌํ•œ๋‹ค. ws ๋ฐฐ์—ด์—๋Š” ํ•ญ๋ชฉ์„ ํ•˜๋‚˜๋งŒ ๋‘์–ด ํฌ๊ธฐ๊ฐ€ ์„ž์ด์ง€ ์•Š๊ฒŒ ํ•œ๋‹ค(select(data.ws)๊ฐ€ ๋ฌด์ž‘์œ„๋กœ ๊ณ ๋ฅธ๋‹ค).
cd loadtest-scenarios/data
python3 - <<'PY'
import json, copy
base = json.load(open('test-data.json'))
for size in (256, 8000):
    for run in ('f3-h0', 'f3-h50', 'f3-h200'):
        d = copy.deepcopy(base)
        body = 'x' * size
        d['ws'] = [{
            "name": f"chat-{size}b",
            "path": f"/ws?runId={run}",
            "headers": {}, "onOpenMessages": [],
            "heartbeatMessage": None, "heartbeatIntervalMs": None,
            "messages": [{"id": "m-{{runId}}-{{vu}}-{{iteration}}-{{timestamp}}", "body": body}],
        }]
        json.dump(d, open(f'ws-{size}b-{run}.json', 'w'))
    # ์ €์žฅ ์—†๋Š” echo ๊ฒฝ๋กœ: runId ์ฟผ๋ฆฌ๋ฅผ ์ œ๊ฑฐํ•˜๋ฉด ํ”ฝ์Šค์ฒ˜๊ฐ€ DB๋ฅผ ์“ฐ์ง€ ์•Š๊ณ  ์—์ฝ”๋งŒ ํ•œ๋‹ค
    d = copy.deepcopy(base)
    d['ws'] = [{
        "name": f"echo-{size}b", "path": "/ws",
        "headers": {}, "onOpenMessages": [],
        "heartbeatMessage": None, "heartbeatIntervalMs": None,
        "messages": [{"id": "m-{{vu}}-{{iteration}}", "body": 'x' * size}],
    }]
    json.dump(d, open(f'ws-{size}b-echo.json', 'w'))
PY

F4์šฉ ๋ฐ์ดํ„ฐ ํŒŒ์ผ๋„ run id๋ฅผ ๋ฐ”๊ฟ” ํ•จ๊ป˜ ๋งŒ๋“ ๋‹ค.

python3 - <<'PY2'
import json, copy
base = json.load(open('test-data.json'))
def retarget(d, run):
    for group in ('hotRowSame', 'hotRowDistributed', 'hotRowVerify'):
        for spec in d['requests'][group]:
            spec['path'] = spec['path'].replace('/runs/hot-01/', f'/runs/{run}/')
    return d
for run in ('f4-L0', 'f4-L100', 'f4-L500', 'f4-cap4'):
    json.dump(retarget(copy.deepcopy(base), run), open(f'test-data-{run}.json', 'w'))
PY2

f4-L* run์€ ์ขŒ์„ 1,000์„์ด๋ฏ€๋กœ hotRowVerify์˜ approved <= capacity ๋น„๊ต๊ฐ’์„ 4์—์„œ 1000์œผ๋กœ ๊ณ ์ณ์•ผ ํ•œ๋‹ค. ์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ์šฉ f4-cap4๋งŒ 4๋กœ ๋‘”๋‹ค.

  • ํ”ฝ์Šค์ฒ˜ ์ œ์•ฝ: body๋Š” 8,192์ž ์ดํ•˜์—ฌ์•ผ ์ €์žฅ ๊ฒฝ๋กœ๊ฐ€ ์„ฑ๊ณตํ•œ๋‹ค. ๋”ฐ๋ผ์„œ payload ํฐ ์ชฝ์€ 8,000 B๋ฅผ ์ƒํ•œ์œผ๋กœ ์“ด๋‹ค.
  • ์ €์žฅ ๊ฒฝ๋กœ(?runId= ์žˆ์Œ)๋Š” ์ปค๋ฐ‹ ํ›„ {"id","ok"} ACK๋งŒ ํšŒ์‹ ํ•˜๊ณ , echo ๊ฒฝ๋กœ(?runId= ์—†์Œ)๋Š” ๋ฐ›์€ payload๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜์†กํ•œ๋‹ค. ๋‘ ์กฐ๊ฑด์„ ๋น„๊ตํ•  ๋•Œ outbound ๋ฐ”์ดํŠธ ์ฐจ์ด๋ฅผ ๊ฐ์•ˆํ•œ๋‹ค.
  1. ๋ฐœ์ƒ๊ธฐ headroom ๊ฒ€์ฆ(T0). F2์˜ ์ตœ๋Œ€ ์…€(์—ฐ๊ฒฐ 1,800 ยท 8,000 B ยท 120 msg/s โ‰ˆ 960 KB/s โ‰ˆ 7.7 Mbps upload)๊ณผ F1-a์˜ ์ตœ๋Œ€ ์—ฐ๊ฒฐ(5,000 VU)์„ ๋กœ์ปฌ์—์„œ 1๋ถ„ ์˜ˆํ–‰ํ•˜๊ณ  ulimit -n, ๋กœ์ปฌ CPU, ์—…๋กœ๋“œ ๋Œ€์—ญํญ, dropped iterations๋ฅผ ํ™•์ธํ•œ๋‹ค. k6/ws๋Š” VU 1๊ฐœ๊ฐ€ ์—ฐ๊ฒฐ 1๊ฐœ์ด๋ฏ€๋กœ ์—ฐ๊ฒฐ ์ˆ˜๊ฐ€ ๊ณง VU ์ˆ˜๋‹ค.

4. F1. WebSocket ์—ฐ๊ฒฐ ์ฆ๊ฐ€๊ฐ€ Spring ์ž์›์„ ์–ผ๋งˆ๋‚˜ ์ ์œ ํ•˜๋Š”๊ฐ€

F1-a. ๋™์‹œ ์—ฐ๊ฒฐ ์ˆ˜ (๋ฉ”์‹œ์ง€ ์—†์Œ)

์—ฐ๊ฒฐ๋งŒ ์œ ์ง€ํ•ด ์—ฐ๊ฒฐ ์ž์ฒด์˜ ์ž์› ๋‹จ๊ฐ€๋ฅผ ๊ตฌํ•œ๋‹ค.

  • ์—ฐ๊ฒฐ ์ˆ˜: control 0 โ†’ 500 โ†’ 1,000 โ†’ 1,575 โ†’ 1,800 โ†’ 2,100 โ†’ 3,000 โ†’ 5,000
  • CONNECT_RATE=6 ๊ณ ์ •, HOLD_DURATION=30m, ramp-down 1๋ถ„
  • WS_PER_CONNECTION_BYTES: 0(์—ฐ๊ฒฐ ์ˆœ์ˆ˜ ๋น„์šฉ)์œผ๋กœ ์ „ ๋‹จ๊ณ„ โ†’ 32768(์„ธ์…˜์ด ์‚ฌ์šฉ์žยท๋ฐฉยท์ตœ๊ทผ ๋ฉ”์‹œ์ง€ ์ƒํƒœ๋ฅผ ๋“ค๊ณ  ์žˆ๋Š” ์‹ค์ œ ์•ฑ ๊ทผ์‚ฌ)์œผ๋กœ 1,800 / 3,000 / 5,000 ์žฌ์ธก์ •
RUN_ID=f1a-c1800-b0 TARGET_CONNECTIONS=1800 CONNECT_RATE=6 HOLD_DURATION=30m \
  DATA_FILE=./data/ws-256b-echo.json \
  loadtest-target/k6.sh ws-connect.js \
  --summary-export=results/f1a-c1800-b0.json

์ธก์ •: hold ๊ตฌ๊ฐ„์—์„œ jcmd <pid> GC.run ํ›„ heap used, jcmd <pid> VM.native_memory summary(Thread/Internal/Other ์ฆ๊ฐ€๋ถ„), direct buffer, lab_ws_connections, backendยทnginx FD์™€ ESTABLISHED/TIME_WAIT, GC pause, backendยทnginx CPU์™€ throttling, container_memory_working_set_bytes.

ํŒ์ •:

  • ์—ฐ๊ฒฐ๋‹น heapยทdirect bytes๋ฅผ ๊ณ„์‚ฐํ•ด ์ˆ˜์šฉ ๊ฐ€๋Šฅ ์—ฐ๊ฒฐ ์ƒํ•œ์„ ์‚ฐ์ถœํ•œ๋‹ค. ์ด ๊ฐ’์ด ์˜ˆ์ธก ํ”ผํฌ 2,100์„ ๋ฐ‘๋Œ๋ฉด ์—ฐ๊ฒฐ ์ˆ˜ ์ž์ฒด๊ฐ€ V1 ํ•œ๊ณ„๋‹ค.
  • working set์ด 768 MB์— ์ ‘๊ทผํ•˜๋ฉฐ OOM-kill์ด heap OOM๋ณด๋‹ค ๋จผ์ € ์˜ค๋ฉด ์›์ธ์€ heap์ด ์•„๋‹ˆ๋ผ direct/native ์˜์—ญ์ด๋‹ค. -Xmx ์ถ•์†Œ ๋˜๋Š” ์ปจํ…Œ์ด๋„ˆ ๋ฉ”๋ชจ๋ฆฌ ์ฆ์„คยทWS ๋ถ„๋ฆฌ๋กœ ๋ฐฉํ–ฅ์ด ๊ฐˆ๋ฆฐ๋‹ค.
  • Tomcat max-connections 8192, backend nofile 16384, nginx nofile 32768(์—ฐ๊ฒฐ๋‹น client+upstream 2 FD)์ด ๊ตฌ์กฐ์  ์ƒํ•œ์ด๋‹ค. ๊ทธ ์ „์— CPUยท๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ํฌํ™”๋˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

F1-b. ๋™์‹œ ์—ฐ๊ฒฐ ์‹œ๋„์œจ (handshake rate)

์—ฐ๊ฒฐ ์ˆ˜๋ฅผ ๊ณ ์ •ํ•˜๊ณ  ์ดˆ๋‹น ์‹ ๊ทœ ์—ฐ๊ฒฐ๋งŒ ๋Š˜๋ ค ์—ฐ๊ฒฐ ์ˆ˜๋ฆฝ ๋น„์šฉ์„ ๋ถ„๋ฆฌํ•œ๋‹ค.

  • TARGET_CONNECTIONS=1000 ๊ณ ์ •, CONNECT_RATE = 6 โ†’ 10 โ†’ 20 โ†’ 50 โ†’ 100 โ†’ 200
  • ์ธก์ • ์ฐฝ์€ ramp ๊ตฌ๊ฐ„์ด๋‹ค. ws-connect.js๋Š” ์—ฐ๊ฒฐ ์ˆ˜๋ช…์„ ramp + hold + rampDown + 60s๋กœ ์žก์•„ ํ•œ ๋ฒˆ ๋งบ์€ ์—ฐ๊ฒฐ์„ ํ…Œ์ŠคํŠธ ๋๊นŒ์ง€ ์œ ์ง€ํ•˜๋ฏ€๋กœ, hold ๊ตฌ๊ฐ„์—์„œ๋Š” ์‹ ๊ทœ handshake๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋”ฐ๋ผ์„œ HOLD_DURATION=2m๋กœ ์งง๊ฒŒ ๋‘๊ณ  ๊ฐ™์€ ๋‹จ๊ณ„๋ฅผ 5ํšŒ ๋ฐ˜๋ณตํ•ด ramp ๊ตฌ๊ฐ„์˜ handshake ์ง€ํ‘œ๋ฅผ ๋ชจ์€๋‹ค(ramp ๊ธธ์ด = ์—ฐ๊ฒฐ์ˆ˜ รท ์—ฐ๊ฒฐ๋ฅ ์ดˆ).
  • ์ง€์†์ ์ธ handshake ๋ถ€ํ•˜๊ฐ€ ํ•„์š”ํ•˜๋ฉด ws-connect.js์— ์—ฐ๊ฒฐ ์ˆ˜๋ช… (CONNECTION_TTL) ์ถ•์„ ์ถ”๊ฐ€ํ•ด ์—ฐ๊ฒฐ์ด ์ฃผ๊ธฐ์ ์œผ๋กœ ๋Š๊ธฐ๊ณ  ๋‹ค์‹œ ๋งบ๋„๋ก ๋ฐ”๊ฟ”์•ผ ํ•œ๋‹ค. ํ˜„์žฌ RECONNECT_DELAY๋Š” ํ…Œ์ŠคํŠธ ์ข…๋ฃŒ ํ›„ iteration ๊ฐ„๊ฒฉ์ด๋ผ ์ด ๋ชฉ์ ์— ์“ฐ์ด์ง€ ์•Š๋Š”๋‹ค.
for i in 1 2 3 4 5; do
  RUN_ID=f1b-rate50-r$i TARGET_CONNECTIONS=1000 CONNECT_RATE=50 HOLD_DURATION=2m \
    DATA_FILE=./data/ws-256b-echo.json \
    loadtest-target/k6.sh ws-connect.js --summary-export=results/f1b-rate50-r$i.json
done

์ธก์ •: handshake p95/p99, 101 ์„ฑ๊ณต๋ฅ , ws_errors, nginx active/reading/writing/ waiting๊ณผ accept ์‹คํŒจ, listen queue overflow(node_netstat_TcpExt_ListenOverflows), nginxยทbackend CPU์™€ throttling, TLS handshake ๋น„์šฉ.

ํŒ์ •: TLS์™€ accept๋Š” **nginx 0.25 CPU ยท worker_processes 1**์—์„œ ์ฒ˜๋ฆฌ๋œ๋‹ค. backend๊ฐ€ ์—ฌ์œ ๋กœ์šด๋ฐ nginx CPU๊ฐ€ quota๋ฅผ ์ฑ„์šฐ๊ณ  handshake p95๋งŒ ์•…ํ™”๋˜๋ฉด ํ•œ๊ณ„๋Š” Spring์ด ์•„๋‹ˆ๋ผ ์ง„์ž… ๊ณ„์ธต์ด๋ฏ€๋กœ, Spring ๋ถ„๋ฆฌ๋ณด๋‹ค nginx CPUยทworker ์กฐ์ • ๋˜๋Š” TLS ์ข…๋ฃŒ ์œ„์น˜ ๋ณ€๊ฒฝ์ด ๋จผ์ €๋‹ค.

F1-c. ์—ฐ๊ฒฐ ์ ์œ ๊ฐ€ RESTยท๋‹ค๋ฅธ API์— ์žฅ์• ๋ฅผ ์ฃผ๋Š”๊ฐ€

WS ์—ฐ๊ฒฐ์„ ๋‹จ๊ณ„๋ณ„๋กœ ๊ณ ์ •ํ•œ ์ƒํƒœ์—์„œ ๋™์ผํ•œ REST ๋ถ€ํ•˜๋ฅผ ๋ฐ˜๋ณตํ•ด control๊ณผ ๋น„๊ตํ•œ๋‹ค.

  1. control: WS 0์—์„œ http-operation.js REQUEST_GROUP=light, TARGET_RPS=50
  2. ํ„ฐ๋ฏธ๋„ 1์—์„œ ws-connect.js๋ฅผ hold์— ์ง„์ž…์‹œํ‚ค๊ณ , ํ„ฐ๋ฏธ๋„ 2์—์„œ ๊ฐ™์€ REST ๋ถ€ํ•˜๋ฅผ ์‹คํ–‰
  3. WS ์—ฐ๊ฒฐ = 1,000 ยท 1,800 ยท 2,100 ยท 3,000 ยท 5,000
  4. ํ•„์š” ์‹œ F2์˜ 36 msg/s๋ฅผ ์ถ”๊ฐ€ํ•ด "์—ฐ๊ฒฐ๋งŒ์˜ ๊ฐ„์„ญ"๊ณผ "๋ฉ”์‹œ์ง€๊นŒ์ง€์˜ ๊ฐ„์„ญ"์„ ๋ถ„๋ฆฌ
# ํ„ฐ๋ฏธ๋„ 1
RUN_ID=f1c-ws2100 TARGET_CONNECTIONS=2100 CONNECT_RATE=6 HOLD_DURATION=30m \
  DATA_FILE=./data/ws-256b-echo.json loadtest-target/k6.sh ws-connect.js
# ํ„ฐ๋ฏธ๋„ 2 (control๊ณผ ๋™์ผ ์กฐ๊ฑด)
RUN_ID=f1c-rest-ws2100 REQUEST_GROUP=light TARGET_RPS=50 WARMUP=5m DURATION=10m \
  loadtest-target/k6.sh http-operation.js \
  --summary-export=results/f1c-rest-ws2100.json

๊ธฐ๋ก ํ‘œ๋Š” ๋‹ค์Œ ํ˜•ํƒœ๋กœ ๋งŒ๋“ ๋‹ค.

WS ์—ฐ๊ฒฐ REST p95 control ๋Œ€๋น„ ์˜ค๋ฅ˜์œจ backend CPU throttling heap GC pause frontend CPU mysql CPU CPU credit

ํŒ์ •:

  • REST p95๊ฐ€ control ๋Œ€๋น„ 20% ์ด์ƒ ์•…ํ™”๋˜๊ณ  backend CPUยทGCยทTomcat busy์™€ ์ƒ๊ด€๋˜๋ฉด ๊ฐ™์€ ์ปจํ…Œ์ด๋„ˆ ์ž์› ๊ณต์œ ๊ฐ€ ์›์ธ์ด๋‹ค โ†’ WS ๋ถ„๋ฆฌ(๋ณ„๋„ ํ”„๋กœ์„ธ์Šค ๋˜๋Š” ๊ฒŒ์ดํŠธ์›จ์ด)๋ฅผ V2 ํ›„๋ณด๋กœ ์˜ฌ๋ฆฐ๋‹ค.
  • frontendยทmysql๊นŒ์ง€ ๋™์‹œ์— ์•…ํ™”๋˜๋ฉด ์ปจํ…Œ์ด๋„ˆ quota๊ฐ€ ์•„๋‹ˆ๋ผ ํ˜ธ์ŠคํŠธ CPUยท ๋„คํŠธ์›Œํฌยทcredit์ด ์›์ธ์ด๋‹ค. credit balance๋ฅผ ๋จผ์ € ํ™•์ธํ•˜๊ณ  ์žฌ์ธก์ •ํ•œ๋‹ค.
  • REST๋Š” light(DB ๋ฏธ์‚ฌ์šฉ)์™€ detail(DB ์‚ฌ์šฉ) ๋‘ ๊ณ„์—ด๋กœ ๊ฐ๊ฐ ๋ณธ๋‹ค. light๋งŒ ์•…ํ™”๋˜๋ฉด CPUยท์Šค๋ ˆ๋“œ, detail๋„ ์•…ํ™”๋˜๋ฉด ํ’€ยทDB ๊ฒฝ๋กœ๊นŒ์ง€ ๋ฒˆ์ง„ ๊ฒƒ์ด๋‹ค.

5. F2. ๋ฉ”์‹œ์ง€ ์œ ํ˜•๋ณ„ CPUยท๋ฉ”๋ชจ๋ฆฌ ์ ์œ  ๋น„๊ต

"๋ฉ”์‹œ์ง€ ์ปจํ…์ŠคํŠธ๊ฐ€ ํฌ๊ณ  ์—ฐ๊ฒฐ์ด ์ ์€ ๊ฒฝ์šฐ"์™€ "๋ฉ”์‹œ์ง€๊ฐ€ ์งง๊ณ  ์—ฐ๊ฒฐ์ด ๋งŽ์€ ๊ฒฝ์šฐ"๋ฅผ ๊ทธ๋ƒฅ ๋‘ ๋ฒˆ ์žฌ๋ฉด ์›์ธ์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์—†๋‹ค. ์—ฐ๊ฒฐ ์ˆ˜ยทpayloadยท๋ฉ”์‹œ์ง€์œจ์„ 2ร—2ร—2 ์š”์ธ ์„ค๊ณ„๋กœ ๋Œ๋ ค ๊ฑด์ˆ˜ ๋น„์šฉ๊ณผ ๋ฐ”์ดํŠธ ๋น„์šฉ์„ ๋ถ„๋ฆฌํ•œ๋‹ค.

์…€ ์—ฐ๊ฒฐ payload(body) aggregate msg/s inbound bytes/s ์„ฑ๊ฒฉ
C1 200 256 B 36 9 KB/s ๊ธฐ์ค€
C2 200 8,000 B 36 288 KB/s ํฐ ์ปจํ…์ŠคํŠธยท์ ์€ ์—ฐ๊ฒฐ
C3 1,800 256 B 36 9 KB/s ์งง์€ ๋ฉ”์‹œ์ง€ยท๋งŽ์€ ์—ฐ๊ฒฐ
C4 1,800 8,000 B 36 288 KB/s ๊ต์ฐจ ํ™•์ธ
C5 200 256 B 120 31 KB/s ๋ฉ”์‹œ์ง€์œจ ๊ธฐ์šธ๊ธฐ
C6 200 8,000 B 120 960 KB/s ํฐ ์ปจํ…์ŠคํŠธ ์ƒํ–ฅ
C7 1,800 256 B 120 31 KB/s ์งง์€ ๋ฉ”์‹œ์ง€ ์ƒํ–ฅ
C8 1,800 8,000 B 120 960 KB/s ์ตœ๋Œ€
  • 8๊ฐœ ์…€ ๋ชจ๋‘ REST ๋ฐฐ๊ฒฝ ์—†์ด echo ๊ฒฝ๋กœ(์ €์žฅ ์—†์Œ)๋กœ ๋จผ์ € ๋Œ๋ ค Springยท ๋„คํŠธ์›Œํฌ ๋น„์šฉ๋งŒ ๋ณธ๋‹ค. DB ์ถ•์€ F3์—์„œ ๋‹ค๋ฃฌ๋‹ค. ์ €์žฅ ๊ฒฝ๋กœ์™€ ๋ณ‘ํ–‰ REST๋Š” F3์—์„œ ๋ณ„๋„์˜ control/treatment๋กœ ์ธก์ •ํ•œ๋‹ค.
  • ws-message.js๋Š” messageIntervalMs = (TARGET_CONNECTIONS รท MESSAGE_RATE) ร— 1000์œผ๋กœ ์—ฐ๊ฒฐ๋‹น ๊ฐ„๊ฒฉ์„ ๊ณ„์‚ฐํ•˜๋ฏ€๋กœ, aggregate msg/s๊ฐ€ ๊ณ ์ •๋˜๊ณ  ์—ฐ๊ฒฐ ์ˆ˜๋Š” ๋…๋ฆฝ ์ถ•์ด ๋œ๋‹ค.
# C4: ์—ฐ๊ฒฐ 1,800 ยท 8,000 B ยท 36 msg/s ยท echo
RUN_ID=f2-C4-echo TARGET_CONNECTIONS=1800 MESSAGE_RATE=36 \
  CONNECTION_RAMP=5m DURATION=10m \
  DATA_FILE=./data/ws-8000b-echo.json \
  loadtest-target/k6.sh ws-message.js --summary-export=results/f2-C4-echo.json

์ธก์ •: backend CPU(core-seconds/s)์™€ throttling, heap usedยทallocation rateยทGC pause, direct buffer, rate(lab_ws_messages_total[1m]), ์ปจํ…Œ์ด๋„ˆ network rx/tx, k6 ws_messages_sent/receivedยทws_errors, ์‘๋‹ต(ACK/echo) ์ง€์—ฐ.

ํŒ์ • ์‚ฐ์‹:

backend CPU(core) โ‰ˆ a + bยทR + cยท(R ร— S_KB) + dยทC
   R = aggregate msg/s, S_KB = payload KB, C = ์—ฐ๊ฒฐ ์ˆ˜(d๋Š” F1-a์—์„œ ๊ตฌํ•œ ๊ฐ’)
b = ๋ฉ”์‹œ์ง€ ๊ฑด๋‹น ๋น„์šฉ (ํ”„๋ ˆ์ž„ ์ฒ˜๋ฆฌยท์—ญ์ง๋ ฌํ™”ยท์Šค๋ ˆ๋“œ ์ „ํ™˜ยทํŠธ๋žœ์žญ์…˜ ์˜ค๋ฒ„ํ—ค๋“œ)
c = ๋ฐ”์ดํŠธ๋‹น ๋น„์šฉ (๋ณต์‚ฌยท๋ฒ„ํผยท์ง๋ ฌํ™”ยท๋„คํŠธ์›Œํฌ)
  • b๊ฐ€ ์ง€๋ฐฐ(C3ยทC7์ด C2ยทC6๋ณด๋‹ค ๋‚˜์จ) โ†’ ๋ณ‘๋ชฉ์€ ๊ฑด์ˆ˜๋‹ค. ๋ฉ”์‹œ์ง€ ๋ฐฐ์นญ, ACK ๊ฒฝ๋Ÿ‰ํ™”, ์ €์žฅ ๋น„๋™๊ธฐํ™”, ์—ฐ๊ฒฐยท์„ธ์…˜ ์ฒ˜๋ฆฌ ๊ฒฝ๋กœ ์ตœ์ ํ™”๊ฐ€ ์šฐ์„ ์ด๋‹ค.
  • c๊ฐ€ ์ง€๋ฐฐ(C2ยทC6์ด ๋” ๋‚˜์จ) โ†’ ๋ณ‘๋ชฉ์€ ๋ฐ”์ดํŠธ๋‹ค. payload ์ƒํ•œ, ์••์ถ•, ๋ณธ๋ฌธ ๋ถ„๋ฆฌ ์ €์žฅ(๋ฉ”ํƒ€๋งŒ ์‹ค์‹œ๊ฐ„ ์ „๋‹ฌ)์ด ์šฐ์„ ์ด๋‹ค.
  • ๋ฉ”๋ชจ๋ฆฌ๋Š” heap ์ฆ๊ฐ€๋Ÿ‰๊ณผ GC ๋นˆ๋„๋กœ ๋ณธ๋‹ค. ํฐ payload๋Š” allocation rate์™€ GC ํšŸ์ˆ˜๋ฅผ, ๋งŽ์€ ์—ฐ๊ฒฐ์€ ์ƒ์ฃผ heap(์—ฐ๊ฒฐ๋‹น ์ƒ์ˆ˜)์„ ์˜ฌ๋ฆฐ๋‹ค. ๋‘ ์ถ• ์ค‘ ์–ด๋А ๊ฒƒ์ด -Xmx512m๋ฅผ ๋จผ์ € ์œ„ํ˜‘ํ•˜๋Š”์ง€ ๋ช…์‹œํ•œ๋‹ค.
  • ์–ด๋–ค ์…€์ด๋“  dropped iterationยท๋ฐœ์ƒ๊ธฐ ํฌํ™”๊ฐ€ ์žˆ์œผ๋ฉด ๊ทธ ์…€์€ ํšŒ๊ท€์—์„œ ์ œ์™ธํ•œ๋‹ค.

6. F3. DB ์ ์œ  โ€” ๋น„๋™๊ธฐยท์บ์‹œ๊ฐ€ ํ•„์š”ํ•œ๊ฐ€

ํ”ฝ์Šค์ฒ˜์˜ ์ €์žฅ ๊ฒฝ๋กœ๋Š” ์ปค๋ฐ‹ ํ›„ ACK๋ฅผ ๋ณด๋‚ด๋ฏ€๋กœ WS ์‘๋‹ต ๊ฒฝ๋กœ๊ฐ€ DB ํŠธ๋žœ์žญ์…˜์— ์ง์ ‘ ๋ฌถ์ธ๋‹ค. ์ด ๊ฒฐํ•ฉ์ด ์–ด๋””์„œ ๊นจ์ง€๋Š”์ง€, ํ’€ 10๊ฐœ๋กœ ๊ฐ๋‹น ๊ฐ€๋Šฅํ•œ msg/s๊ฐ€ ์–ผ๋งˆ์ธ์ง€ ๊ตฌํ•œ๋‹ค.

  • chat_hold_ms = 0 / 50 / 200 (f3-h0, f3-h50, f3-h200 run)
  • aggregate msg/s = 36 โ†’ 120 โ†’ 300
  • ์—ฐ๊ฒฐ 1,800 ๊ณ ์ •, payload 256 B ๊ณ ์ •(๋ฐ”์ดํŠธ ์ถ• ์ œ๊ฑฐ)

๊ฐ ์กฐ๊ฑด์„ ๋‘ ๋ฒˆ ์ธก์ •ํ•œ๋‹ค.

  1. WS ์ €์žฅ๋งŒ ์‹คํ–‰ํ•ด DB commitยทHikari์˜ ์ˆœ์ˆ˜ ์ ์œ  ๋น„์šฉ์„ ์ธก์ •ํ•œ๋‹ค.
  2. ๊ฐ™์€ WS ๋ถ€ํ•˜์— REQUEST_GROUP=detail, TARGET_RPS=50์„ ๋ณ‘ํ–‰ํ•ด REST ๊ฐ„์„ญ์„ ์ธก์ •ํ•œ๋‹ค.

๋‘ ๋ฒˆ์งธ ์‹คํ–‰์€ ์ฒซ ๋ฒˆ์งธ ์‹คํ–‰์˜ ๋Œ€์ฒด๊ฐ€ ์•„๋‹ˆ๋ผ ๋ณ„๋„์˜ treatment๋‹ค. ์ฒซ ๋ฒˆ์งธ ์‹คํ–‰์ด ์—†์œผ๋ฉด Hikari pending ์ฆ๊ฐ€๊ฐ€ ์ €์žฅ ์ž์ฒด ๋•Œ๋ฌธ์ธ์ง€ ๋ณ‘ํ–‰ REST์™€์˜ ๊ฒฝํ•ฉ ๋•Œ๋ฌธ์ธ์ง€ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์—†๋‹ค.

# ์ฒซ ๋ฒˆ์งธ ์‹คํ–‰: WS ์ €์žฅ๋งŒ ์ธก์ •
RUN_ID=f3-h50-r120 TARGET_CONNECTIONS=1800 MESSAGE_RATE=120 \
  CONNECTION_RAMP=5m DURATION=10m \
  DATA_FILE=./data/ws-256b-f3-h50.json \
  loadtest-target/k6.sh ws-message.js --summary-export=results/f3-h50-r120.json

# ๋ณ„๋„ ๋ฐ˜๋ณต: ๊ฐ™์€ WS ์กฐ๊ฑด์— REST ์กฐํšŒ๋ฅผ ์ถ”๊ฐ€ํ•œ treatment
# ํ„ฐ๋ฏธ๋„ 1
RUN_ID=f3-ws-h50-r120-rest TARGET_CONNECTIONS=1800 MESSAGE_RATE=120 \
  CONNECTION_RAMP=5m DURATION=10m \
  DATA_FILE=./data/ws-256b-f3-h50.json \
  loadtest-target/k6.sh ws-message.js --summary-export=results/f3-ws-h50-r120-rest.json
# ํ„ฐ๋ฏธ๋„ 2: ์œ„ WS ์‹คํ–‰๊ณผ ๋™์‹œ์—
RUN_ID=f3-rest-h50-r120 REQUEST_GROUP=detail TARGET_RPS=50 WARMUP=5m DURATION=10m \
  loadtest-target/k6.sh http-operation.js \
  --summary-export=results/f3-rest-h50-r120.json

์˜ˆ์ธก๊ฐ’์„ ๋จผ์ € ์ ๊ณ  ์‹ค์ธก๊ณผ ๋น„๊ตํ•œ๋‹ค(ํ’€ ๊ธฐ์ค€ ์ƒํ•œ TPS = 10 รท ์ ์œ ์‹œ๊ฐ„).

chat_hold_ms ์ปค๋„ฅ์…˜ ์ ์œ ์‹œ๊ฐ„(๊ฐ€์ •) ํ’€ ๊ธฐ์ค€ ์ƒํ•œ msg/s ์‹ค์ธก C_knee
0 ~3 ms ~3,300 (CPU๊ฐ€ ๋จผ์ €)
50 ~53 ms ~190
200 ~203 ms ~49

์ธก์ •: lab_chat_commit p95/p99, lab_chat_errors_total(= ACK ok:false), hikaricp_connections_pendingยทactive, ํš๋“ ์‹œ๊ฐ„ p95(1,000 ms ์ดˆ๊ณผ ์‹œ ์‹คํŒจ), MySQL redoยทfsyncยทdirty pageยทrow lock, EBS await, ๊ทธ๋ฆฌ๊ณ  ๋ณ‘ํ–‰ REST์˜ p95์™€ 503 ๋น„์œจ.

ํŒ์ •:

์ฆ๊ฑฐ ๊ฒฐ๋ก 
ํ’€ pendingยทํš๋“ ์‹œ๊ฐ„์ด ๋จผ์ € ์ƒ์Šนํ•˜๊ณ  MySQL CPUยทI/O๋Š” ์—ฌ์œ  ์ปค๋„ฅ์…˜ ์ ์œ ์‹œ๊ฐ„ ๋ฌธ์ œ โ†’ ์ €์žฅ ๋น„๋™๊ธฐํ™”(ํ + ๋ฐฐ์น˜ insert)๋กœ WS ์‘๋‹ต ๊ฒฝ๋กœ์—์„œ ๋ถ„๋ฆฌ
MySQL CPUยทredoยทfsync๊ฐ€ ํ•จ๊ป˜ ํฌํ™” DB ์“ฐ๊ธฐ ์ž์ฒด ํ•œ๊ณ„ โ†’ ๋ฐฐ์น˜ ์ปค๋ฐ‹ยท์ธ๋ฑ์Šค ์กฐ์ • ํ›„ DB ๋ถ„๋ฆฌ/RDS
๋ณ‘ํ–‰ REST ์กฐํšŒ p95ยท503์ด WS ์ €์žฅ๊ณผ ํ•จ๊ป˜ ์•…ํ™” ํ’€์ด ๊ณต์œ  ์ž์›์œผ๋กœ ์žฅ์• ๋ฅผ ์ „ํŒŒ โ†’ ํ’€ ๋ถ„๋ฆฌ(์ฝ๊ธฐ/์“ฐ๊ธฐ) ๋˜๋Š” ์กฐํšŒ ์บ์‹œ
WS ์ €์žฅ ์—†์Œ(echo) ๋Œ€๋น„ ์ €์žฅ ์กฐ๊ฑด์—์„œ๋งŒ ์•…ํ™” ์‹ค์‹œ๊ฐ„ ๊ฒฝ๋กœ์™€ ์˜์† ๊ฒฝ๋กœ ๋ถ„๋ฆฌ๊ฐ€ V2 ํ•„์ˆ˜ ํ•ญ๋ชฉ
์กฐํšŒ p95๊ฐ€ ๋ฐ์ดํ„ฐ๋Ÿ‰ ์ฆ๊ฐ€์— ๋น„๋ก€ /api/lab/posts๋Š” ์ „์ฒด ์ •๋ ฌ์ด๋ฏ€๋กœ ์บ์‹œยท์ธ๋ฑ์ŠคยทํŽ˜์ด์ง• ์šฐ์„ 

ํ”ฝ์Šค์ฒ˜ ์ฃผ์˜: saveMessage๋Š” ๋ฉ”์‹œ์ง€๋งˆ๋‹ค SELECT chat_hold_ms๋ฅผ ํ•œ ๋ฒˆ ๋” ์ˆ˜ํ–‰ํ•˜๋ฏ€๋กœ ์‹ค์ œ ์•ฑ๋ณด๋‹ค ์ปค๋„ฅ์…˜ ์ ์œ ๊ฐ€ ๊ธธ๋‹ค. ์ ˆ๋Œ€ msg/s๊ฐ€ ์•„๋‹ˆ๋ผ ์กฐ๊ฑด ๊ฐ„ ๋น„๊ต๋กœ ํ•ด์„ํ•œ๋‹ค.

7. F4. ์ธ๊ธฐ ํŒŸ ๊ฒฝํ•ฉ๊ณผ ํ’€ ์ ์œ ๊ฐ€ ๋‹ค๋ฅธ API์— ์ฃผ๋Š” ์˜ํ–ฅ

์Šน์ธ API๋Š” ํŒŸ ํ–‰์„ SELECT ... FOR UPDATE๋กœ ์žก์œผ๋ฏ€๋กœ ๋™์ผ ํŒŸ ์š”์ฒญ์€ ์ง๋ ฌํ™”๋œ๋‹ค. RPS๊ฐ€ ์•„๋‹ˆ๋ผ ๊ฐ™์€ ํ–‰์— ๋™์‹œ์— ๋„๋‹ฌํ•˜๋Š” ์š”์ฒญ ์ˆ˜๋ฅผ ์ œ์–ดํ•œ๋‹ค.

  • ๋™์ผ ํŒŸ: REQUEST_GROUP=hotRowSame, ๋Œ€์กฐ๊ตฐ: hotRowDistributed
  • ๋™์‹œ์„ฑ: 2 ยท 5 ยท 10 ยท 20 ยท 50 ยท 100
  • lock_hold_ms: 0 ยท 100 ยท 500 (f4-L0, f4-L100, f4-L500 run)
  • ๋ณ‘ํ–‰ REST: REQUEST_GROUP=light์™€ detail ๊ฐ๊ฐ 50 RPS
RUN_ID=f4-same-c20-L100 REQUEST_GROUP=hotRowSame VERIFY_GROUP=hotRowVerify \
  CONCURRENCY=20 ROUNDS=3 ROUND_PAUSE=10s \
  DATA_FILE=./data/test-data-f4-L100.json \
  loadtest-target/k6.sh hot-row.js --summary-export=results/f4-same-c20-L100.json

๋‘ ์‹คํŒจ ๋ชจ๋“œ๋ฅผ ๋ฐ˜๋“œ์‹œ ๊ตฌ๋ถ„ํ•œ๋‹ค. ์„ค์ •์ƒ ์ž„๊ณ„๊ฐ€ ๋‹ค๋ฅด๋‹ค.

ํ’€ timeout : ๋™์‹œ์„ฑ > HIKARI_MAX_POOL_SIZE(10) โ†’ ๋Œ€๊ธฐ 1,000ms ์ดˆ๊ณผ โ†’ 503 database_error
             ์ฆ๊ฑฐ: hikaricp_connections_pendingโ†‘, ํš๋“ ์‹œ๊ฐ„ p95 โ‰ˆ 1s, lab_seat_errors_totalโ†‘
lock wait  : ๋™์‹œ์„ฑ ร— lock_hold_ms > innodb_lock_wait_timeout(3,000ms) โ†’ 503
             ์ฆ๊ฑฐ: innodb_row_lock_current_waitsโ†‘, lab_seat_lock p95 โ‰ˆ 3s

์˜ˆ์ธก ์ž„๊ณ„(์‹ค์ธก์œผ๋กœ ํ™•์ธ): lock_hold_ms=500์€ ๋™์‹œ์„ฑ 6๋ถ€ํ„ฐ, 100์€ ๋™์‹œ์„ฑ 30๋ถ€ํ„ฐ lock wait timeout ์˜์—ญ์— ๋“ค์–ด๊ฐ„๋‹ค. ๋™์‹œ์„ฑ 10์„ ๋„˜๋Š” ์ˆœ๊ฐ„๋ถ€ํ„ฐ๋Š” ํ’€ timeout์ด ๋จผ์ € ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, HIKARI_MAX_POOL_SIZE๋ฅผ 10์œผ๋กœ ๊ณ ์ •ํ•œ ๊ฒฐ๊ณผ์™€ 20์œผ๋กœ ์˜ฌ๋ฆฐ ๋Œ€์กฐ ์‹คํ–‰์„ ํ•œ ์Œ ๋‚จ๊ฒจ ๋‘ ์›์ธ์„ ๋ถ„๋ฆฌํ•œ๋‹ค.

์ •ํ•ฉ์„ฑ ๊ฒ€์ฆ(ํ•œ ๊ฑด์ด๋ผ๋„ ์œ„๋ฐ˜์ด๋ฉด ์‹คํŒจ):

  • hotRowVerify(/api/lab/runs/{run}/stats)์˜ parties.N.remaining >= 0, parties.N.approved <= capacity
  • k6 assertion์œผ๋กœ๋Š” ํ•ญ๋“ฑ์‹์„ ํ‘œํ˜„ํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ, round ์ข…๋ฃŒ ํ›„ stats ์‘๋‹ต์„ ์ €์žฅํ•ด approved + remaining == capacity ๋ฅผ ๋ณ„๋„๋กœ ํ™•์ธํ•œ๋‹ค
  • f4-cap4 run(์ •์› 4์„)์œผ๋กœ ์ดˆ๊ณผ ์Šน์ธยท์ค‘๋ณต ์Šน์ธ์„ ์žฌํ˜„ ์‹œ๋„ํ•œ๋‹ค. ์Šคํ‚ค๋งˆ์— CHECK (remaining >= 0)๊ฐ€ ์žˆ์–ด ์œ„๋ฐ˜์ด ์Œ์ˆ˜ ๊ฐ’์ด ์•„๋‹ˆ๋ผ 503์œผ๋กœ ๋‚˜ํƒ€๋‚  ์ˆ˜ ์žˆ์œผ๋‹ˆ, 503 ์›์ธ์„ ํ’€ยทlockยทCHECK ์ค‘ ๋ฌด์—‡์ธ์ง€ ๋กœ๊ทธ๋กœ ๊ตฌ๋ถ„ํ•œ๋‹ค

ํŒ์ •: ๋™์ผ ํŒŸ์—์„œ๋งŒ ์ง€์—ฐ์ด ๊ธ‰์ฆํ•˜๊ณ  ๋Œ€์กฐ๊ตฐ์€ ํ‰ํƒ„ํ•˜๋ฉด hot-row๊ฐ€ ์›์ธ์ด๋‹ค. ์ด๋•Œ ๋ณ‘ํ–‰ REST(light)๊นŒ์ง€ ์•…ํ™”๋˜๋ฉด ๊ฒฝํ•ฉ์ด ์ปจํ…Œ์ด๋„ˆ ์Šค๋ ˆ๋“œยทCPU๊นŒ์ง€ ๋ฒˆ์ง„ ๊ฒƒ์ด๊ณ , detail๋งŒ ์•…ํ™”๋˜๋ฉด ํ’€ ๊ฒฝํ•ฉ์— ๊ตญํ•œ๋œ๋‹ค. ์ „์ž๋Š” ์Šน์ธ ๊ฒฝ๋กœ ๊ตฌ์กฐ ๋ณ€๊ฒฝ(Redis ์›์ž ์นด์šดํ„ฐยท๋ฉฑ๋“ฑํ‚คยท๋Œ€๊ธฐ์—ด), ํ›„์ž๋Š” ํ’€ ๋ถ„๋ฆฌยทํƒ€์ž„์•„์›ƒ ์กฐ์ •์ด ์šฐ์„ ์ด๋‹ค.

8. F5. ๋‹จ์ˆœ ์กฐํšŒ RPS ์ฆ๊ฐ€์— ๋”ฐ๋ฅธ ์ปจํ…Œ์ด๋„ˆ ์„ฑ๋Šฅ ๋ณ€ํ™” (t3.medium)

๋‘ ๊ณ„์—ด์„ ๊ฐ™์€ ๋‹จ๊ณ„๋กœ ์˜ฌ๋ ค Spring ์ž์ฒด ๋น„์šฉ๊ณผ DB ๊ฒฝ๋กœ ๋น„์šฉ์„ ๋ถ„๋ฆฌํ•œ๋‹ค.

  • light = /api/probe (DBยท์™ธ๋ถ€ ํ˜ธ์ถœ ์—†์Œ)
  • detail = /api/lab/posts/{id} (DB 1ํ–‰ ์กฐํšŒ, ํ’€ ์‚ฌ์šฉ)
  • ์ฐธ๊ณ ๋กœ list = /api/lab/posts (์ „์ฒด ์ •๋ ฌ + LIMIT 20, ๋ฐ์ดํ„ฐ๋Ÿ‰ ๋ฏผ๊ฐ)
  • RPS: 25 โ†’ 40 โ†’ 46 โ†’ 53 โ†’ 75 โ†’ 100 โ†’ 150 โ†’ 200 โ†’ 300 โ†’ 400 โ†’ 512
  • WS ์—ฐ๊ฒฐ 0, ๋ณ„๋„ REST ๋ฐฐ๊ฒฝ ๋ถ€ํ•˜ ์—†์Œ(F1-c์™€์˜ control๋กœ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค)
RUN_ID=f5-light-100 REQUEST_GROUP=light TARGET_RPS=100 WARMUP=5m DURATION=10m \
  loadtest-target/k6.sh http-operation.js \
  --summary-export=results/f5-light-100.json --out json=results/f5-light-100-raw.json

๊ธฐ๋ก ํ‘œ:

๋ชฉํ‘œ RPS ์‹ค์ œ RPS p50 p95 p99 ์˜ค๋ฅ˜์œจ dropped backend CPU(core) throttled s/s ์š”์ฒญ๋‹น CPU-ms Tomcat busy heap GC pause nginx CPU ํ˜ธ์ŠคํŠธ CPU credit

ํŒ์ •:

  • ์š”์ฒญ๋‹น CPU-ms๊ฐ€ RPS ์ „ ๊ตฌ๊ฐ„์—์„œ ์ผ์ •ํ•˜๋ฉด ์„ฑ๋Šฅ ์ €ํ•˜๋Š” CPU ๋‹จ๊ฐ€๊ฐ€ ์•„๋‹ˆ๋ผ ํ์ž‰์ด๋‹ค. quota ์ด๋ก  ์ƒํ•œ RPS = 750 รท ์š”์ฒญ๋‹น CPU-ms์™€ ์‹ค์ธก C_knee๋ฅผ ๋น„๊ตํ•ด quota ํฌํ™” ์—ฌ๋ถ€๋ฅผ ํ™•์ •ํ•œ๋‹ค.
  • container_cpu_cfs_throttled_seconds_total์ด ์˜ค๋ฅด๊ธฐ ์‹œ์ž‘ํ•˜๋Š” RPS๊ฐ€ 0.75 CPU quota์˜ ์‹ค์งˆ ํ•œ๊ณ„๋‹ค. ์ด ์ง€์ ์ด ๋ชฉํ‘œ ํ”ผํฌ(50 RPS)์˜ ๋ช‡ ๋ฐฐ์ธ์ง€๊ฐ€ V1 ์—ฌ์œ ์œจ์ด๋‹ค.
  • light๋Š” ํ‰ํƒ„ํ•œ๋ฐ detail๋งŒ ๊บพ์ด๋ฉด ํ•œ๊ณ„๋Š” Spring์ด ์•„๋‹ˆ๋ผ DB ๊ฒฝ๋กœ(ํ’€ยท์ฟผ๋ฆฌ)๋‹ค.
  • ํ˜ธ์ŠคํŠธ CPU๊ฐ€ baseline 20%๋ฅผ ๋„˜๋Š” ๊ตฌ๊ฐ„์—์„œ credit์ด ์ค„์–ด๋“œ๋Š”์ง€ ํ™•์ธํ•˜๊ณ , credit ๊ณ ๊ฐˆ๋กœ ์ธํ•œ ์ €ํ•˜๋Š” C_knee์—์„œ ์ œ์™ธํ•œ๋‹ค. ํ•„์š”ํ•˜๋ฉด C_safe์—์„œ 60๋ถ„ soak์„ ๋Œ๋ ค credit ์˜ํ–ฅ๊นŒ์ง€ ํฌํ•จํ•œ ์ง€์† ๊ฐ€๋Šฅ RPS๋ฅผ ๋”ฐ๋กœ ๋‚จ๊ธด๋‹ค.

9. ์‹คํ–‰ ์ˆœ์„œ์™€ ์˜ˆ์ƒ ์†Œ์š”

๋‹จ๊ณ„ ์‹คํ–‰ ์ˆ˜ 1ํšŒ ๊ธธ์ด ์†Œ๊ณ„
F0 ์ค€๋น„ยท๋ฐœ์ƒ๊ธฐ ๊ฒ€์ฆ - - 1.5 h
F1-a ์—ฐ๊ฒฐ ์ˆ˜ (b0 8๋‹จ๊ณ„ + b32768 3๋‹จ๊ณ„) 11 ramp + 30m hold 7.5 h
F1-b ์—ฐ๊ฒฐ๋ฅ  6 15 m 1.5 h
F1-c REST ๊ฐ„์„ญ (control + 5๋‹จ๊ณ„ ร— 2๊ณ„์—ด) 11 15 m 3.0 h
F2 ๋ฉ”์‹œ์ง€ ์œ ํ˜• (echo 8์…€ + ์ €์žฅ 4์…€) 12 15 m 3.0 h
F3 DB ์ ์œ  (3 hold ร— 3 msg/s) 9 15 m 2.5 h
F4 hot-row (6 ๋™์‹œ์„ฑ ร— 3 hold + ๋Œ€์กฐ๊ตฐยท์ •ํ•ฉ์„ฑ) 24 5 m 2.5 h
F5 ์กฐํšŒ RPS (11๋‹จ๊ณ„ ร— 2๊ณ„์—ด) 22 15 m 5.5 h
์ž„๊ณ„ ๊ทผ์ฒ˜ 3ํšŒ ๋ฐ˜๋ณตยท์žฌ์ธก์ • ์—ฌ์œ  - - 6.0 h

์ด ์•ฝ 33์‹œ๊ฐ„, ํ•˜๋ฃจ 8์‹œ๊ฐ„ ๊ธฐ์ค€ 4~5์ผ์ด๋‹ค. ์ˆœ์„œ๋Š” F0 โ†’ F5(๊ธฐ์ค€์„ ) โ†’ F1-a โ†’ F1-b โ†’ F1-c โ†’ F2 โ†’ F3 โ†’ F4๋‹ค. F5๋ฅผ ๋จผ์ € ํ•˜๋Š” ์ด์œ ๋Š” WS ์—†๋Š” REST ๊ธฐ์ค€์„ ์ด F1-cยทF3ยทF4์˜ control๋กœ ์“ฐ์ด๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ธก์ • ์ฐฝ์ด 2์‹œ๊ฐ„๋ฟ์ธ ๊ฒฝ์šฐ๋Š” ยง13์˜ ์••์ถ• ์‹คํ–‰์•ˆ์„ ์“ด๋‹ค. ์ด ์ ˆ์˜ 33์‹œ๊ฐ„ ๊ณ„ํš์€ ์ฐฝ์ด ๋ฉฐ์น  ํ™•๋ณด๋  ๋•Œ์˜ ๊ธฐ์ค€์ด๋‹ค.

10. ์ค‘๋‹จ ์กฐ๊ฑด

402 ยง4.2๋ฅผ ๊ทธ๋Œ€๋กœ ์ ์šฉํ•˜๊ณ  ๋‹ค์Œ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

  • backend ์ปจํ…Œ์ด๋„ˆ OOM-kill ๋˜๋Š” ์žฌ์‹œ์ž‘ (mem_limit 768m ์ดˆ๊ณผ)
  • CPUCreditBalance๊ฐ€ 0์— ๋„๋‹ฌ (ํ•ด๋‹น ๋‹จ๊ณ„๋Š” ๋ฌดํšจ ์ฒ˜๋ฆฌํ•˜๊ณ  ํšŒ๋ณต ํ›„ ์žฌ์ธก์ •)
  • approved + remaining != capacity ๋˜๋Š” ์ค‘๋ณต ์Šน์ธ 1๊ฑด
  • nginx 502/504๊ฐ€ 1๋ถ„ ์ด์ƒ ์ง€์† (upstream ๋‹จ์ ˆ โ†’ ์ƒํƒœ ๋ณด์กด ํ›„ ์ค‘๋‹จ)
  • ๋กœ์ปฌ ๋ฐœ์ƒ๊ธฐ CPU 60% ์ดˆ๊ณผ, swap ๋ฐœ์ƒ, dropped iterations ๋ฐœ์ƒ

11. ๊ฒฐ๊ณผ ๊ธฐ๋ก ๊ทœ์น™

RUN_ID = <๋‹จ๊ณ„>-<์ถ•๊ฐ’>-<๋ฐ˜๋ณต> (์˜ˆ: f1a-c1800-b0-r2, f2-C6-echo-r1, f4-same-c50-L500-r3). ๊ฐ ์‹คํ–‰์€ k6 summary JSON๊ณผ raw JSON์„ loadtest-scenarios/results/์—, Grafana ๊ตฌ๊ฐ„ ์Šคํฌ๋ฆฐ์ƒท๊ณผ jcmd ์ถœ๋ ฅ์„ ๊ฐ™์€ RUN_ID ์ด๋ฆ„์œผ๋กœ ๋‚จ๊ธด๋‹ค. ์‹คํ–‰๋งˆ๋‹ค target.env ๊ฐ’, ์ด๋ฏธ์ง€ digest, ํ”ฝ์Šค์ฒ˜ run ์„ค์ •, credit ์ƒํƒœ, ๋ฐœ์ƒ๊ธฐ headroom์„ ํ•จ๊ป˜ ๊ธฐ๋กํ•œ๋‹ค.

์ตœ์ข… ๋ณด๊ณ ์„œ์—๋Š” ยง2.2์˜ ํŒŒ์ƒ๊ฐ’ ํ‘œ์™€ ๋‹จ๊ณ„๋ณ„ C_knee/C_safe, ๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜ ๊ฒฐ์ •ํ‘œ๋ฅผ ์ฑ„์šด๋‹ค.

๊ด€์ธก ์ฆ๊ฑฐ V2 ๊ฒฐ์ • ํ›„๋ณด
์—ฐ๊ฒฐ๋‹น heapยทdirect ๋น„์šฉ์ด ์ปค์„œ ์˜ˆ์ธก ํ”ผํฌ ์ „์— -Xmx512mยท768 MB๋ฅผ ์œ„ํ˜‘ WS ํ”„๋กœ์„ธ์Šค ๋ถ„๋ฆฌ ๋˜๋Š” backend ๋ฉ”๋ชจ๋ฆฌ ์ฆ์„ค
handshake๋งŒ ์•…ํ™”๋˜๊ณ  nginx CPU๊ฐ€ ๋จผ์ € ํฌํ™” nginx workerยทCPU ์žฌ๋ฐฐ๋ถ„, TLS ์ข…๋ฃŒ ์œ„์น˜ ๋ณ€๊ฒฝ
WS ์—ฐ๊ฒฐ๋งŒ์œผ๋กœ REST p95๊ฐ€ 20% ์ด์ƒ ์•…ํ™” RESTยทWS ๋ถ„๋ฆฌ (V2 ์„ค๊ณ„ ํ™•์ • ๊ทผ๊ฑฐ)
๋ฉ”์‹œ์ง€ ๊ฑด์ˆ˜ ๋น„์šฉ(b)์ด ์ง€๋ฐฐ ๋ฐฐ์นญยท์ €์žฅ ๋น„๋™๊ธฐํ™”ยทACK ๊ฒฝ๋Ÿ‰ํ™”
๋ฉ”์‹œ์ง€ ๋ฐ”์ดํŠธ ๋น„์šฉ(c)์ด ์ง€๋ฐฐ payload ์ƒํ•œยท์••์ถ•ยท๋ณธ๋ฌธ ๋ถ„๋ฆฌ
์ €์žฅ ์กฐ๊ฑด์—์„œ๋งŒ ํ’€ pendingยทREST 503 ์ฑ„ํŒ… ์ €์žฅ ๋น„๋™๊ธฐํ™”, ํ’€ ๋ถ„๋ฆฌ, ์กฐํšŒ ์บ์‹œ
๋™์ผ ํŒŸ์—์„œ๋งŒ ์ง€์—ฐ ๊ธ‰์ฆยท503 Redis ์›์ž ์นด์šดํ„ฐยท๋ฉฑ๋“ฑํ‚คยท๋Œ€๊ธฐ์—ด๋กœ hot-row ๊ตฌ์กฐ ๋ณ€๊ฒฝ
์กฐํšŒ RPS์—์„œ throttling์ด ๋ชฉํ‘œ ํ”ผํฌ ๊ทผ์ฒ˜์—์„œ ์‹œ์ž‘ ์ธ์Šคํ„ด์Šค ์ƒํ–ฅ ๋˜๋Š” ์ปจํ…Œ์ด๋„ˆ quota ์žฌ๋ฐฐ๋ถ„
credit ๊ณ ๊ฐˆ์ด ์ง€์† ์„ฑ๋Šฅ์„ ์ขŒ์šฐ unlimited ๊ณ ์ • ๋˜๋Š” ๋น„๋ฒ„์Šคํ„ฐ๋ธ” ์ธ์Šคํ„ด์Šค

12. ์ด ๊ณ„ํš์œผ๋กœ ๋‹ตํ•  ์ˆ˜ ์—†๋Š” ํ•ญ๋ชฉ

ํ”ฝ์Šค์ฒ˜(perf/ws-lab/demo)์˜ ๊ตฌ์กฐ์  ํ•œ๊ณ„์ด๋ฏ€๋กœ ๊ฒฐ๋ก ์— ๋ช…์‹œํ•œ๋‹ค.

  • fan-out์ด ์—†๋‹ค. ํ•ธ๋“ค๋Ÿฌ๋Š” ๋ฐœ์‹ ์ž์—๊ฒŒ๋งŒ ACK ๋˜๋Š” echo๋ฅผ ๋ณด๋‚ด๊ณ  ์„ธ์…˜ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌยท๋ฐฉ ๊ฐœ๋…์ด ์—†๋‹ค. ๋”ฐ๋ผ์„œ "์ˆ˜์‹ ์ž 1ยท3ยท9๋ช…"๊ณผ "ํ•œ ๋ฐฉ ์ง‘์ค‘ โ†” ์—ฌ๋Ÿฌ ๋ฐฉ ๋ถ„์‚ฐ" ์ถ•์€ ์ธก์ • ๋ถˆ๊ฐ€๋‹ค. ํ•„์š”ํ•˜๋ฉด ํ•ธ๋“ค๋Ÿฌ์— ๋ฐฉยท์„ธ์…˜ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๋ณ„๋„ ์ž‘์—…์ด ์„ ํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค(402 T10์˜ ํ•ด๋‹น ํ•ญ๋ชฉ๋„ ๊ฐ™์€ ์ œ์•ฝ์„ ๋ฐ›๋Š”๋‹ค).
  • ์ธ์ฆยท๊ถŒํ•œ ๊ฒฝ๋กœ๊ฐ€ ์—†๋‹ค(tokens ๋น„์–ด ์žˆ์Œ). ์‹ค์ œ ์•ฑ์˜ ํ† ํฐ ๊ฒ€์ฆ CPU๋Š” ํฌํ•จ๋˜์ง€ ์•Š๋Š”๋‹ค.
  • ํ˜ผ์žก๋„ ์™ธ๋ถ€ API(FastAPIยทLLM) ํ˜ธ์ถœ์ด ์—†๋‹ค. 402 T5๋Š” ์ด ๊ณ„ํš ๋ฒ”์œ„ ๋ฐ–์ด๋‹ค.
  • ์ฟผ๋ฆฌ ๋ณต์žก๋„ยท๋ฐ์ดํ„ฐ๋Ÿ‰์ด ์‹ค์ œ ์Šคํ‚ค๋งˆ์™€ ๋‹ค๋ฅด๋‹ค. /api/lab/posts๋Š” ์ „์ฒด ์ •๋ ฌ ์ด๋ฏ€๋กœ ๋ฐ์ดํ„ฐ๋Ÿ‰์— ๋ฏผ๊ฐํ•˜๊ณ , /api/lab/posts/{id}๋Š” ์‹ค์ œ ์ƒ์„ธ ์กฐํšŒ๋ณด๋‹ค ๋‹จ์ˆœํ•˜๋‹ค.
  • saveMessage์˜ ์ถ”๊ฐ€ SELECT, chat_hold_msยทlock_hold_ms์˜ Thread.sleep์€ ํ•ฉ์„ฑ ์ง€์—ฐ์ด๋‹ค. ์ ˆ๋Œ€๊ฐ’์ด ์•„๋‹ˆ๋ผ ์กฐ๊ฑด ๊ฐ„ ์ƒ๋Œ€ ๋น„๊ต๋กœ๋งŒ ์‚ฌ์šฉํ•œ๋‹ค.

13. 2์‹œ๊ฐ„ ์ฐฝ ์••์ถ• ์‹คํ–‰์•ˆ

13.1 ์‹œ๊ฐ„ ์‚ฐ์ • โ€” 2์‹œ๊ฐ„ ์ค‘ ์‹ค์ œ๋กœ ๋ถ€ํ•˜์— ์“ธ ์ˆ˜ ์žˆ๋Š” ์‹œ๊ฐ„

2์‹œ๊ฐ„์„ ๊ทธ๋Œ€๋กœ ์ธก์ •์— ์“ธ ์ˆ˜ ์—†๋‹ค. ์ค„์ผ ์ˆ˜ ์—†๋Š” ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋จผ์ € 40๋ถ„์„ ๊ฐ€์ ธ๊ฐ„๋‹ค.

ํ•ญ๋ชฉ ์‹œ๊ฐ„ ์ค„์ผ ์ˆ˜ ์—†๋Š” ์ด์œ 
์ค€๋น„ (status, credit ๊ธฐ๋ก, ๋ฐ์ดํ„ฐ ํŒŒ์ผ, run seed, smoke) 10๋ถ„ ํ”ฝ์Šค์ฒ˜ run๊ณผ payload๋ณ„ DATA_FILE์ด ์—†์œผ๋ฉด ์‹œ์ž‘ ์ž์ฒด๊ฐ€ ๋ถˆ๊ฐ€
JVM ์›Œ๋ฐ์—… 3๋ถ„ JITยทํด๋ž˜์Šค๋กœ๋”ฉ์ด ๋๋‚˜๊ธฐ ์ „ 60~90์ดˆ๋Š” ๋Œ€ํ‘œ์„ฑ์ด ์—†๋‹ค. ์ด๊ฑธ ๊ฑด๋„ˆ๋›ฐ๋ฉด ์ฒซ ๋ธ”๋ก ์ „์ฒด๊ฐ€ ๋ฒ„๋ ค์ง„๋‹ค
๋ธ”๋ก ๊ฐ„ ๊ณต๋ฐฑ (12ํšŒ ร— 45~90์ดˆ) 12๋ถ„ ๊ณต๋ฐฑ์ด ๊ฐ ๋ธ”๋ก์˜ control ๊ตฌ๊ฐ„์ด๋‹ค. ์—†์• ๋ฉด "๊ฐ„์„ญ"์„ ์ฆ๋ช…ํ•  ๋Œ€์กฐ๊ตฐ์ด ์‚ฌ๋ผ์ง„๋‹ค
๋งˆ๋ฌด๋ฆฌ (ํŒŒ์ƒ๊ฐ’ ์ถ”์ถœ, ์ •ํ•ฉ์„ฑ ํ™•์ธ, ํŒ์ •ํ‘œ) 20๋ถ„ ์ธก์ • ์งํ›„์— ์ฑ„์šฐ์ง€ ์•Š์œผ๋ฉด ํ•ด์„ ๋ถˆ๊ฐ€ํ•œ ์ˆซ์ž ๋”๋ฏธ๋งŒ ๋‚จ๋Š”๋‹ค
์˜ค๋ฒ„ํ—ค๋“œ ํ•ฉ 45๋ถ„
๋ถ€ํ•˜ ์‹คํ–‰ ๊ฐ€๋Šฅ ์‹œ๊ฐ„ ์•ฝ 75๋ถ„ ์ด ์ค‘ ์œ ํšจ ์ธก์ •์ฐฝ(warm-upยทramp ์ œ์™ธ)์€ ์•ฝ 55๋ถ„

์ „์ œ: ํ”ฝ์Šค์ฒ˜๊ฐ€ ์ด๋ฏธ ๋ฐฐํฌ๋˜์–ด ์žˆ๊ณ  exporterยทPrometheusยทGrafana๊ฐ€ ์‚ด์•„ ์žˆ๋‹ค. deploy.sh up๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์•ผ ํ•˜๋ฉด ๋นŒ๋“œยท์ „์†กยท๊ธฐ๋™์— 20๋ถ„ ์ด์ƒ์ด ๋” ๋“ค๊ณ  ์ด ์‚ฐ์ •์€ ์„ฑ๋ฆฝํ•˜์ง€ ์•Š๋Š”๋‹ค. ๋ฐฐํฌ๋Š” 2์‹œ๊ฐ„ ์ฐฝ ๋ฐ–์—์„œ ๋๋‚ด ๋‘”๋‹ค.

13.2 75๋ถ„์„ ๋ฐฐ๋ถ„ํ•˜๋Š” ์„ธ ๊ฐ€์ง€ ์••์ถ• ๊ธฐ๋ฒ•

  1. ๋ฐฐ๊ฒฝ ๋ถ€ํ•˜๋ฅผ ํ•„์š”ํ•œ ๋ธ”๋ก์—๋งŒ ์ผ ๋‹ค. F1-aยทF1-bยทF2 echoยทF5๋Š” REST ์—†์ด ์‹คํ–‰ํ•œ๋‹ค. F1-cยทF3ยทF4์—์„œ๋งŒ REST control/treatment๋ฅผ ์‹คํ–‰ํ•œ๋‹ค. ๋ฐฐ๊ฒฝ์„ ๊ณ„์† ์œ ์ง€ํ•˜๋ฉด ์ˆœ์ˆ˜ ์—ฐ๊ฒฐยท๋ฉ”์‹œ์ง€ ๋‹จ๊ฐ€์™€ REST ๊ฒฝํ•ฉ ๋น„์šฉ์ด ์„ž์ด๋ฏ€๋กœ, ์••์ถ• ์‹คํ–‰์˜ ํŽธ์˜๋ณด๋‹ค ๋น„๊ต ๊ฐ€๋Šฅ์„ฑ์„ ์šฐ์„ ํ•œ๋‹ค.
  2. ๋žจํ”„ ์ž์ฒด๋ฅผ ๋…๋ฆฝ๋ณ€์ˆ˜๋กœ ์“ด๋‹ค. ์—ฐ๊ฒฐ ์ˆ˜๋ฅผ 8๋‹จ๊ณ„๋กœ ๋‚˜๋ˆ  ๊ฐ๊ฐ 30๋ถ„ ์œ ์ง€ํ•˜๋Š” ๋Œ€์‹ , 0โ†’3,000์„ 12.5๋ถ„์— ๊ฑธ์ณ ์„ ํ˜•์œผ๋กœ ์˜ฌ๋ฆฌ๊ณ  lab_ws_connections์— ๋Œ€ํ•ด heapยทdirectยทCPU๋ฅผ ํšŒ๊ท€ํ•œ๋‹ค. 5์ดˆ scrape์ด๋ฏ€๋กœ ํ‘œ๋ณธ ์•ฝ 150์ ์ด ๋‚˜์˜ค๊ณ , ์—ฐ๊ฒฐ๋‹น ๋‹จ๊ฐ€(๊ธฐ์šธ๊ธฐ)๋Š” ๋‹จ๊ณ„๋ณ„ ํ‰๊ท ๋ณด๋‹ค ์˜คํžˆ๋ ค ์•ˆ์ •์ ์ด๋‹ค. ์ด ๋žจํ”„๋Š” REST ์—†์ด F1-a๋ฅผ ์ธก์ •ํ•˜๊ณ , ๊ฐ™์€ ์—ฐ๊ฒฐ ์ˆ˜์ค€์„ ๋ณ„๋„ REST treatment์—์„œ ์žฌํ˜„ํ•ด F1-c๋ฅผ ์ธก์ •ํ•œ๋‹ค.
  3. ์งง์€ ์ฐฝ์—์„œ๋Š” ์ด๋ฒคํŠธ์œจ์„ ์˜ฌ๋ฆฐ๋‹ค. 90~120์ดˆ ์ธก์ •์—์„œ 36 msg/s๋Š” ํ‘œ๋ณธ์ด ๋„ˆ๋ฌด ์ ๋‹ค. ๋ฉ”์‹œ์ง€ ์ถ•์€ 120 msg/s๋กœ ๊ณ ์ •ํ•˜๊ณ  ์—ฐ๊ฒฐ ์ˆ˜ยทpayload๋งŒ ๋ฐ”๊พผ๋‹ค. RPS ๊ณ„๋‹จ๋„ 90์ดˆ(warm-up 15์ดˆ + ์ธก์ • 75์ดˆ)๋กœ ์ž๋ฅธ๋‹ค.

13.3 ์‹คํ–‰ ์Šค์ผ€์ค„

์•„๋ž˜๋Š” REST ๋ฐฐ๊ฒฝ ๋ถ€ํ•˜๋ฅผ ยง1.5์— ๋งž๊ฒŒ ๋ชฉ์ ๋ณ„๋กœ ๋ถ„๋ฆฌํ•œ ์Šค์ผ€์ค„์ด๋‹ค. loadtest-target/run-2h.sh๊ฐ€ ์ด ์ˆœ์„œ๋ฅผ ๊ทธ๋Œ€๋กœ ์‹คํ–‰ํ•œ๋‹ค(--plan ์ถœ๋ ฅ๊ณผ ๋™์ผ). ๋ธ”๋ก ๋‹จ์œ„ ์ˆ˜๋™ ์‹คํ–‰์€ --block <์ด๋ฆ„>์„ ์“ด๋‹ค.

loadtest-target/run-2h.sh --plan       # ์Šค์ผ€์ค„ ํ™•์ธ (๋ถ€ํ•˜ ์—†์Œ)
loadtest-target/run-2h.sh --prepare    # T+00~10: ๋ฐ์ดํ„ฐ ํŒŒ์ผ, run seed, smoke
loadtest-target/run-2h.sh --run        # T+10~94: ์•„๋ž˜ ํ‘œ ์ „์ฒด๋ฅผ ๋ฌด์ธ ์‹คํ–‰
loadtest-target/run-2h.sh --collect    # T+94~: ๊ตฌ๊ฐ„๋ณ„ ํŒŒ์ƒ๊ฐ’ CSV

loadtest-target/run-2h.sh --block a    # ๊ฐœ๋ณ„ ๋ธ”๋ก๋งŒ (warm a b c d e f1c f g)
๊ตฌ๊ฐ„ ๋‚ด์šฉ ๊ธธ์ด ๋ˆ„์ 
์ค€๋น„ --prepare: status, credit ๊ธฐ๋ก, ๋ฐ์ดํ„ฐ ํŒŒ์ผ 3์ข…, run seed, smoke 10๋ถ„ T+10
warm light 30 RPS. ์ดํ›„ ์ปจํ…Œ์ด๋„ˆ ์žฌ๊ธฐ๋™ ๊ธˆ์ง€ 3๋ถ„ T+13
a REST ์—†์ด ws-connect 0โ†’3,000 ๋žจํ”„ 12.5๋ถ„ + hold 3๋ถ„ โ†’ F1-a 16๋ถ„ T+30
b REST ์—†์ด handshake rate 20 / 100 / 200 conn/s โ†’ F1-b 5๋ถ„ T+36
c REST ์—†์ด ๋ฉ”์‹œ์ง€ ์œ ํ˜• 2ร—2 4์…€ (echo, 120 msg/s) โ†’ F2 16๋ถ„ T+53
d detail control โ†’ WS ์ €์žฅ๋งŒ โ†’ ์ €์žฅ+detail treatment โ†’ F3. ์ €์žฅ ์—†์Œ ๋Œ€์กฐ๊ตฐ์€ c3 ์žฌ์‚ฌ์šฉ 8๋ถ„ T+62
e light control โ†’ hot-row ๋‹จ๋… 4๋‹จ๊ณ„ โ†’ hot-row+detail treatment โ†’ ๋ถ„์‚ฐ ๋Œ€์กฐ๊ตฐ โ†’ F4 9๋ถ„ T+71
f1c light 50 RPS control โ†’ WS 2,100 ์œ ์ง€ + ๋™์ผ REST treatment โ†’ F1-c 4๋ถ„ T+75
f REST ์—†์ด RPS ๊ณ„๋‹จ light 50/150/300/500/700, detail 50/150/300 โ†’ F5 14๋ถ„ T+90
g c3 ๋ฐ˜๋ณต (๋ณ€๋™ํญ ํ™•์ธ) 4๋ถ„ T+94
๋งˆ๋ฌด๋ฆฌ --collect, ์ •ํ•ฉ์„ฑ ํ™•์ธ, ํŒŒ์ƒ๊ฐ’ยทํŒ์ •ํ‘œ 20๋ถ„ T+114

C ๋ธ”๋ก์˜ 4์…€์€ 200conn/256B ยท 200conn/8000B ยท 1800conn/256B ยท 1800conn/8000B๋‹ค. 2ร—2๊ฐ€ ์™„์„ฑ๋˜์–ด์•ผ ยง5์˜ ํšŒ๊ท€๋กœ ๊ฑด์ˆ˜ ๋น„์šฉ b์™€ ๋ฐ”์ดํŠธ ๋น„์šฉ c๋ฅผ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. 3์…€์ด๋ฉด ๊ต์ฐจํ•ญ์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์—†์–ด "ํฐ ์ปจํ…์ŠคํŠธ๊ฐ€ ๋” ๋ฌด๊ฒ๋‹ค" ์ˆ˜์ค€์˜ ์ธ์ƒ๋งŒ ๋‚จ๋Š”๋‹ค. 90๋ถ„ ์ฐฝ์ด๋ผ๋ฉด ์—ฌ๊ธฐ์„œ ํ•œ ์…€์„ ์žƒ๋Š”๋‹ค.

F1-c์˜ REST ๊ฐ„์„ญ ๊ฒฐ๊ณผ๋Š” AยทBยทC์˜ ์ˆœ์ˆ˜ ๋น„์šฉ ๊ฒฐ๊ณผ์™€ ์„ž์ง€ ์•Š๋Š”๋‹ค. ๋ณ„๋„์˜ REST๋งŒ control๊ณผ REST + WS treatment ๊ตฌ๊ฐ„์„ ๋งŒ๋“ค๊ณ , control ๋Œ€๋น„ REST p95ยท์˜ค๋ฅ˜์œจยทTomcatยทHikari ๋ณ€ํ™”๋ฅผ ๋ณด๊ณ ํ•œ๋‹ค.

์‹œ๊ฐ„์ด ๋ฐ€๋ฆด ๋•Œ ๋ฒ„๋ฆฌ๋Š” ์ˆœ์„œ: f์˜ 700 RPS โ†’ g โ†’ b์˜ 200 conn/s โ†’ d์˜ treatment โ†’ f1c. ๋ธ”๋ก a์™€ e๋Š” ๋งˆ์ง€๋ง‰๊นŒ์ง€ ์ง€ํ‚จ๋‹ค. a๋Š” V2์˜ RESTยทWS ๋ถ„๋ฆฌ ๊ฒฐ์ •์— ์ง์ ‘ ์“ฐ์ด๊ณ , e๋Š” 1๋ถ„์งœ๋ฆฌ ์‹คํ–‰์œผ๋กœ ๊ฐ€์žฅ ๊ฐ•ํ•œ ์‹ ํ˜ธ๋ฅผ ์ค€๋‹ค.

13.4 2์‹œ๊ฐ„์— ํฌ๊ธฐํ•˜๋Š” ๊ฒƒ

๊ฒฐ๋ก ์— ๋ฐ˜๋“œ์‹œ ํ•จ๊ป˜ ์ ๋Š”๋‹ค.

  • ๋ฐ˜๋ณต 3ํšŒ๊ฐ€ ์—†๋‹ค. A'์™€ G๊ฐ€ ๊ฐ๊ฐ 1ํšŒ control/treatment ๋˜๋Š” ์žฌ์ธก์ •์ผ ๋ฟ์ด๋‹ค. 90120์ดˆ ๋‹จ์ผ ์ธก์ •์—์„œ p95๋Š” ยฑ2030% ํ”๋“ค๋ฆฌ๋ฏ€๋กœ, ์ง€์—ฐ ์ฐจ์ด๋Š” 30% ์ด์ƒ์ผ ๋•Œ๋งŒ ์‹ค์žฌ๋กœ ์ธ์ •ํ•˜๊ณ , 1์ฐจ ๊ทผ๊ฑฐ๋Š” ๋ณ€๋™์ด ์ž‘์€ ์ž์› ์ง€ํ‘œ(์—ฐ๊ฒฐ๋‹น heap, ๋ฉ”์‹œ์ง€๋‹น CPU-ms, CPU quota ์‚ฌ์šฉ๋ฅ )๋กœ ์‚ผ๋Š”๋‹ค.
  • 30๋ถ„ idle hold๊ฐ€ ์—†๋‹ค. ์—ฐ๊ฒฐ ๋ˆ„์ˆ˜ยทheartbeat ๋ˆ„์ ยท์™„๋งŒํ•œ heap ์ฆ๊ฐ€๋Š” ๊ฒ€์ถœ๋˜์ง€ ์•Š๋Š”๋‹ค. C_safe soak(60~120๋ถ„)๋„ ์ด ์ฐฝ ๋ฐ–์˜ ๊ณผ์ œ๋‹ค.
  • ์„œ๋ฒ„ ์„ค์ • ์ถ•์„ ๊ฑด๋“œ๋ฆด ์ˆ˜ ์—†๋‹ค. HIKARI_MAX_POOL_SIZE, WS_PER_CONNECTION_BYTES, JVM_XMX ๋ณ€๊ฒฝ์€ ์žฌ๋ฐฐํฌ์™€ JVM ๋ƒ‰์‹œ๋™์„ ๋ถ€๋ฅด๊ณ  ๋ธ”๋ก ๊ฐ„ ๋น„๊ต ๊ฐ€๋Šฅ์„ฑ์„ ๊นจ๋œจ๋ฆฐ๋‹ค. ์ด ์ฐฝ์˜ ๋ชจ๋“  ์ถ•์€ k6ยท๋ฐ์ดํ„ฐ ํŒŒ์ผ ์ชฝ์—๋งŒ ๋‘”๋‹ค. ํ’€ ํฌ๊ธฐ ๋Œ€์กฐ(10 โ†” 20)๋Š” ๋ณ„๋„ ์ฐฝ์—์„œ ํ•œ๋‹ค.
  • CPU credit์„ ํ†ต์ œํ•  ์ˆ˜ ์—†๋‹ค. 120๋ถ„ ์—ฐ์† ๋ถ€ํ•˜๋Š” baseline 20%๋ฅผ ๊ณ„์† ๋„˜์œผ๋ฏ€๋กœ credit์„ ์†Œ๋ชจํ•œ๋‹ค. ์‹œ์ž‘ ์ „ CPUCreditBalance๋ฅผ ๊ธฐ๋กํ•˜๊ณ , standard ๋ชจ๋“œ์—์„œ ์ž”๋Ÿ‰์ด ์ ์œผ๋ฉด unlimited๋กœ ๋ฐ”๊พธ๊ฑฐ๋‚˜ ๋ธ”๋ก A๋ฅผ ๋จผ์ € ๋๋‚ธ๋‹ค ๋Š” ์ „์ œ๋กœ ์ง„ํ–‰ํ•œ๋‹ค. balance๊ฐ€ 0์— ๋‹ฟ์€ ๋’ค์˜ ๊ตฌ๊ฐ„์€ ์šฉ๋Ÿ‰ ํ•œ๊ณ„๊ฐ€ ์•„๋‹ˆ๋‹ค.
  • lock_hold_msยทchat_hold_ms๋Š” ๊ฐ๊ฐ ํ•œ ๊ฐ’(100ms / 0ยท50ms)๋งŒ ๋ณธ๋‹ค. ยง6ยทยง7์˜ ๋‹ค๋‹จ๊ณ„ ๋น„๊ต๋Š” ์ด ์ฐฝ์—์„œ ์„ฑ๋ฆฝํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • ยง12์˜ ํ”ฝ์Šค์ฒ˜ ํ•œ๊ณ„(fan-outยท๋ฐฉ ๊ฐœ๋… ์—†์Œ)๋Š” ์‹œ๊ฐ„๊ณผ ๋ฌด๊ด€ํ•˜๊ฒŒ ๊ทธ๋Œ€๋กœ๋‹ค.

13.5 ๋งˆ๋ฌด๋ฆฌ 20๋ถ„์— ํ•˜๋Š” ์ผ

loadtest-target/collect-window.py๊ฐ€ results/runs.tsv์˜ ์‹คํ–‰ ๊ตฌ๊ฐ„๋งˆ๋‹ค Prometheus๋ฅผ ์กฐํšŒํ•ด ยง2.2 ํŒŒ์ƒ๊ฐ’์„ CSV๋กœ ๋งŒ๋“ ๋‹ค. ๋žจํ”„ ๋Ÿฐ(a-, a2-)์€ lab_ws_connections์— ๋Œ€ํ•œ ์ตœ์†Œ์ œ๊ณฑ ํšŒ๊ท€๋กœ ์—ฐ๊ฒฐ๋‹น heapยทdirect bytes์™€ 1,000 ์—ฐ๊ฒฐ๋‹น CPU ์ฝ”์–ด๊นŒ์ง€ ํ•จ๊ป˜ ์ถœ๋ ฅํ•œ๋‹ค.

loadtest-target/run-2h.sh --collect          # PROM_URL ์€ terraform output ์—์„œ ์ž๋™
PROM_URL=http://<monitoring-ip>:9090 loadtest-target/run-2h.sh --collect

CSV๋ฅผ ๋ฐ›์œผ๋ฉด ยง11์˜ ๊ฒฐ์ •ํ‘œ๋ฅผ ๊ทธ ์ž๋ฆฌ์—์„œ ์ฑ„์šด๋‹ค. ์ •ํ•ฉ์„ฑ์€ hot run stats๋กœ ํ™•์ธํ•œ๋‹ค.

curl -sk https://$(cat loadtest-target/.local/public_host)/api/lab/runs/h-L100/stats \
  | python3 -m json.tool
# parties[i].approved + parties[i].remaining == capacity ์ธ์ง€ ํ™•์ธ

--prepare๊ฐ€ .local/fixture-runs.json์˜ base/hot์„ ์ƒˆ run์œผ๋กœ ๋ฎ์–ด์“ฐ๋ฏ€๋กœ, ์ฐฝ์ด ๋๋‚˜๋ฉด ๋˜๋Œ๋ฆฐ๋‹ค.

python3 loadtest-target/make-test-data.py --base-run base-01 --hot-run hot-01
โš ๏ธ **GitHub.com Fallback** โš ๏ธ