Files
core2026/tools/load-tests
Hide_D 7e099326ec perf: 1200장수 용량 fixture와 E1 profile을 추가
900 NPC와 300 synthetic 사용자를 5분 턴으로 처리하는 결정론적 engine profile을 추가하고, 전용 PostgreSQL schema와 Redis DB에 300개 인증 session을 준비하는 격리 load fixture를 제공한다.
2026-08-16 18:31:14 +00:00
..

인증 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한다.

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 값을 다시 명령행에 풀어 쓰지 않는다.

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에 넣지 않는다.

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가 같아야 한다.

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은 보존한다. 데이터 폐기가 필요하지 않으면 이 명령을 실행하지 않는다.

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에서 다시 측정해야 한다.

도구 자체 검증

pnpm --filter @sammo-ts/load-tests test
pnpm --filter @sammo-ts/load-tests typecheck