406 V1 real server loadtest redesign - 100-hours-a-week/KTB4-3rd-CLOUD GitHub Wiki
406. SLI/SLO ์ ์์ ๋ฐ๋ฅธ ๋ถํํ ์คํธ ์ฝ๋ยท์๋๋ฆฌ์ค ์์
๊ทผ๊ฑฐ: ์ํค ใV1์ ํต์ฌ ์ฌ์ฉ์ ์ฌ์ ๋ณ๋ก SLI/SLO ์ ์ใ, ใ4๋จ๊ณ V2 ํธ๋ํฝ ์ฑ์ฅ์ ๋ฐ๋ฅธ V1 ์ธํ๋ผ ํ๊ณใ,
KTB4-3rd-BE(v1.0.0) ์ฝ๋,deploy/nginx/nginx.conf,deploy/compose.yaml์ฐ์ถ๋ฌผ:loadtest-scenarios/real/(์ ๊ท),loadtest-scenarios/lib/config.jsยทcuj-journey.jsยทws-connect.jsยทws-message.js(์๊ณ๊ฐ ์์ )
1. ๊ธฐ์กด ์ฝ๋์์ ๋ฐ๋์ด์ผ ํ๋ ๊ฒ
1-1. ๋์ API: ํฝ์ค์ฒ โ ์ค์ ์๋ฒ
๊ธฐ์กด (data/test-data.json) |
์ค์ ์๋ฒ | ๋ฐ์ |
|---|---|---|
/api/lab/posts, /api/lab/posts/{n} |
GET /api/map-pins, GET /api/nearby-posts(์ปค์), GET /api/companion-posts/{id}, GET /api/community-posts/{id}ยท/comments |
lib/api.js, lib/read-mix.js |
/api/lab/runs/*/parties/*/approve (์ข์ ์น์ธ) |
V1์๋ ์น์ธ ๋จ๊ณ๊ฐ ์์. ๊ฒฝํฉ ์ง์ ์ POST /api/companion-posts/{id}/participants์ ์กฐ๊ฑด๋ถ UPDATE์ POST /api/taxi-pots์ FOR UPDATE |
hot-row.js, lib/contention.js |
/api/lab/runs/*/stats (ํผ์ก๋) |
V1์ ํผ์ก๋ API ์์ โ ์ง๋ ์ด๋(/api/map-pins)์ผ๋ก ๋์ฒด |
lib/read-mix.js ๊ฐ์ค์น ํ |
/ws?runId= raw WS echo |
/api/wss STOMP: CONNECT(Authorization) โ SUBSCRIBE /sub/chat/{room} โ SEND /pub/chat/{room} |
lib/stomp.js |
| ํ ํฐ ์๋ ์ ๋ ฅ | Kakao OAuth๋ฟ โ ํ
์คํธ ์๋ฒ JWT_SECRET์ผ๋ก HS256(iss=moyeota, sub=userId) ๋ฐ๊ธ |
tools/make-users.py |
| ID ํ๋์ฝ๋ฉ | ๊ณต๊ฐ API๋ก ์๋ ํ fixture.json, ์คํ ์ map-pins๋ก ์ฌ์์ง |
seed.js, discoverTargets() |
1-2. ์ฑ๊ณตยท์คํจ ํ์ : ์ํ์ฝ๋ โ SLI good/bad/์ ์ธ
๊ธฐ์กด์๋ expectedStatuses ๋ชฉ๋ก์ ๋ค์ด๊ฐ๋ฉด ์ฑ๊ณต, ์๋๋ฉด ์คํจ์๋ค. SLI ๋ฌธ์ 6์ฅ ๊ธฐ์ค์ผ๋ก ๋ฐ๊ฟจ๋ค (lib/slo.js classify, lib/api.js RULES).
| ๋ถ๋ฅ | ์ | ๊ฐ์ฉ์ฑ ๋ถ๋ชจ |
|---|---|---|
| good | 2xx, ์๋๋ ๋น ์ํ(current-taxi-pot 200 + null) |
ํฌํจ |
| bad | 5xx, timeoutยทreset(status 0), 409 MATCH_BUSY, nginx 429 |
ํฌํจ |
| ๋๋ฉ์ธ ๊ฑฐ์ | ALREADY_PARTICIPATING, COMPANION_NOT_JOINABLE, BANK_ACCOUNT_REQUIRED, MATCH_ALREADY_IN_PROGRESS, COMPANION_POST_CANCELED(410) ๋ฑ |
์ ์ธ (business_rejections๋ก ๋ณ๋ ์ง๊ณ) |
| ์ ๋ ฅ ์ค๋ฅ | 400 MALFORMED_REQUEST, 422 VALIDATION_ERROR ๋ฑ |
์ ์ธ |
| ํ ์คํธ ๋ฐ์ดํฐ ์ค๋ฅ | 401ยท403ยท404(๊ท์น์ ์๋ ๊ฒ) | ์ ์ธ, test_data_errors==0 ์๊ณ๊ฐ์ผ๋ก ์คํ ๋ฌดํจ ์ฒ๋ฆฌ |
MATCH_BUSY์ ๋๋ฉ์ธ ๊ฑฐ์ ์ด ๊ฐ์ 409๋ผ์ ์ํ์ฝ๋๋ง์ผ๋ก๋ ๊ตฌ๋ถ์ด ์ ๋๋ค. ๊ทธ๋์ ์๋ต ๋ณธ๋ฌธ์ error.code๋ฅผ ์ฝ๋๋ค(๋ณธ๋ฌธ์ ๋ฒ๋ฆฌ์ง ์๋๋ก discardResponseBodies:false).
1-3. ์๊ณ๊ฐ: ์ด์ V2 ์ด์๊ฐ โ V1 ์ด๊ธฐ SLO
| ํญ๋ชฉ | ์ด์ ๊ธฐ๋ณธ๊ฐ | SLO ๋ฌธ์ | ์์ ์์น |
|---|---|---|---|
| ๊ฐ์ฉ์ฑ / ์ค๋ฅ์จ | 99.95% / 0.05% | 99.9% / 0.1% | lib/config.js v2SloThresholds, real/lib/slo.js |
| ์กฐํ ์ง์ฐ | p95 750ms, p99 2s | p95 1s, p99 2.5s | ๋์ผ |
| ์ฐ๊ธฐยท๋งค์นญ ์ง์ฐ | p95 1.2s, p99 2.5s | p95 1.5s, p99 3s | ๋์ผ |
| WS ์ฐ๊ฒฐ ์ฑ๊ณต๋ฅ | 99.95% | 99.9%, 3์ด ์์ CONNECTED | ws-connect.js, ws-message.js, cuj-journey.js, lib/stomp.js |
| ๋ฉ์์ง ์ ๋ฌ | ์ฑ๊ณต 99.99%, p99 1.5s | 99.9%, p99 2s | ๋์ผ |
| CUJ ์๋ฃ์จ | 98.5% | 97% (์๋ ํ์ ํ ๊ธฐ์ค) | cuj-journey.js, real/cuj.js |
| ์ ํฉ์ฑ | ์์ | 100% | hot_row_consistency, taxi_state_fresh |
์ง์ฐ SLO๋ http_req_duration์ด ์๋๋ผ **์ ํจ ์ด๋ฒคํธ๋ง ๋ด๋ api_duration**์ผ๋ก ํ์ ํ๋ค. ๋๋ฉ์ธ ๊ฑฐ์ (๋น ๋ฅธ 409)์ด ์์ด๋ฉด p95๊ฐ ์ค์ ๋ณด๋ค ์ข๊ฒ ๋์ค๊ธฐ ๋๋ฌธ์ด๋ค.
1-4. ์ค์ ์๋ฒ ๊ณ์ฝ ๋๋ฌธ์ ๋ฐ๋ ํด๋ผ์ด์ธํธ ๋์
- ๋ฉ์์ง ์ ๋ฌ ํ์ธ์
content๋ก ๋งค์นญํ๋ค. ๋ธ๋ก๋์บ์คํธ๋๋MessageItem์๋client_message_id๊ฐ ์๋ค(id, type, sender, content, created_at). SLI ๋ฌธ์ P6์ "๊ฐ์client_message_id์ MESSAGE"๋ ํ์ฌ ๊ตฌํ์ผ๋ก๋ ์ธก์ ํ ์ ์๋ค. ๊ทธ๋์ UUID๋ฅผ ๋ฃ์ ๊ณ ์ content๋ก echo๋ฅผ ๋งค์นญํ๋ค. BE์์client_message_id๋ฅผ ๋ด๋ ค์ฃผ๋๋ก ๋ฐ๊พธ๋ ๊ฒ์ ๊ถ์ฅํ๋ค. - SEND ๋ณธ๋ฌธ์
client_message_id(snake_case)๋ก ๋ณด๋ธ๋ค. ์ปฌ๋ผ์ดNOT NULL์ด๋ผ null์ด๋ฉด ์ ์ฅ์ด ์คํจํ๊ณ , ํด๋ผ์ด์ธํธ์๋ ์๋ฌด ์๋ต ์์ด ๋๋๋ค. STOMP ์ปจ๋ฒํฐ์ ๋ค์ด๋ฐ ์ ๋ต์ด ์ ์ฉ๋๋์ง ํ์ธ๋์ง ์์clientMessageId๋ ๊ฐ์ด ๋ณด๋ธ๋ค. - SUBSCRIBE ์งํ 200ms ๊ธฐ๋ค๋ฆฐ ๋ค SENDํ๋ค. inbound ์ฑ๋์ด ์์๋ฅผ ๋ณด์ฅํ์ง ์์(
setPreserveReceiveOrder๋ฏธ์ค์ ) SEND๊ฐ ๊ตฌ๋ ๋ฑ๋ก๋ณด๋ค ๋จผ์ ์ฒ๋ฆฌ๋๋ฉด ์๊ธฐ echo๋ฅผ ๋ชป ๋ฐ๋๋ค. - ๊ตฌ๋
์๋ ๋ฐ๋์ ์ค์ ์ฐธ์ฌ์์ฌ์ผ ํ๋ค. ๋น์ฐธ์ฌ์์ SUBSCRIBE๋ ์๋ฌ ์์ด ๋ฒ๋ ค์ง๋ฏ๋ก,
seed.js๊ฐ ๋ฐฉ์ ๋ง๋ค๊ณ ๋ฉค๋ฒ๋ฅผ ์ฐธ์ฌ์ํจ ๋งคํ๋ง ์ด๋ค. - ๋ชจ์ง์๋ ๋ฑ๋ก ์งํ ๋ฐฉ ID๋ฅผ ์ ์ ์๋ค.
POST /api/companion-posts์๋ต์chat_room_id๊ฐ ์์ด์GET /api/chat-rooms์์companion_id๋ก ์ฐพ๋๋ค(H5 ์ธก์ ๊ตฌ๊ฐ์ ์์ฒญ 1๊ฑด์ด ์ถ๊ฐ๋จ). - ํ์ํ์ ์ขํ 6์๋ฆฌ์ ๋ถ ๋จ์ ์ถ๋ฐ์๊ฐ์ด ์์ ํ ์ผ์นํด์ผ ๋งค์นญ๋๋ค.
@Digits(fraction=6)๋๋ฌธ์ ์ขํ๋ ์์ 6์๋ฆฌ๋ก ๋ฐ์ฌ๋ฆผํ๋ค. CUJ๋ 10๋ถ ์ฌ๋กฏ์ ์จ์ ๊ธฐ์กด ํ ํฉ๋ฅ๋ ์ผ์ด๋๊ฒ ํ๊ณ ,taxi_match_total{match_result}๋ก ํฉ๋ฅ์จ์ ๋ณธ๋ค. - URL ์ ๊ทํ. ์ค์ ID๋ฅผ
nameํ๊ทธ์์ ๋นผ๊ณ/api/companion-posts/{companion_id}ํํ๋ก ๊ธฐ๋กํ๋ค(SLI ๋ฌธ์ 5-1์ ์นด๋๋๋ฆฌํฐ ์์น).
2. ์ค์ ์๋ฒ ์ฝ๋ ๊ธฐ์ค ๋ถํ ์ง์
| ์๋๋ฆฌ์ค | ํธ์ถ API | ์๋ฒ ๋ด๋ถ ๊ฒฝ๋ก | ํ์ธํ ์์ |
|---|---|---|---|
| S1 ์กฐํ ์ฆ๊ฐ | nearby 28 / ์์ธ 28 / map-pins 21 / ์ฐ๊ธฐ 7 / ๊ธฐํ 7 (ํฉ 91 = 91 RPS ๊ธฐ์ค) | HomeService ๋ ํ
์ด๋ธ MBRContains + ST_Distance_Sphere ์ ๋ ฌ, ๋ณํฉ |
Spring CPU, HikariCP, MySQL ๊ณต๊ฐ ์ธ๋ฑ์ค |
| S2 ์ฐ๊ฒฐ ์ฆ๊ฐ | /api/wss CONNECTยทSUBSCRIBE, 1,800์ฐ๊ฒฐยท6/s |
JWT ๋์ฝ๋ฉ, SUBSCRIBE๋ง๋ค ์ฐธ์ฌ์ ์กฐํ ์ฟผ๋ฆฌ | ์ธ์ heapยทGC, ์์ผ, ๊ตฌ๋ ์์ DB |
| S3 ๋ฉ์์ง ์ ์ฅ | SEND 36 msg/s | ์ฐธ์ฌ์ ์กฐํ โ ๋ฐฉยท์ฌ์ฉ์ ์กฐํ โ INSERT โ last_message_id UPDATE (๋ฉ์์ง 1๊ฑด = ์ฟผ๋ฆฌ 4~5๊ฐ, ์ปค๋ฅ์
1๊ฐ) |
HikariCP ์ ์ ยท๋๊ธฐ, MySQL commit |
| S4-a ๋ํ ์ข์ ๊ฒฝํฉ | ๊ฐ์ ๊ธ์ ๋์ ์ฐธ์ฌ 20/50/100 | UPDATE companions SET current_count+1 WHERE current_count<capacity ๊ฐ์ ํ X-lock (์ ์ ์ต๋ 10 โ ๋๋จธ์ง๋ COMPANION_NOT_JOINABLE) |
row lock ๋๊ธฐ, ์ปค๋ฅ์ ์ ์ |
| S4-b ํ์ํ ๊ฒฝํฉ | ๊ฐ์ ์ขํยท๋ถ์ผ๋ก ๋์ ๋งค์นญ | users FOR UPDATE โ companions FOR UPDATE(์์ผ๋ฉด gap lock) โ ์คํจ ์ 3ํ ์ฌ์๋ โ MATCH_BUSY |
lock wait(๊ธฐ๋ณธ 50s), ๋ฐ๋๋ฝ, ํ ์ชผ๊ฐ์ง(taxi_pot_fragmentation) |
| S5 ํผํฉ | ์กฐํ + ws/chat/hotrow | ๊ฐ์ JVMยทHikariCP 10๊ฐ | control ๋๋น treatment์ ์กฐํ p95ยทp99 |
| CUJ | ์ฐธ์ฌ์ยท๋ชจ์ง์ยทํ์ํ E2E | ์ ๊ฒฝ๋ก ์ ๋ถ + STOMP ์ฒซ ๋ฉ์์ง | cuj_reliability 97% |
์ฃผ์ํ ์ฝ๋ ํน์ฑ:
GET /api/map-pins๋ ์๋ฒ์์ ๋ทฐํฌํธ ํฌ๊ธฐ๋ฅผ ๊ฒ์ฆํ์ง ์๊ณ , ์ฟผ๋ฆฌ์ LIMIT๋ ์๋ค. 500๊ฐ ์ด๊ณผ ์ฌ๋ถ๋ ์ ๋ถ ์ฝ์ ๋ค์ ํ๋จํ๋ค. ๋์ ๋ทฐํฌํธ ํ๋๋ก๋ ํ ์ด๋ธ ์ ์ฒด๋ฅผ ์ค์บํ ์ ์์ผ๋ฏ๋ก, ์๋๋ฆฌ์ค๋ ๋ชจ๋ฐ์ผ ํ ํ๋ฉด(0.02ยฐร0.025ยฐ)์ผ๋ก ์ ํํ๋ค. ๋ฐ๋๋ก ์ด API ์์ฒด๋ ๋ณ๋ ๊ฐ์ ๋์์ด๋ค.- ํ์ํ ๊ฒฝํฉ์
innodb_lock_wait_timeout(๊ธฐ๋ณธ 50์ด)๊น์ง ์ปค๋ฅ์ ์ ๋ถ์ก์ ์ ์์ด์ ์์ฒญ ํ์์์์ 60์ด๋ก ๋ฐ๋ก ๋๋ค.
3. ํ ์คํธ ์ ์ ์๋ฒ์์ ํ์ธยท๋ณ๊ฒฝํด์ผ ํ ๊ฒ (์ฝ๋๋ง์ผ๋ก ํด๊ฒฐ ์ ๋จ)
| # | ํญ๋ชฉ | ํ์ฌ (repo ๊ธฐ์ค) | ์ํฅ | ์กฐ์น |
|---|---|---|---|---|
| 1 | nginx limit_req |
IP๋น 20r/s, burst 40 | ๋ฐ์๊ธฐ ํ ๋์์ 20 RPS๋ฅผ ๋์ผ๋ฉด 429 โ 91 RPS ์ธก์ ๋ถ๊ฐ | ํ
์คํธ ๊ธฐ๊ฐ์ ๋ฐ์๊ธฐ IP๋ฅผ geo๋ก ์ ์ธ. ์คํฌ๋ฆฝํธ๋ nginx_rate_limited==0์ผ๋ก ๊ฐ์ง |
| 2 | /api/wss ํ๋ก์ |
location /api/์ ๊ฑธ๋ ค ์๊ณ Upgrade/Connection ํค๋ ๋ฏธ์ ๋ฌ, proxy_read_timeout 60s |
์ ๊ทธ๋ ์ด๋ ์คํจ ๋๋ ์ ํด ์ฐ๊ฒฐ์ด 60์ด ๋ค ๋๊น | location /api/wss๋ฅผ ๋ถ๋ฆฌํด ํค๋ยท3600s ์ ์ฉ. ws_unexpected_close๋ก ๊ฐ์ง |
| 3 | STOMP heartbeat | Simple Broker์ TaskScheduler๊ฐ ์์ด ์๋ฒ heartbeat 0 | ์ ํด ์ฐ๊ฒฐ ์ ์ง๋ nginx ํ์์์์ ๋ฌ๋ ค ์์ | ์ 2๋ฒ๊ณผ ํจ๊ป ์ฒ๋ฆฌ |
| 4 | ์งํ ๋ ธ์ถ | Actuator๋ง ์๊ณ Prometheus registryยทendpoint ๋ ธ์ถ ์ค์ ์์ | HikariCP active/pending, Tomcat, JVM GC๋ฅผ ๋ณผ ์ ์์ | micrometer-registry-prometheus + management.endpoints.web.exposure.include (ํ
์คํธ ์๋ฒ ํ์ ) |
| 5 | ๊ณต๊ฐ ์ธ๋ฑ์ค | ์คํค๋ง๊ฐ repo์ ์์(ddl-auto: validate) |
origin_locationยทlocation์ SPATIAL INDEX๊ฐ ์์ผ๋ฉด S1 ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง |
SHOW INDEX ํ์ธ ํ ๋ฌธ์์ ๊ธฐ๋ก |
| 6 | ํ ์คํธ ์ฌ์ฉ์ | users ํ ์ด๋ธ ์ง์ INSERT | ์ํฐํฐ ์ธ NOT NULL ์ปฌ๋ผ์ด ์์ผ๋ฉด ์คํจ | seed-users.sql ์ ์ฉ ์ SHOW CREATE TABLE users ํ์ธ |
| 7 | ๋ฐ์๊ธฐ ์์น | ๋ก์ปฌ PC 1๋ | ์ง๋ ์ธก์ ์ 1,734์ฐ๊ฒฐ ์ํ ์์ธ์ด ํ์ ๋์ง ์์ | ์ฐ๊ฒฐ ํ ์คํธ๋ ๋ฐ์๊ธฐ 2๋ ๋ถ์ฐ๊ณผ ๋น๊ต |
4. ๋จ์ SLI ๊ณ์ธก (๋ถํํ ์คํธ ๋ฐ)
SLI ๋ฌธ์์ ์๋ฒ ์ธก ์งํ(companion_join_total, taxi_match_retry_total, taxi_match_lock_wait_seconds, after-commit ๋ฉํธ๋ฆญ, Nginx access log์ $request_time)๋ ์์ง BEยท์ธํ๋ผ์ ์๋ค. ์ง๊ธ k6 ๊ฒฐ๊ณผ๋ ํด๋ผ์ด์ธํธ ๊ด์ ์ SLI๋ค. ์๋ฒ ์งํ๊ฐ ๋ค์ด์ค๋ฉด ๊ฐ์ X-Load-Test-Run-Id ํค๋๋ก ๊ตฌ๊ฐ์ ๋ง์ถฐ ๋น๊ตํ๋ค.