Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
|---|---|
| 모노레포 | npm workspaces |
| 공유 타입 | `@heist/shared` (TypeScript) |
| 백엔드 | Node.js, Socket.IO, tsx |
| 백엔드 | NestJS, Socket.IO |
| 프론트엔드 | React, Vite, Zustand, styled-components |
| 렌더링 | Canvas 2D (HiDPI 지원, devicePixelRatio) |
| 블록체인 | Solana (devnet), @solana/web3.js |
Expand All @@ -63,18 +63,12 @@ packages/
│ ├── constants.ts # 게임 밸런스 상수
│ ├── map.ts # 저장소, 스폰, 감옥, 장애물 배치
│ └── protocol.ts # Socket.IO 이벤트 타입
├── backend/ # 게임 서버
├── backend-nest/ # Nest 게임 서버
│ └── src/
│ ├── game/
│ │ ├── GameLoop.ts # 20Hz 틱 루프 (50ms)
│ │ ├── GameState.ts # 상태 관리, 플레이어별 스냅샷 필터링
│ │ ├── physics.ts # 이동, 장애물 충돌, LOS 계산
│ │ ├── skills.ts # 스킬 로직 (steal, arrest, break_jail)
│ │ └── BotAI.ts # 봇 AI
│ ├── rooms/
│ │ └── Room.ts # 방 관리, 소켓별 개별 emit
│ └── solana/
│ └── payout.ts # 정산 및 환불
│ ├── gateway/ # Socket.IO Gateway
│ ├── web/ # health/metrics/state 엔드포인트
│ ├── services/ # 런타임 서비스
│ └── core/ # 게임 코어(Room/GameLoop/Observability)
└── frontend/ # 게임 클라이언트
└── src/
├── canvas/
Expand Down Expand Up @@ -110,7 +104,7 @@ npm run build -w packages/shared
npm run dev
```

- 백엔드: `http://localhost:3001`
- 백엔드: `http://localhost:8081`
- 프론트엔드: `http://localhost:5173`

## 게임 밸런스 상수
Expand Down
18 changes: 9 additions & 9 deletions TECHNICAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
│ │
│ ┌──────────────┐ ┌──────────────────┐ ┌────────────────┐ │
│ │ @heist/ │ │ @heist/ │ │ @heist/ │ │
│ │ shared │◄──│ backend │ │ frontend │ │
│ │ shared │◄──│ backend-nest │ │ frontend │ │
│ │ │◄──│ │ │ │ │
│ │ Types │ │ Express │ │ React + Vite │ │
│ │ Types │ │ NestJS │ │ React + Vite │ │
│ │ Constants │ │ Socket.IO │ │ Canvas 2D │ │
│ │ Map Data │ │ Game Loop │ │ Zustand │ │
│ │ Protocol │ │ Solana Payout │ │ Socket.IO │ │
Expand All @@ -41,21 +41,21 @@ npm workspaces 기반 모노레포로 3개 패키지를 관리합니다.
| 패키지 | 역할 | 주요 의존성 |
|--------|------|-------------|
| `@heist/shared` | 타입, 상수, 맵 데이터, 소켓 프로토콜 정의 | TypeScript |
| `@heist/backend` | 게임 서버 (상태 관리, 물리, 스킬, 정산) | Express, Socket.IO, @solana/web3.js |
| `@heist/backend-nest` | 게임 서버 (Nest Gateway + 런타임 코어) | NestJS, Socket.IO, @solana/web3.js |
| `@heist/frontend` | 게임 클라이언트 (렌더링, 입력, UI) | React, Vite, Zustand, styled-components, Socket.IO Client |

`@heist/shared`는 백엔드와 프론트엔드 양쪽에서 참조하여 **타입 안전한 통신 프로토콜**을 보장합니다.
`@heist/shared`는 Nest 백엔드와 프론트엔드 양쪽에서 참조하여 **타입 안전한 통신 프로토콜**을 보장합니다.

