가상 대기열 트래픽 제어 — 크로스 도메인 통합 DAG
TPM 산출물. 세 도메인(INFRA·BE·FE)의 이미 확정된 도메인 내 DAG를 재분해하지 않고, 도메인 사이의 의존만 엮은 통합 계획이다. 도메인 내 티켓 분해·순서는 시니어 소관(불변).
- 근거 설계:
20260709-가상대기열-tdd.md(BE),20260709-가상대기열-design-fe-app.md(FE),20260709-redis-contract.md(Redis),20260709-가상대기열-prd.md - 도메인 내 DAG(입력, 불변):
- INFRA:
iW0= INFRA-01 (의존 없음) - BE:
bW1=BE-01 →bW2=BE-02·03·04·05 →bW3=BE-06·07 →bW4=BE-08·09·10 →bW5=BE-11 - FE:
fW1=FE-01·02·03·04 →fW2=FE-05·06 →fW3=FE-07 →fW4=FE-08·09
- INFRA:
핵심 결론 (요약)
- 도메인 간 실질 의존 엣지는 2개뿐이다 — 나머지는 계약 확정으로 이미 충족됐다.
INFRA-01 → BE prod 배포: 배포 게이트(컴파일·dev 머지 무관).BE-08·BE-09 → FE-08·09 실검증 + BE-11 E2E: 실검증 게이트(구현·유닛 무관).
- BE API 계약이 TDD “FE/외부 계약”에 시그니처 수준으로 동결돼 있어, FE는 BE 구현을 기다리지 않고 IW1부터 병렬 착수한다.
- 크로스 도메인 Single Writer 위반 0 —
docker-compose.prod.yml(INFRA) /backend/**(BE) /mobile/**(FE) 교집합 ∅. - 통합 wave 5개, 최대 너비 6, 평균 4.2 — 직선형 아님(분해 건전).
통합 DAG (Mermaid flowchart LR)
flowchart LR subgraph IW1["IW1 (너비 6)"] INFRA01[INFRA-01 prod redis] BE01[BE-01 계약·Lua·키] FE01[FE-01 큐 API client] FE02[FE-02 토큰스토어·플래그] FE03[FE-03 대기실 컴포넌트] FE04[FE-04 큐 라우트 상수] end subgraph IW2["IW2 (너비 6)"] BE02[BE-02~05 store·hmac·도메인] FE05[FE-05 useEnterQueue] FE06[FE-06 useQueueStatus] end subgraph IW3["IW3 (너비 3)"] BE06[BE-06·07 usecase·pump] FE07[FE-07 대기실 뷰모델·화면] end subgraph IW4["IW4 (너비 5)"] BE08[BE-08 Controller] BE09[BE-09 인터셉터] BE10[BE-10 관측] FE08[FE-08 한정판 진입·토큰헤더] FE09[FE-09 티케팅 진입·토큰헤더] end subgraph IW5["IW5 (너비 1)"] BE11[BE-11 E2E+풀부팅 크로스검증] end BE01 --> BE02 BE02 --> BE06 BE06 --> BE08 BE08 --> BE11 FE01 --> FE05 FE05 --> FE07 FE07 --> FE08 FE07 --> FE09 INFRA01 -.배포게이트.-> BE11 BE08 -.실검증게이트.-> BE11 BE09 -.실검증게이트.-> BE11 BE08 -.실검증게이트.-> FE08 BE09 -.실검증게이트.-> FE09
점선(
-.->)이 도메인 간 엣지다. 실선은 도메인 내(불변, 참고용 압축 표기). 도메인 간 점선은 컴파일 의존이 아니라 배포/실검증 게이트임에 유의.
통합 wave 표
각 통합 wave에 동시에 열리는 도메인 티켓과 ready 셋 크기. 도메인 내 wave 진행은 시니어가 지휘하고, TPM은 wave 경계의 게이트만 연다.
| 통합 wave | INFRA | BE | FE | ready 셋 | 너비 |
|---|---|---|---|---|---|
| IW1 | INFRA-01 (iW0) | BE-01 (bW1) | FE-01·02·03·04 (fW1) | INFRA-01, BE-01, FE-01, FE-02, FE-03, FE-04 | 6 |
| IW2 | — (완료) | BE-02·03·04·05 (bW2) | FE-05·06 (fW2) | BE-02, BE-03, BE-04, BE-05, FE-05, FE-06 | 6 |
| IW3 | — | BE-06·07 (bW3) | FE-07 (fW3) | BE-06, BE-07, FE-07 | 3 |
| IW4 | — | BE-08·09·10 (bW4) | FE-08·09 (fW4) | BE-08, BE-09, BE-10, FE-08, FE-09 | 5 |
| IW5 | — | BE-11 (bW5) | — (실검증 참여) | BE-11 (+FE-08/09 크로스 실검증) | 1 |
- BE가 임계 경로(5 wave). FE(4 wave)는 IW1부터 시작해 IW4에서 FE-08/09가 BE-08/09와 동시에 열린다 — FE 유닛 테스트는 BE를 mock하므로 구현·유닛은 병렬 가능, 크로스 실검증만 IW5로 미룬다.
- INFRA-01은 IW1에 단발 완료 후 이후 wave에 관여하지 않는다(배포 게이트로만 IW5 prod 배포에 선행).
도메인 간 의존 엣지 목록 (무엇이 무엇을 왜, 게이트 유형 구분)
| # | 엣지 | 게이트 유형 | 무엇을 기다리나 | 근거 |
|---|---|---|---|---|
| E1 | INFRA-01 → BE 코드 prod 배포 | 배포 게이트 (컴파일·dev 머지 아님) | prod Redis maxmemory 512mb+·noeviction 반영이 코드 prod 배포보다 선행 | TDD Release Scenario 0단계 “1단계 코드 배포의 필수 선행 조건”. BE 구현·테스트는 Testcontainers Redis로 자급자족 → INFRA-01 없이 dev 머지·유닛/통합 전부 가능. 자기잠식(대기열 OOM→fail-open) 차단 목적 |
| E2 | BE TDD “FE/외부 계약” → FE 전체 | 계약 게이트 (이미 충족) | API 시그니처 확정 — 런타임 엣지 아님 | 계약이 TDD에 시그니처 수준 동결(엔드포인트 5종·QueueEntryResponse·429/403 코드). FE-01이 이 계약으로 타입·client 정의. 확정됨 → FE는 IW1 병렬 착수, BE 구현 대기 불요 |
| E3 | BE-08(Controller)+BE-09(인터셉터) → BE-11 크로스 E2E | 실검증 게이트 | 실제 엔드포인트·인터셉터 실물 | BE-11이 enter→admit→토큰→POST /limited-drops/{id}/orders·/events/{id}/seats/select(X-Entry-Token) 통과를 Testcontainers 풀부팅으로 검증. 컨트롤러·인터셉터 실물 필요 (도메인 내 의존이나 IW4→IW5 경계) |
| E4 | BE-08(Controller)+BE-09(인터셉터) → FE-08·09 실검증 | 실검증 게이트 (구현·유닛 아님) | 실제 큐/구매 엔드포인트·403 게이트 | FE-08/09 유닛(axios-mock-adapter로 X-Entry-Token 부착·403 QUEUE_BYPASS_DENIED 매핑)은 BE mock으로 IW4 병렬 통과. 그러나 앱↔실 BE 통합 확인은 BE-08/09 머지 후(IW5) 가능 |
- 컴파일 의존 도메인 간 엣지: 0개. 세 도메인은 서로의 산출물을 import·컴파일 참조하지 않는다(BE=Kotlin backend, FE=RN mobile, INFRA=compose). E1은 배포, E2는 계약(충족), E3·E4는 실검증뿐.
크로스 도메인 Single Writer 검증
레포 최상위 확인: backend/, mobile/, docker-compose.prod.yml이 각각 독립 경로로 존재.
| 도메인 | 수정 경로 집합 | 타 도메인과 교집합 |
|---|---|---|
| INFRA (INFRA-01) | docker-compose.prod.yml (레포 루트 단일 파일) | ∅ |
| BE (BE-01~11) | backend/** (Kotlin src·backend/docs/redis/·backend/src/main/resources/redis/) | ∅ |
| FE (FE-01~09) | mobile/** (app/·api/·lib/·theme/·components/) | ∅ |
- 교집합 ∅ — 크로스 도메인 Single Writer 위반 0. INFRA는 compose만, BE는 backend만, FE는 mobile만 수정. 세 도메인이 같은 wave에서 열려도 머지 충돌 없음.
- 주의: BE 티켓은 dev
docker-compose.yml·Testcontainers를 쓰지 프로덕션 compose를 수정하지 않는다. INFRA-01은docker-compose.prod.yml만 — dev/base compose 불변. - 각 도메인 내부 Single Writer(예: BE-08만
SecurityConfig.kt, BE-09는 신규 WebMvcConfig / FE-08=한정판·FE-09=티케팅 파일 분리 / FE는theme/tokens.ts·api/types.ts미수정)는 시니어가 이미 보장(티켓 명시). TPM 범위 밖이나 크로스 판정에 영향 없음.
통합 wave 게이트 — 진입·종료 조건
TPM은 아래 게이트만 연다. 게이트마다 완료 근거(리뷰 verdict·머지·검증 아티팩트)를 확인한 뒤 다음 wave를 연다 — 시니어의 “완료” 단언만으로 열지 않는다.
G-IW1 (착수)
- 진입: senior-pm 정합 검증 PASS + 사용자 구현 승인. (선행 게이트 — PASS 기록 없으면 열지 않음.)
- 동시 스폰: INFRA(
private-redis-implementerfor INFRA-01) + BE senior(BE-01 wave 지휘) + FE senior(FE-01·02·03·04 wave 지휘)를 한 메시지에서 동시 스폰. - 종료:
- INFRA-01:
private-infra-reviewerverdict APPROVED/COMMENT + prod composeCONFIG GET maxmemory≥536870912·maxmemory-policy=noeviction확인 아티팩트. (REQUEST_CHANGES면 재수정 후 재리뷰.) - BE-01: 리뷰 통과·머지 (Lua EVAL raw 로그·VO 단위 테스트).
- FE-01~04: 리뷰 통과·머지 (client 유닛·타입
tsc --noEmit0).
- INFRA-01:
G-IW2 → G-IW3 (도메인 내 진행, TPM 관망)
- IW2·IW3는 도메인 내 wave 전진이라 시니어가 지휘. TPM은 각 도메인 wave 종료(머지·검증)만 추적표에 반영.
- BE bW2(store·hmac·도메인)·bW3(usecase·pump), FE fW2(훅)·fW3(뷰모델·화면) 각각 리뷰 통과·머지로 닫힌다.
- 크로스 게이트 없음 — 두 도메인이 서로를 기다리지 않는다(계약 동결).
G-INFRA (배포 게이트, E1)
- 진입: IW1에서 INFRA-01 종료(위 G-IW1) — 이르게 닫힘.
- 종료·효과: prod 코드 배포(IW5 이후 릴리즈) 선행 조건 충족. dev 배포·BE 구현은 이 게이트와 무관하게 진행. QA PASS 후 prod 배포 시 INFRA-01 반영 상태를 재확인.
G-INTEGRATION (실검증 게이트, E3·E4) — 핵심 크로스 게이트
- 진입: BE-08·BE-09 리뷰 통과·
main머지 확인 (IW4 BE 종료) + FE-08·09 구현·유닛 통과(mock 기반). - 종료:
- BE-11 실행:
@SpringBootTest풀부팅 성공 + 시나리오 통과 아티팩트 — 양 경로(한정판 reserve.lua·티케팅 seat:lock) admitted→토큰→구매 통과, 플래그 OFF directEntry, Redis 장애 fail-open(오버셀 0), 우회 403, §0-1 연쇄 admission 회귀, 이탈 방출. - FE-08/09 크로스 실검증: 실 BE 엔드포인트 대상 X-Entry-Token 부착·403 재대기 흐름 확인(dev 구동).
- BE-11 실행:
- REQUEST_CHANGES·실패 시 담당 도메인 시니어에게 반려, 재검증 전 wave 미종료.
병렬 처리량 요약
- 팀 = 에이전트 병렬 스폰. wave 너비 = 동시 스폰 implementer 수.
- 통합 wave 5개, 티켓 총 21개(INFRA 1·BE 11·FE 9).
- 너비 분포: IW1=6, IW2=6, IW3=3, IW4=5, IW5=1 → 최대 6, 평균 4.2.
- 직선형(모든 wave 1~2) 아님 — 계약 동결로 BE·FE·INFRA를 IW1부터 병렬화해 fan-out을 넓혔다. 임계 경로는 BE 5 wave, FE는 그 안에 완전히 포개진다.
리스크
| 리스크 | 영향 | 완화 |
|---|---|---|
| INFRA-01 prod 반영 누락 후 prod 배포 | 마케팅 피크에 대기열 자기 OOM→자기 fail-open(정합성은 MySQL이 지키나 유입 제어 목적 상실) | G-INFRA를 prod 배포 체크리스트에 강제 — QA PASS와 별개로 CONFIG GET maxmemory 재확인 |
| FE-08/09를 BE-08/09 머지 전 mock으로만 검증하고 종료 | 계약 동결이라 위험 낮으나, 실 BE와 헤더 케이스({type} kebab 파싱 등) 미세 불일치 가능 | G-INTEGRATION에서 실 BE 대상 크로스 실검증 필수 — 유닛 통과만으로 IW5 닫지 않음 |
{type} 와이어 값 정합(limited-drop/ticketing-event ↔ QueueTargetType) | BE 파싱 실패 시 400, FE 전 큐 호출 실패 | 계약 정합 확인됨(아래) — BE-08 파싱 테스트 “잘못된 {type} 400”으로 회귀 방어 |
| BE 임계 경로 지연(bW2 store·Lua 결함) | 후행 BE 전체·IW5 E2E 지연. FE는 영향 없음(계약 병렬) | BE 지연은 FE 흐름을 막지 않음 — FE-08/09까지 독립 완주 가능, E2E만 대기 |
계약 정합 확인 (API·토큰·타입·Redis)
BE TDD “FE/외부 계약 — API 명세” ↔ FE 설계 “API 연동 표”/QueueEntryResponse 대조 — 불일치 0.
| 항목 | BE 계약 | FE 소비 | 정합 |
|---|---|---|---|
| 엔드포인트 | POST/GET/DELETE /virtual-queues/{type}/{targetId}/entries[/me], /stats | enterQueue·getQueueStatus·leaveQueue 동일 경로 | ✅ |
| status enum | WAITING|ADMITTED|DIRECT_ADMITTED | QueueEntryStatus 동일 3값 | ✅ |
| position/aheadCount/etaSeconds | Long? | number | null | ✅ |
| entryToken/tokenExpiresAt | String?(ISO-8601) | string | null | ✅ |
| 포화 | 429 QUEUE_FULL | 429→FULL 판별 유니온 | ✅ |
| 미진입 | 404 | 404→NOT_IN_QUEUE 판별 | ✅ |
| 구매 게이트 헤더 | X-Entry-Token, 403 QUEUE_BYPASS_DENIED | 동일 헤더·403 매핑 | ✅ |
{type} 와이어값 | limited-drop|ticketing-event | 동일 리터럴 유니온 | ✅ |
| 인증 | X-User-Id 헤더 | 동일 | ✅ |
- stats(FR-11): BE-08 제공, FE 미소비(운영자 전용) — FE 의존 없음, 정합 이슈 아님.
- Redis 계약: BE 내부(BE-01/02) + INFRA-01 사이징(§4)만 참조. FE 비노출. INFRA↔BE는 동일
20260709-redis-contract.md참조로 정합.
Document History
| 날짜 | 변경 내용 |
|---|---|
| 2026-07-09 | 최초 작성 — INFRA·BE·FE 3도메인 통합 DAG. 통합 wave 5개(너비 6/6/3/5/1). 도메인 간 엣지 4개(배포 E1·계약 E2 충족·실검증 E3·E4). 크로스 Single Writer 위반 0. 계약 정합 9항목 일치 |