900 NPC와 300 synthetic 사용자를 5분 턴으로 처리하는 결정론적 engine profile을 추가하고, 전용 PostgreSQL schema와 Redis DB에 300개 인증 session을 준비하는 격리 load fixture를 제공한다.
154 lines
8.6 KiB
Markdown
154 lines
8.6 KiB
Markdown
# 인증 HTTP/tRPC + SSE 부하 도구
|
|
|
|
이 package는 `docs/architecture/realtime-change-journal.md`의 A1/A2/A3/M1 viewer 부하를
|
|
재현하기 위한 read-only driver다. 300개 game bearer token으로 SSE를 열고, idle/own/global/mixed
|
|
phase에서 실제 game API tRPC query를 실행한다. HTTP latency p50/p95/p99, 성공/오류, SSE
|
|
open/close/reconnect/event와 public payload 금지 field 수, driver CPU/RSS/event-loop lag를 raw JSON에
|
|
남긴다. token 값, 사용자/장수/도시/국가 ID와 response/event payload는 출력하지 않는다.
|
|
dashboard query의 opaque revision은 viewer별 메모리에서만 다음 `known` 입력으로 이어서
|
|
unchanged/snapshot/patch 경로를 구분하며 raw JSON에는 종류별 count만 남긴다.
|
|
|
|
## 안전 경계
|
|
|
|
- 운영/public profile에 실행하지 않는다. config의 `publicProfile`은 반드시 `false`이고 target hostname은
|
|
`allowedHosts`에 정확히 있어야 하며 loopback, RFC 1918/ULA 또는 `.local`/`.internal`이어야 한다. 이
|
|
guard를 우회하는 CLI flag는 없다.
|
|
- 전용 PostgreSQL schema는 `load_`로, Redis prefix는 `load-tests:`로 시작해야 한다. fixture/runtime을
|
|
기동하는 외부 orchestration에도 같은 값을 주어 공유 개발·운영 profile과 분리한다.
|
|
- fixture CLI는 config와 DB URL의 schema가 정확히 같고, Redis DB가 config의 전용 `1..15` DB와 정확히
|
|
같을 때만 동작한다. 둘 다 loopback/private host만 허용한다. cleanup은 전용 Redis manifest와 schema명을
|
|
다시 확인한 뒤 그 `load_` schema와 해당 profile의 access token만 지운다. Redis `FLUSHDB`와 공유 schema
|
|
삭제는 하지 않는다.
|
|
- driver는 query만 허용한다. own/global phase 이름은 invalidation 뒤 viewer read fan-out을 뜻하며 mutation을
|
|
만들지 않는다. 실제 own/global change stimulus는 격리 runtime에서 별도 orchestration으로 발생시킨다.
|
|
- token 파일은 이 workspace 안의 Git ignored path여야 하고 정확히 `0600`이어야 한다. 권장 위치는
|
|
`tools/load-tests/secrets/game-tokens.json`이며 JSON 형식은 `{"tokens":["...", "..."]}` 하나뿐이다.
|
|
- raw result는 새 파일로만 쓰고(`wx`) `0600`을 적용한다. 기본 ignored 위치는
|
|
`tools/load-tests/results/`다.
|
|
|
|
## 재현 명령
|
|
|
|
### 1. 격리 PostgreSQL/Redis와 1,200장수 fixture
|
|
|
|
아래 Compose는 loopback에만 포트를 열고 PostgreSQL 18.4, Redis 8.2.7을 고정한다. 실제 password와 URL은
|
|
Git ignored `secrets/`에 두며 명령행이나 결과 JSON에는 기록하지 않는다. `capacity.env`에는 최소
|
|
`LOAD_TEST_DATABASE_URL`(query의 `schema=load_capacity_300_900_5m` 포함),
|
|
`LOAD_TEST_REDIS_URL`(`/15` 포함), API 기동에 필요한 `GAME_TOKEN_SECRET`,
|
|
`GAME_IMAGE_UPLOAD_SECRET_FILE`을 넣는다. URL의 password는 percent-encoding한다.
|
|
|
|
```sh
|
|
install -m 600 /dev/null tools/load-tests/secrets/postgres-password.txt
|
|
install -m 600 /dev/null tools/load-tests/secrets/capacity.env
|
|
# 두 파일은 로컬 편집기로 채우고 내용을 stdout에 출력하지 않는다.
|
|
|
|
docker compose -f tools/load-tests/compose.capacity.yml config --quiet
|
|
docker compose -f tools/load-tests/compose.capacity.yml up -d --wait
|
|
|
|
set -a
|
|
source tools/load-tests/secrets/capacity.env
|
|
set +a
|
|
|
|
pnpm --filter @sammo-ts/load-tests seed \
|
|
--config tools/load-tests/config/300-users-900-npcs-5m.json \
|
|
--tokens tools/load-tests/secrets/game-tokens.json
|
|
pnpm --filter @sammo-ts/load-tests verify-fixture \
|
|
--config tools/load-tests/config/300-users-900-npcs-5m.json
|
|
```
|
|
|
|
`seed`는 해당 `load_` schema에 migration을 적용하고 scenario 2601을 고정 seed/time으로 설치한 뒤 정확히
|
|
900 NPC + 300 synthetic 사용자 장수로 재구성한다. 각 사용자의 24시간 access token은 Redis 전용 DB와
|
|
새 `0600` JSON에만 저장한다. stdout에는 token, user/general ID, DB/Redis URL을 내보내지 않고 count와
|
|
비밀값을 제외한 fixture SHA-256만 기록한다. token 파일이 이미 있으면 DB 작업 전에 실패한다.
|
|
|
|
fixture와 같은 환경으로 API를 띄울 때 핵심 namespace는 다음과 같다. `capacity.env` 값을 다시 명령행에
|
|
풀어 쓰지 않는다.
|
|
|
|
```sh
|
|
export DATABASE_URL="$LOAD_TEST_DATABASE_URL"
|
|
export REDIS_URL="$LOAD_TEST_REDIS_URL"
|
|
export PROFILE=load_capacity_300_900_5m
|
|
export SCENARIO=2601
|
|
export GAME_PROFILE_NAME=load-tests:capacity-300-900-5m
|
|
export GAME_API_HOST=127.0.0.1
|
|
export GAME_API_PORT=15001
|
|
export GAME_TRPC_PATH=/api/trpc
|
|
export GAME_API_EVENTS_PATH=/events
|
|
pnpm --filter @sammo-ts/game-api start
|
|
```
|
|
|
|
API와 driver는 별도 terminal/process로 실행한다. dev-sam2026와 같은 판정이 필요하면 API/engine container에
|
|
4 CPU/8 GiB 제한을 주고 driver는 그 cgroup 밖에서 실행하며, image digest를 아래 run config에 기록한다.
|
|
공개 `dev-sam2026.hided.net` profile에는 이 fixture나 driver를 연결하지 않는다.
|
|
|
|
### 2. 인증 HTTP/SSE driver
|
|
|
|
먼저 sample의 `runtimeMetadata` placeholder를 실제 fixture SHA-256, image digest, PostgreSQL/Redis
|
|
version으로 바꾼 ignored 복사본을 만든다. secret이나 ID를 config에 넣지 않는다.
|
|
|
|
```sh
|
|
pnpm --filter @sammo-ts/load-tests validate --config tools/load-tests/config/300-users-900-npcs-5m.json
|
|
pnpm --filter @sammo-ts/load-tests dry-run \
|
|
--config tools/load-tests/config/300-users-900-npcs-5m.json \
|
|
--tokens tools/load-tests/secrets/game-tokens.json
|
|
pnpm --filter @sammo-ts/load-tests run \
|
|
--config tools/load-tests/config/300-users-900-npcs-5m.json \
|
|
--tokens tools/load-tests/secrets/game-tokens.json \
|
|
--output tools/load-tests/results/300-users-900-npcs-5m.json
|
|
```
|
|
|
|
`validate`는 config만 검사한다. `dry-run`은 config, host allowlist, token count/permission/Git-ignore와 phase
|
|
계획을 검사하지만 network connection을 열지 않는다. driver process는 가능하면 target runtime과 다른
|
|
host/cgroup에서 실행하고 두 host의 CPU quota와 competing load를 별도로 기록한다.
|
|
`run`은 sample의 runtime metadata placeholder가 하나라도 남아 있으면 시작하지 않는다.
|
|
|
|
## 결과와 해석
|
|
|
|
raw JSON은 Git commit/tree/dirty 상태, config/runtime/host hash, Node/V8, host CPU/memory와 cgroup limit,
|
|
fixture/image/PostgreSQL/Redis metadata, phase별 metric을 포함한다. 이 정보는 재현 조건이지 합격 판정 자체가
|
|
아니다. `runtimeMetadata` placeholder가 남은 run과 외부 stimulus가 없던 own/global phase를 capacity pass로
|
|
보고하지 않는다.
|
|
|
|
### 3. E1 결정론적 engine capacity profile
|
|
|
|
E1은 DB/Redis 없이 scenario 2601을 정확히 900 NPC + 300 synthetic 사용자 장수로 확장해 5분 턴 한 달을
|
|
실행한다. report에는 정확한 초기 count, 전체 장수턴 처리량, 장수턴 및 월 wall-time p50/p95/p99/max,
|
|
RSS/heap과 최종 상태 SHA-256이 들어간다. 같은 commit/Node/fixture에서 상태 hash가 같아야 한다.
|
|
|
|
```sh
|
|
NPC_UNIFICATION_BENCHMARK_FIXED_MONTHS=1 \
|
|
NPC_UNIFICATION_BENCHMARK_REPORT_PATH=/dev/shm/npc-capacity-1200.json \
|
|
pnpm --filter @sammo-ts/game-engine profile:npc-capacity-1200
|
|
```
|
|
|
|
이 프로필은 자연 통일 소요시간 시험이 아니라 고정 1개월 engine 처리량 시험이다. 기존
|
|
`profile:npc-unification-timing`의 무보정 자연 진행 의미는 바꾸지 않는다.
|
|
|
|
### 4. 명시적 cleanup
|
|
|
|
token 파일은 별도로 안전하게 삭제하고, fixture schema/Redis token은 schema명을 그대로 확인 인자로 주어
|
|
정리한다. named volume은 보존한다. 데이터 폐기가 필요하지 않으면 이 명령을 실행하지 않는다.
|
|
|
|
```sh
|
|
pnpm --filter @sammo-ts/load-tests cleanup \
|
|
--config tools/load-tests/config/300-users-900-npcs-5m.json \
|
|
--confirm load_capacity_300_900_5m
|
|
docker compose -f tools/load-tests/compose.capacity.yml down
|
|
```
|
|
|
|
## 아직 남은 측정 경계
|
|
|
|
- `seed`/`verify-fixture`는 실제 PostgreSQL schema와 Redis access-token 상태를 만든다. 그러나 E2의 daemon
|
|
fast-forward, 한 달치 PostgreSQL flush/outbox publish, schedule lag와 DB statement count를 하나로
|
|
계측하는 실행기는 아직 없다. 따라서 E1이나 API/SSE driver 결과를 E2 합격으로 대체하지 않는다.
|
|
- own/global phase의 mutation stimulus는 driver가 만들지 않는다. 격리 runtime의 실제 engine/API mutation과
|
|
함께 실행하지 않았다면 A2/A3/M1 전체 합격으로 보고하지 않는다.
|
|
- 이 repository에서 실행한 로컬 E1 수치는 source-tree 회귀 근거다. dev-sam2026 동급 4 CPU/8 GiB container
|
|
결과로 부르려면 동일 image/fixture와 cgroup에서 다시 측정해야 한다.
|
|
|
|
## 도구 자체 검증
|
|
|
|
```sh
|
|
pnpm --filter @sammo-ts/load-tests test
|
|
pnpm --filter @sammo-ts/load-tests typecheck
|
|
```
|