---

## 3. 백엔드 구현

### 3.1 서버 초기화

`Express` HTTP 서버 위에 `Socket.IO`를 마운트합니다.
`NestJS` 애플리케이션 위에서 `Socket.IO Gateway`를 통해 실시간 이벤트를 처리합니다.

```
Express (HTTP)
NestJS (HTTP + WebSocket Gateway)
└── Socket.IO (WebSocket + long-polling fallback)
├── CORS 설정 (프론트엔드 origin 허용)
└── TypeScript 제네릭으로 타입 안전한 이벤트 처리
Expand Down Expand Up @@ -444,8 +444,8 @@ App.tsx

### 개발 환경
```bash
npm install # 의존성 설치
npm run dev # 백엔드(8080) + 프론트엔드(3000) 동시 실행
npm install # 의존성 설치
npm run dev # 백엔드(8081) + 프론트엔드(3000) 동시 실행
```

### 프로덕션 배포
Expand All @@ -462,7 +462,7 @@ npm run dev # 백엔드(8080) + 프론트엔드(3000) 동시 실행
|------|------|
| `ESCROW_SECRET_KEY` | 에스크로 지갑 비밀키 (정산/환불용) |
| `VITE_SERVER_URL` | 프론트엔드에서 백엔드 접속 URL |
| `PORT` | 백엔드 서버 포트 (기본 8080) |
| `PORT` | 백엔드 서버 포트 (기본 8081) |

---

Expand Down
102 changes: 102 additions & 0 deletions docs/backend-nest-full-cutover-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Backend Nest Full Cutover Checklist

비운영(개발) 환경에서 `packages/backend`를 제거하고 `packages/backend-nest`로 완전 전환하기 위한 작업 순서.

## 원칙

- 순서를 건너뛰지 않는다.
- 각 단계 완료 시 최소 스모크 테스트를 수행한다.
- 롤백이 필요하면 해당 단계 커밋으로 되돌린다.
- 단계별 수행이 끝날 때마다 커밋한다.
- 커밋 제목 형식은 `feat:`/`fix:`/`refactor:` + 한글 설명으로 통일한다.
- 코드 주석은 블록 단위로 맥락이 보이게 작성하고, 주석 문구는 한글로 작성한다.

## 1) 기준선 고정 (브랜치/백업)

- [x] 전환 전용 브랜치 생성 (`feat/nest-full-cutover`)
- [x] 현재 동작 상태 태그 또는 커밋 SHA 기록
- [x] `.env`/배포 변수 스냅샷 저장

완료 기준:
- [x] 되돌릴 기준점이 명확하다.

## 2) 실행 엔트리 Nest 기준으로 통일

- [x] 루트 `package.json`에서 `dev` 기본 백엔드를 `backend-nest`로 전환
- [x] `build/start` 관련 스크립트가 Nest 산출물(`dist/main.js`) 기준인지 확인
- [x] PM2/프로세스 매니저 설정(`ecosystem.config.cjs`)이 Nest 엔트리 기준인지 정렬
- [x] README 실행 가이드를 Nest 기준으로 수정

완료 기준:
- [x] `npm run dev` 시 프론트 + Nest 조합으로 정상 구동된다.

## 3) 네트워크/프록시 라우팅 단일화

- [x] Nginx/Ingress 설정에서 legacy 업스트림 의존 제거
- [x] WebSocket 업그레이드 경로가 Nest 포트로 향하는지 검증
- [x] 로컬/스테이징 포트 충돌 여부 확인

완료 기준:
- [x] HTTP + WS 모두 Nest만 타고 동작한다.

## 4) 기능 동등성 스모크 테스트

- [x] 로비 진입/방 생성/방 참가
- [x] 게임 시작/틱 진행/결과 처리
- [x] 재연결(소켓 끊김 후 재입장) 시나리오
- [x] 운영 엔드포인트 (`/health`, `/_metrics`, `/_state/*`) 응답 검증
- [x] `scripts/canary-smoke.sh` 재사용 가능 시 Nest 대상 실행

완료 기준:
- [x] 사용자 핵심 플로우에서 blocker 이슈가 없다.

## 5) 카나리 토글 제거 및 상수화

- [x] `NEST_TRAFFIC_CANARY_*` 기반 분기 코드 제거 또는 `always nest`로 단순화
- [x] `route=legacy` 반환 경로 제거
- [x] migration 전용 엔드포인트(`/_migrate/*`) 유지/삭제 여부 결정 후 반영

완료 기준:
- [x] 런타임 라우팅 의사결정이 더 이상 legacy를 참조하지 않는다.

## 6) 레거시 backend 의존 코드 정리

- [x] 프론트/스크립트/문서에서 `packages/backend` 참조 전수 검색 (`rg "packages/backend|dev:backend"`)
- [x] CI 명령에서 legacy 경로 제거
- [x] 배포 설정(예: Railway/Vercel/컨테이너) legacy 엔트리 제거

완료 기준:
- [x] 코드/설정/문서에 legacy 실행 경로가 남아있지 않다.

## 7) legacy 패키지 제거

- [x] `packages/backend` 디렉터리 제거
- [x] 워크스페이스 의존성 정리 후 `npm install` 재실행
- [x] 타입체크/빌드 재검증 (`npm run typecheck`, `npm run build`)

완료 기준:
- [x] 모노레포가 Nest 단일 백엔드로 정상 빌드된다.

## 8) 최종 검증 및 릴리즈 준비

- [x] 로컬 E2E 스모크 1회 이상 재실행
- [x] 모니터링 대시보드/알람 경로가 Nest 지표 기준인지 확인
- [x] 전환 결과 문서화 (`what changed`, `known issues`, `rollback point`)

완료 기준:
- [x] 다음 개발 사이클에서 legacy 없이 기능 개발 가능하다.

## 권장 커밋 분할

- [x] Commit A: `feat: 실행 엔트리/문서 전환`
- [x] Commit B: `refactor: 라우팅 및 카나리 분기 단순화`
- [x] Commit C: `fix: 레거시 backend 제거 후 CI/배포 경로 정리`

## 빠른 검증 명령

```bash
npm run dev
npm run typecheck
npm run build
npm run canary:smoke -w packages/backend-nest
```
26 changes: 8 additions & 18 deletions docs/backend-nest-migration-plan.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
# Backend Nest Migration Plan

기존 `packages/backend`(Express + Socket.IO)를 유지한 채 `packages/backend-nest`를 병행 운영하며 단계적으로 이관한다.
기존 `packages/backend`를 유지한 채 `packages/backend-nest`를 병행 운영하며 단계적으로 이관했다.
`2026-03-09` 기준 full cutover 완료 상태를 기록한다.

## 원칙

- Big-bang 전환 금지, 단계별 카나리 전환
- 단계 완료마다 커밋 + 실행 검증
- 관측/리스크 게이트(`/_metrics`, `/_state/*`) 먼저 이식 후 트래픽 이관

## 단계
## 단계(이력)

1. Nest 병행 실행 골격 [완료]
- Nest 앱 신규 패키지 생성
Expand All @@ -31,7 +32,7 @@
- `RoomStateRepository` 및 정합성 검사 도구 이식
- 복구 드릴 API 및 risk gate 연동

6. 점진 트래픽 전환 [진행중]
6. 점진 트래픽 전환 [완료]
- `10% -> 25% -> 50% -> 100%` 룸 타입/비율 카나리
- 게이트 위반 시 즉시 롤백

Expand All @@ -40,6 +41,7 @@
- Stage4 리스크 게이트를 Nest에서도 동일 충족
- legacy backend 의존 read/write 경로 제거
- 운영 엔드포인트/대시보드 완전 이관
- `packages/backend` 제거

## 현재 상태

Expand All @@ -48,20 +50,8 @@
- 완료: 3단계(Room lifecycle 경로 이식)
- 완료: 4단계(실제 RoomManager/GameLoop 경로를 Nest Gateway에 연결)
- 완료: 5단계(Nest 로컬 `/_metrics`, `/_state/rooms`, `/_state/consistency`, `/_state/recovery-drill` 연동)
- 진행중: 6단계(룸 해시 기반 트래픽 카나리 + strict 게이트, 10% 검증 완료)

## 6단계 환경변수

- `NEST_TRAFFIC_CANARY_ENABLED=true|false` (기본 `false`)
- `NEST_TRAFFIC_CANARY_PERCENT=10` (기본 `10`)
- `NEST_TRAFFIC_CANARY_ROOM_TYPES=default,ranked` (기본 `default`)
- `NEST_TRAFFIC_CANARY_STRICT=true|false` (기본 `false`)
- `NEST_TRAFFIC_SAMPLE_ROOM_ID=default:sample` (`/_metrics` 샘플 판정용)

## 6단계 검증 포인트

- `GET /_migrate/room/decision?roomId=default:abc`로 라우팅 판정 확인
- strict on + 비대상 roomId일 때 `join_room`/`/_migrate/room/join`에서 `route=legacy` 응답 확인
- `/_metrics.migration.nestTrafficCanary`로 현재 카나리 설정/샘플 판정 확인
- 완료: 6단계(100% Nest 전환)
- 완료: 카나리/legacy 라우팅 분기 제거
- 완료: migration 전용 엔드포인트(`/_migrate/*`) 제거

참고 런북: `docs/backend-stage6-nest-traffic-rollout.md`
5 changes: 2 additions & 3 deletions docs/backend-stage4-message-ordering.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,10 @@ Stage 4의 메시지 순서 보장 정책은 `roomId`를 파티션 키로 삼고

파일:

- `packages/backend/src/rooms/RoomManager.ts`
- `packages/backend/src/observability/MetricsRegistry.ts` (`/_metrics.operations.messageOrdering`)
- `packages/backend-nest/src/core/rooms/RoomManager.ts`
- `packages/backend-nest/src/core/observability/MetricsRegistry.ts` (`/_metrics.operations.messageOrdering`)

## 한계

- 프로세스 내부 순서 보장이다.
- 멀티 노드 환경의 전역 순서 보장은 여전히 room affinity/sticky session/adapter 구성에 의존한다.

6 changes: 3 additions & 3 deletions docs/backend-stage4-state-strategy.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ Stage 4 시작 단계로, 메모리 authoritative 상태 + 외부 메타상태

## 파일

- `packages/backend/src/state/RoomStateRepository.ts`
- `packages/backend/src/rooms/RoomManager.ts`
- `packages/backend/src/server.ts`
- `packages/backend-nest/src/core/state/RoomStateRepository.ts`
- `packages/backend-nest/src/core/rooms/RoomManager.ts`
- `packages/backend-nest/src/web/ops.controller.ts`

## 다음 단계(Phase 2+)

Expand Down
39 changes: 15 additions & 24 deletions docs/backend-stage6-nest-traffic-rollout.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,27 @@
# Backend Stage 6 - Nest Traffic Rollout Runbook

Nest 병행 이관의 마지막 단계로, 트래픽을 `10% -> 25% -> 50% -> 100%`로 점진 전환한다.
이 문서는 병행 이관 당시의 카나리 전환 기록이다.
`2026-03-09` 기준으로 full cutover가 완료되어 현재는 100% Nest 단일 경로로 운영한다.

## 현재 체크포인트
## 현재 상태

- [x] `10%` + `strict=true` 동작 검증 완료
- [x] 비대상 룸은 `route=legacy`로 거절 확인
- [x] 대상 룸은 Nest 수용(`ok=true`) 확인
- [x] 카나리 단계 이관 기록 보존
- [x] `route=legacy` 경로 제거
- [x] migration 전용 엔드포인트(`/_migrate/*`) 제거

## 승격 절차
## 현재 스모크 절차

1. 비율 변경
- `NEST_TRAFFIC_CANARY_PERCENT=25` (다음 단계는 `50`, `100`)

2. 스모크 검증
- `/_migrate/room/decision`으로 allow/deny roomId 확보
- `/_migrate/room/join`으로 allow/deny 각각 기대 결과 확인
- `0%`/`100%` 특수 케이스는 단방향(deny-only/allow-only) 검증으로 자동 처리

3. 리스크 게이트 확인
1. 리스크 게이트 확인
- `/_metrics.operations.stage4RiskGates.rollbackRecommended=false`
- `/_metrics.operations.stage4RiskGates.scaleOutAllowed=true`
- `/_metrics.consistency.lastOk=true`

4. 관측 유지
- 단계별 최소 30분 관측 후 다음 비율로 승격
2. 운영 상태 점검
- `GET /health`
- `GET /_state/consistency`
- `GET /_state/rooms`

## 롤백 기준
## 롤백 기준(동일)

- `rollbackRecommended=true`
- `scaleOutAllowed=false`
Expand All @@ -35,15 +30,11 @@ Nest 병행 이관의 마지막 단계로, 트래픽을 `10% -> 25% -> 50% -> 10
## 빠른 실행 명령

```bash
# 서버 실행 예시(25% 단계)
NEST_TRAFFIC_CANARY_ENABLED=true \
NEST_TRAFFIC_CANARY_STRICT=true \
NEST_TRAFFIC_CANARY_PERCENT=25 \
NEST_TRAFFIC_CANARY_ROOM_TYPES=default \
# 서버 실행
npm run dev -w packages/backend-nest
```

```bash
# 스모크 체크 자동 실행
# full-cutover 스모크 체크 실행
npm run canary:smoke -w packages/backend-nest
```
6 changes: 3 additions & 3 deletions ecosystem.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ module.exports = {
apps: [
{
name: 'heist-be',
script: 'npx',
args: 'tsx packages/backend/src/index.ts',
script: 'node',
args: '--experimental-specifier-resolution=node packages/backend-nest/dist/main.js',
cwd: '/srv/solstartup',
env: {
NODE_ENV: 'production',
PORT: 8080,
NEST_PORT: 8081,
},
watch: false,
max_memory_restart: '300M',
Expand Down
12 changes: 6 additions & 6 deletions infra/nginx/heist-sticky-session.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@
# - ip_hash는 가장 단순한 방식이며, 운영 환경에 따라 cookie 기반 sticky가 더 적합할 수 있다.
upstream heist_backend_socketio {
ip_hash;
server 10.0.0.11:3001 max_fails=3 fail_timeout=10s;
server 10.0.0.12:3001 max_fails=3 fail_timeout=10s;
server 10.0.0.13:3001 max_fails=3 fail_timeout=10s;
server 10.0.0.11:8081 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8081 max_fails=3 fail_timeout=10s;
server 10.0.0.13:8081 max_fails=3 fail_timeout=10s;
}

# 일반 HTTP API는 sticky가 꼭 필요하지 않으므로 별도 upstream으로 분리 가능
upstream heist_backend_http {
least_conn;
server 10.0.0.11:3001 max_fails=3 fail_timeout=10s;
server 10.0.0.12:3001 max_fails=3 fail_timeout=10s;
server 10.0.0.13:3001 max_fails=3 fail_timeout=10s;
server 10.0.0.11:8081 max_fails=3 fail_timeout=10s;
server 10.0.0.12:8081 max_fails=3 fail_timeout=10s;
server 10.0.0.13:8081 max_fails=3 fail_timeout=10s;
}

server {
Expand Down
Loading