"""인증 서버 부하 테스트 (self-register 방식, 사전 시드 불필요). 각 가상 유저가 on_start 에서 자기 계정을 생성(/create)하고 로그인한 뒤, me/login/refresh/healthz 를 가중치대로 반복한다. supplier 의 /create 는 supplier_id(소속 공급사)가 필수이므로, run_local_locust.sh 가 부하용 공급사(부하테스트공급사) 1건을 보장하고 그 supplier_id 를 LOAD_SUPPLIER_ID 로 넘긴다. 직접 실행 시: LOAD_SUPPLIER_ID=<공급사 uuid> locust -f loadtest/locustfile.py --host http://localhost:9300 정리(테스트 후, self-register 로 쌓인 계정 삭제): psql ... -c "DELETE FROM supplier.supplier_users WHERE id LIKE 'load_user_%';" 주의: - /create, /login 은 bcrypt(CPU 바운드) + DB read/write 라 가장 무겁다. - /me, /refresh 는 su_id DB 존재/활성 검증(read 2회)을 한다 — 순수 JWT 경로가 아니다. - 논리 DB 가 USER/PARTNER 2개라 같은 negosium_db 에 엔진 풀이 4벌(R/W×2) 잡힌다. SELECT count(*) FROM pg_stat_activity WHERE datname = 'negosium_db'; """ import os import random from locust import HttpUser, between, events, task LOAD_SUPPLIER_ID = os.environ.get("LOAD_SUPPLIER_ID", "") LOAD_PW = "loadpw1234" class AuthUser(HttpUser): wait_time = between(0.5, 2.0) def on_start(self): # 유저마다 고유 계정을 생성(self-register)하고 로그인해 토큰을 확보한다. self.login_id = f"load_user_{random.randint(0, 1_000_000_000)}" self.access_token = None self.refresh_token = None with self.client.post( "/v1/auth/create", json={"supplier_id": LOAD_SUPPLIER_ID, "id": self.login_id, "pw": LOAD_PW}, name="POST /v1/auth/create", catch_response=True, ) as resp: if resp.status_code == 200 and resp.json().get("result", {}).get("success"): resp.success() else: resp.failure(f"create failed: {resp.status_code} {resp.text[:120]}") self._login() def _login(self): with self.client.post( "/v1/auth/login", json={"id": self.login_id, "pw": LOAD_PW}, name="POST /v1/auth/login", catch_response=True, ) as resp: if resp.status_code == 200 and resp.json().get("result", {}).get("success"): body = resp.json() self.access_token = body.get("access_token") self.refresh_token = body.get("refresh_token") resp.success() else: resp.failure(f"login failed: {resp.status_code} {resp.text[:120]}") @task(5) def me(self): # 토큰 검증 + su_id/공급사명 DB 조회(2 read). 보호 엔드포인트 처리량 측정. if not self.access_token: return self.client.get( "/v1/auth/me", headers={"Authorization": f"Bearer {self.access_token}"}, name="GET /v1/auth/me", ) @task(2) def login(self): # bcrypt + DB read 2 + DB write 가 포함된 무거운 경로. self._login() @task(1) def refresh(self): # refresh 토큰 검증 + su_id DB 존재/활성 확인 후 access 재발급. if not self.refresh_token: return with self.client.post( "/v1/auth/refresh_token", headers={"Authorization": f"Bearer {self.refresh_token}"}, name="POST /v1/auth/refresh_token", catch_response=True, ) as resp: if resp.status_code == 200 and resp.json().get("result", {}).get("success"): self.access_token = resp.json().get("access_token") resp.success() else: resp.failure(f"refresh failed: {resp.status_code} {resp.text[:120]}") @task(1) def healthz(self): # 베이스라인 (앱 오버헤드 측정). self.client.get("/healthz", name="GET /healthz") @events.test_start.add_listener def _on_start(environment, **kwargs): if not LOAD_SUPPLIER_ID: print("[warn] LOAD_SUPPLIER_ID 가 비어있습니다 — /create 가 전부 실패합니다. run_local_locust.sh 로 실행하세요.") print("부하 테스트 시작 - self-register 방식. PostgreSQL 커넥션 수 모니터링 권장 (pg_stat_activity)")