Stress 테스트 인프라
기존 e2e (테스트 가이드) 가 사건 15건 cold-start 만 검증하는 한계를 보완. 사무소가 15년 운영하면 누적되는 회귀 (페이지네이션·N+1·인덱스 누락·findNearest p95·격리 누설·UI race) 를 nightly 로 사전 차단.
2026-05-06 PR #1720~#1733 (14 PR · 20 spec) 으로 구축. 본 문서는 신규 stress spec 추가 가이드.
두 차원
| 차원 | 목적 | 시드 | spec 수 | 회귀 영역 |
|---|---|---|---|---|
| 수직 | 단일 사무소 5년치 누적 | cases 2k + legacy 1k | 5 | 페이지네이션·N+1·인덱스·UI 인터랙션 |
| 수평 | 50 사무소 동시 운영 | 50 tenant × 50 cases × 5 legacy | 15 | cross-tenant 격리·ops 콘솔·운영자 mutation |
실행
로컬
# 1. emulator 띄우기 (stress 환경 — functions 제외)
# stress 시드 (5000 cases batch) 가 functions trigger 폭주 + 4GB heap 한계
# OOM 을 일으킴. 로컬 Mac 에서 stress 작업 시 functions 비활성 emulator 권장.
pnpm emulator:stress
# 일반 개발 (functions 포함):
# pnpm emulator
# 2. dev 서버 (web + ops 양쪽 필요)
pnpm dev --filter=@neohollo/web # 3000
pnpm dev --filter=@neohollo/ops # 3001
# 3. stress 실행 (시드는 globalSetup 이 자동)
pnpm e2e:stress # 단일 (cases 2k + legacy 1k)
pnpm e2e:multitenant # 수평 (100 tenant × 50 cases × 5 legacy)
로컬 OOM 사고 (2026-05-06): 100 tenant × 50 cases = 5000 cases 일괄 시드가
aggregateCaseStatsCloud Function 5000 동시 trigger → Mac heap 4GB 초과 OOM. nightly (ubuntu 16GB+) 는 영향 없음. 해결:pnpm emulator:stress(functions 제외). production code 의 functions 정합은 unit test (functions/__tests__/)
- nightly workflow 가 cover.
nightly
.github/workflows/nightly-stress.yml 가 매일 03:00 KST 자동 실행 + 수동 trigger 가능. PR 차단 아님 — 회귀 발견 시 fix PR.
gh workflow run "Nightly Stress" # 수동 trigger
gh run list --workflow="Nightly Stress" --limit 5 # 결과 확인
디렉토리 구조
scripts/
seed-stress-tenant.ts # 단일 stress (2k cases + 1k legacy)
seed-multi-tenant-stress.ts # 수평 stress (50 tenant × 50 cases × 5 legacy)
e2e/
stress/ # 단일 stress spec (5)
stress-setup.ts # globalSetup (clear + 시드)
multitenant/ # 수평 stress spec (15)
multitenant-setup.ts
helpers/
stress-auth.ts # 단일 stress owner login
multitenant-auth.ts # 50 tenant owner login
ops-auth.ts # masterAdmin cookie + clientSignInOps
playwright.stress.config.ts
playwright.multitenant.config.ts
새 spec 작성 가이드
1. 시드 chain 누락 패턴
owner 시드만 하면 spec 이 silent timeout — emulator 한계. 시드는 다음 4개를 모두 chain 해야 spec 동작:
| 누락 | 증상 | 해결 |
|---|---|---|
users/{uid}.consents | 재동의 모달이 헤딩을 덮어 timeout | seedUserConsents() (현행 CURRENT_*_VERSION "2026-04-27") |
setCustomUserClaims.tenantName | sidebar 빈 paragraph | { tenantId, tenantName, role } 모두 set |
embedding.vector 가 일반 number[] | findNearest 가 0 반환 (vector index 인식 X) | FieldValue.vector(arr) wrap |
case.status 가 invalid 값 | dashboard·case list 의 status 필터·뱃지에서 미인식 | apps/web/types/case.ts:CaseStatus 일치 ("소제기"/"진행중"/"판결대기"/"집행중"/"종결") |
2. ops e2e 의 client signIn
ops 의 onSnapshot 은 firestore rules 가 request.auth 검증 요구. cookie 만 inject 하면 client currentUser=null → KPI 등 데이터 fetch 가 permission denied.
apps/ops/lib/firebase/client.ts 가 dev+emulator 한정 window.__opsTestSignIn helper expose. spec 에서:
import { loginToOpsViaCookie, clientSignInOps, OPS_URL } from "../helpers/ops-auth";
await loginToOpsViaCookie(context); // SSR 가드 통과
await page.goto(`${OPS_URL}/dashboard`);
await clientSignInOps(page); // client SDK currentUser set
await page.reload({ waitUntil: "domcontentloaded" }); // onSnapshot listener 재등록
3. emulator 한계 (회피 패턴)
| 한계 | 회피 |
|---|---|
findNearest vector index 자동 생성 X | RAG 격리는 path-scoped UI listing 으로 cover, 진짜 vector 격리는 unit test (apps/web/lib/firebase/__tests__/find-nearest.test.ts) |
aggregateCaseStats Cloud Function 비결정성 | KPI 정확 숫자 매칭 X. "양수만" + "Firestore onSnapshot 실패 sentinel 0건" invariant |
| ops Google OAuth 만 노출 | password REST 로 idToken → cookie inject (helpers/ops-auth.ts) |
4. invariant 설계 원칙
- 결정적 값은 정확 매칭 — 활성 Tenant = 50 (시드 보장)
- 비결정적 값은 비교/양수 — 누적 사건 > 0, 진행중 ≤ 전체
- sentinel 메시지 0건 — "Firestore onSnapshot 실패" 같은 production 경고 미노출
- prefix 매칭으로 cross-tenant 누설 검출 —
MT-007-vsMT-008-같이 시드 시점부터 식별자 분리
5. 회귀 정책
임계값 좁히지 말고 production 코드 수정.
stress spec 이 fail 하면 임계값을 완화하는 게 아니라 production 코드를 fix (페이지네이션 추가·집계 캐시 필드·가상 스크롤·index 추가). nightly run 결과 → 회귀 발견 시 fix PR.
검증 영역 7 차원
- 수직 — 페이지네이션·N+1·인덱스·findNearest (단일 tenant 사건 수천건)
- 수평 격리 —
cases·legacyDocuments·identity (변호사법 §26 비밀유지의무) - 운영자 시점 — ops SSR 가드 + KPI sentinel
- 사용자 행동 — 탭 클릭·검색 디바운스·필터 복구
- 운영팀 master-detail — 90 카드 mount + 검색 + 카테고리 navigation
- ops 공용 자료 — Platform RAG (ADR 0021)
- ops 카드 mutation — server action chain + cross-tenant 격리
디스크 잠식 사고 (2026-05-06)
firestore-debug.log 가 47Gi 까지 누적 (cases 2k 시드 + onSnapshot stream verbose). emulator 자체 옵션으로 logging level 제어 안 됨.
예방: package.json 의 clean:emulator-logs script 가 pnpm emulator 시작 시 5종 debug log truncate. 누적 0 보장.
참고
- 인프라 PR: #1720 (단일 인프라) · #1723 (수평 인프라) · #1727 (ops client signIn helper) · #1730 (ops scenarios mount) · #1731 (emulator log fix) · #1733 (ops 카드 mutation)
- fix PR: #1722 (consents 시드) · #1725 (tenantName + FieldValue.vector) · #1728 (CaseStatus 일치)