Files
sunrise/docs/multiplayer-server.md
T

6.7 KiB

관악산 공동 관측 서버

Node.js 22.18 이상. 단일 프로세스에서 gwanaksan 장소의 gwanaksan-001 방을 운영한다. 입장한 참여자는 최대 100명이며 자신과 다른 탭도 각각 포함한다. Socket.IO 클라이언트는 HTTP long-polling으로 연결 후 WebSocket으로 업그레이드할 수 있다.

실행

npm ci
# 인증이 필요하거나 주소/포트를 바꿀 때 .env.example을 .env로 복사해 편집
npm run build
npm start

기본 접속: http://127.0.0.1:3000. 기본 DB: mongodb://172.16.0.7:27017, DB 이름 sunrise, 컬렉션 visitSessions. 인증이 필요하면 MONGODB_URI에 계정과 authSource 등 해당 서버 설정을 지정한다. URI를 브라우저용 VITE_* 변수에 넣지 않는다. .env는 버전 관리에서 제외한다.

개발 시 별도 터미널에서 npm run dev:server와 npm run dev를 실행한다. Vite의 /socket.io, /health, /api 요청은 3000 포트 서버로 전달된다. npm run preview는 프런트 미리보기이며 공동 방 서버를 대신하지 않는다.

HOST 기본값은 127.0.0.1이라 현재 컴퓨터에서만 접속할 수 있다. LAN/운영 배포 시 실제 바인딩 주소, PORT, ALLOWED_ORIGINS를 지정하고 TLS를 제공하는 프록시와 방화벽을 구성한다. 이번 구현은 외부 호스팅 배포까지 수행하지 않는다. 동일 방/정원을 여러 Node 프로세스에서 따로 실행하지 않는다. 프로세스별 메모리는 공유되지 않으며 현재는 단일 인스턴스 구성이다.

동작

  • 공동 관측은 서버 UTC 시각을 기준으로 진행한다. 날짜 입력과 시간 슬라이더는 잠기며, 혼자 보기에서 기존 LIVE/CUSTOM 기능을 사용할 수 있다.
  • 위치·방향·인사/기도·우산·우비·후드를 서버 메모리에 저장한다. 초당 최대 10회 입력을 검증하고, 변경이 있으면 같은 방에 모아서 보낸다. 신규 입장에는 전체 참여자 상태를 전달한다. 5초마다 전체 상태도 다시 보낸다.
  • 현재는 100명 상한에서 단순성을 위해 변경이 있을 때 방 전체 스냅샷을 보낸다. 여러 방/대규모 확장 시 측정 결과에 따라 변경분 전송을 검토한다.
  • 보행 검증은 기존 관악산 경로·암반 영역과 이동 속도를 재사용한다. 위치 갱신 거리를 시간 기반 예산으로 제한하고 잘못된 좌표는 승인된 위치로 되돌린다. 완전한 서버 물리 시뮬레이션은 아니다.
  • 같은 장소의 날씨는 서버가 기존 조회·캐시 로직으로 읽어서 공유한다. 장비 착용 UI는 기존 비·눈 조건을 유지한다. 장비 자체는 영구 저장하지 않는다.
  • 같은 브라우저의 익명 visitorId를 localStorage에 보존하되, 로그인 인증이나 실제 사람의 신원으로 취급하지 않는다. 각 입장에는 별도의 서버 발급 방문 ID를 사용한다. 재접속 시 시작점과 장비 기본값으로 돌아온다. 시작점 복귀 버튼은 장비를 유지한다.
  • 연결이 끊기면 원격 캐릭터를 정리하고 재접속을 시도한다. heartbeat로 단절을 감지하기 전에는 자리가 잠시 유지될 수 있다. 혼자 보기로 전환하면 연결을 종료한다.
  • 101번째 참여자는 입장을 거절하고 재시도/혼자 보기를 제공한다. MongoDB 기록을 저장하는 동안에도 자리를 예약해 동시 입장으로 정원을 초과하지 않는다.

접속 기록

저장 필드: _id, visitorId, nickname, locationId, roomId, joinedAt, lastSeenAt, leftAt, endReason, serverInstanceId.

입장 기록 저장 성공 후 입장을 확정하고, 접속 중에는 60초마다 lastSeenAt을 갱신한다. 연결 종료와 정상 서버 종료에서 종료 정보를 기록한다. 위치·장비·감정표현은 DB에 쓰지 않는다.

서버 시작 시 과거 기록을 자동으로 변경하지 않는다. 강제 종료 또는 DB 장애로 퇴장 기록을 남기지 못하면 leftAt이 null로 남을 수 있다. 이는 현재 접속 중이라는 증거가 아니다. 실제 정원은 메모리에서 계산하며 lastSeenAt은 마지막 확인 시각일 뿐 정확한 퇴장 시각이 아니다. 로그 무손실 보장을 위한 디스크 큐와 자동 복구 작업은 포함하지 않는다.

DB 연결 실패 시 서버 시작을 중단한다. 실행 중 기록 저장 실패 시 /health는 503이고 신규 입장은 막으며 기존 참여자의 이동은 계속 처리한다. 원인을 해결한 후 서버를 재시작한다. 임의로 메모리 저장소로 전환하지 않는다. 개발·테스트에서만 VISIT_STORE=memory를 명시하면 기록을 영구 저장하지 않는 모드로 실행한다.

검증

npm test
npm run build
npm run verify:mongo
npm run test:load

verify:mongo는 매 실행마다 UUID가 붙은 임시 DB를 생성해 insert/heartbeat/finish/read 및 연결 시 기존 기록 보존을 확인한 뒤 그 DB만 삭제한다. sunrise 운영 데이터는 사용하지 않는다.

test:load는 메모리 저장소의 독립 로컬 서버와 100개 클라이언트를 한 프로세스에서 실행한다. 기본 300초이며 PowerShell에서 $env:LOAD_SECONDS='10'으로 짧은 실행도 가능하다. 보고서는 artifacts/multiplayer/load-report.json에 저장한다. 101번째 입장 거절·퇴장 후 자리 재사용·10Hz 이동을 검사한다.

2026-10-30 로컬 5분 결과: 오류 0, 이동 보정 0, 예상하지 않은 연결 종료 0. 방송 이후 수신 지연 p95 6ms, 최대 61ms. 이 값은 입력부터 방송까지의 대기 시간을 제외하며 실제 인터넷 지연·서버 단독 CPU 사용률·MongoDB 부하·모바일 GPU 성능을 보장하지 않는다. 두 브라우저에서 상호 캐릭터 표시/이동, 인원 표시, 혼자 보기와 재입장을 확인했다. 별도 메모리 검증 서버에서 99개 가상 참여자에 실제 브라우저 한 개가 입장해 100/100명 표시 및 양·우산·우비 렌더링을 확인했다. 해당 장면의 정량 프레임 시간과 모바일 성능은 측정하지 않았다.

주요 파일

  • server/app.ts: HTTP/Socket.IO 서버, 정적 파일, 상태 확인
  • server/room.ts: 정원 예약, 입퇴장, 상태 검증과 방송
  • server/visits.ts: MongoDB 방문 기록과 명시적 메모리 테스트 저장소
  • src/multiplayer/protocol.ts: 클라이언트/서버 공통 이벤트 타입
  • src/multiplayer/client.ts: 입장·재접속·현재 상태·시각 동기화
  • src/multiplayer/remotePlayers.ts: 다른 참여자의 양 캐릭터 표시와 보간
  • src/constants/multiplayer.ts, server/constants/multiplayer.ts: 통신·정원·서버 제한