사이드프로젝트 slecs

WebSocket 멀티플레이어를 혼자 붙이면 생기는 일

목차

Simon Willison의 battledrones v14 릴리스 노트를 읽다가 잠깐 멈췄다. 4인용 맵 추가. 이전까지는 2인 대전이 전부였는데, 이번 버전에서 4방향 회전 대칭 맵을 설계하고 WebSocket 기반 멀티플레이어를 4명까지 확장했다. 오디오 없이 동작하는 멀티플레이어 모드라서 브라우저 정책 충돌 없이 누구든 조인할 수 있다는 내용이다. 구현 세부 사항은 직접 읽어봐야 알겠지만, 기능 설명보다 제목에서 먼저 눈을 잡아끄는 게 있었다.

v14.

14번째 릴리스다. 누군가 이 프로젝트에 14번 돌아왔다는 뜻이다.

숫자가 쌓이는 프로젝트

사이드 프로젝트 대부분은 v2에서 v3 사이 어딘가에서 멈춘다. 처음엔 "일단 돌아가면" 하고 출발하지만, 배포 한 번 하고 나면 다시 열기가 귀찮다. 고쳐야 할 것들은 눈에 보이는데, 지금 당장 불편함을 느끼는 사람이 나밖에 없다 싶으면 손이 안 간다.

v14까지 간다는 건 그 귀찮음을 14번 이겼다는 거다. 작은 기능 하나, 맵 하나, 밸런스 조정 하나. 그 작은 이유들이 계속 그 사람을 코드로 끌어당긴 거다. 이번엔 "4인용 대전이 되면 재밌겠다"는 생각이 그 이유였겠지.

사이드 프로젝트를 오래 유지하는 방법을 물으면 흔히 "완성도를 낮춰라", "작게 시작해라" 같은 말이 나온다. 틀린 말은 아닌데, 충분하지는 않다. 실제로 필요한 건 다음 릴리스를 만들고 싶어지는 구체적인 이유다. battledrones의 경우엔 게임 자체가 그 이유였을 거다. "이 맵에서 직접 해보고 싶다"는 욕구. 그게 있으면 v14까지 간다. 없으면 v3에서 멈춘다.

멀티플레이어를 붙이는 결정

WebSocket 기반 실시간 멀티플레이어는 HTTP 요청-응답 구조로 만든 서비스에서 구조 변화가 가장 큰 기능 중 하나다. 연결이 살아있어야 하고, 서버가 상태를 들고 있어야 하고, 연결이 끊겼다 다시 붙었을 때 처리 방식을 결정해야 한다.

2인 대전이 작동하는 걸 확인한 다음 4인으로 확장하는 건 상대적으로 쉬워 보인다. 4방향 회전 대칭이라는 맵 설계는 꽤 깔끔한 선택이다. 대칭이면 초기 위치 불균형이 없고, 스폰 로직도 단순해진다. 하지만 4개 연결을 동시에 들고 있을 때 어느 시점에 레이턴시가 튀는지, 한 명이 접속을 끊으면 나머지 세 명한테 어떻게 통보하는지 - 이런 건 직접 돌려봐야 안다. 릴리스 노트에 "이 정도면 됩니다"가 쓰여 있어도 내 환경에서 직접 확인하기 전엔 그냥 믿을 수가 없다.

혼자 서비스를 운영할 때 WebSocket을 붙이는 순간 인프라 선택 기준이 달라진다. 정적 사이트나 서버리스 함수로 처리되던 게 이제는 연결을 유지할 서버가 필요하다. 연결이 몇 명까지인지, 게임이 끝나면 소켓이 제대로 닫히는지, 사용자가 탭을 그냥 닫으면 서버는 언제 알아채는지. 이 문제들은 혼자 테스트하기 까다롭다. 로컬에서 탭 세 개 열어 시뮬레이션하다 보면 그게 실제 다른 기기의 네트워크 환경과 얼마나 다른지 금방 느끼게 된다.

그래서 battledrones 같은 게임이 v14까지 가는 방식이 납득된다. 한 번에 4인 온라인 멀티플레이어를 다 만들려고 했으면 아마 v3 근처에서 막혔을 거다. v1 단일 플레이어, v어딘가에서 로컬 2인, v어딘가에서 온라인 2인, 그리고 v14에서 4인. 매 버전마다 한 단계씩 복잡도를 올린 거다.

에셋을 서빙하는 비용, 생각보다 일찍 생긴다

게임 프로젝트엔 보통 에셋이 붙는다. 맵 파일, 스프라이트, 오디오 파일. 처음엔 어디서 서빙하든 별 차이 없어 보이지만 트래픽이 생기면 달라진다.

Cloudflare가 이번에 Origin Range Requests 관련 변경 사항을 발표했다. 큰 파일에 대한 바이트 범위 요청이 들어올 때 캐시 아키텍처를 새로 짜서 R2에서 p99 레이턴시를 절반으로 줄였다는 내용이다. 핵심은, 기존 방식에서는 작은 바이트 범위 요청이 들어오면 실제 요청 크기보다 훨씬 큰 청크를 오리진에서 가져오는 비효율이 있었다는 거다. 그게 기가바이트 단위 오리진 트래픽 낭비로 이어졌다.

이게 나한테 직접 닿는 시나리오는 이렇다. R2에 큰 파일을 올려두고 브라우저가 range 요청으로 일부만 받아가는 경우. 동영상 파일, 큰 에셋 번들, 맵 데이터. 예전엔 Cloudflare를 R2 앞에 두고 "알아서 캐시하겠지"라고 넘기는 게 관행이었는데, 작은 range 요청마다 오리진에서 더 큰 청크를 가져오는 낭비가 실제로 있었다는 거다.

새 아키텍처의 내부 동작은 글을 직접 읽어야 알겠지만, 수치가 나왔으니 배포된 건 맞다. 혼자 운영하는 입장에서 이런 변화의 의미는 단순하다. 내가 설정을 건드리지 않아도 자동으로 좋아졌다면 그냥 고마운 거다. 반대로 명시적으로 설정해야 하는 부분이 생겼다면, 기존 배포에 영향 있는지 한 번 확인해볼 가치는 있다. R2 사용 중이라면 이번 주에 대역폭 수치 한 번 들여다보는 게 나쁘지 않다.

실시간이 들어오면 비용 구조가 바뀐다

WebSocket을 붙이고 나면 "이 서비스 월에 얼마?" 계산이 달라진다. HTTP 요청 수 기반 요금에서 연결 시간 기반 요금으로. 사용자가 10분 게임을 하면 그 10분 동안 연결이 열려 있다. 동시 접속 50명이면 서버 한 대에서 어느 지점이 병목인지, 메모리인지 연결 한도인지 - 이것도 직접 부하를 줘봐야 안다.

처음 만들 때부터 이걸 다 고려하면 프로젝트가 시작조차 못 한다. battledrones가 v14에서 4인 멀티플레이어를 붙일 수 있었던 건, 그 이전 버전들이 더 단순한 구조로 동작한다는 걸 이미 확인했기 때문일 거다. 기반이 있으니 그 위에 하나 더 올릴 수 있는 거다.

그리고 그 사이에 인프라는 조용히 더 좋아져 있다. Cloudflare가 range 요청 처리를 고쳤고, 내년엔 또 다른 무언가가 고쳐져 있을 거다. 그 흐름을 타면서 코드만 계속 쓰면 된다.


참고한 글

게이트웨이 계층이 드디어 WebSocket을 따라잡고 있다

Google이 API Gateway에 스트리밍 지원을 추가했다. SSE, WebSocket, gRPC, 청크 전송 인코딩까지, 기존 API 스펙을 건드리지 않아도 게이트웨이를 통해 실시간 연결을 라우팅할 수 있게 됐다는 내용이다.

혼자 WebSocket 서버를 운영해본 사람이라면 이게 왜 반가운 소식인지 바로 안다. HTTP 기반 서비스에서는 게이트웨이 앞에 두는 게 당연한 수순이었다. 인증, 속도 제한, 로깅, 라우팅, 이 모든 게 게이트웨이 한 층에서 처리됐다. 그런데 WebSocket은 연결이 살아있는 동안 상태가 유지되는 구조라 기존 게이트웨이 모델이랑 잘 안 맞았다. 결국 WebSocket 서버는 게이트웨이를 우회해서 직접 노출하거나, 인증 레이어를 직접 구현하거나 해야 했다.

아직 Public Preview다. 지금 당장 프로덕션에 올리는 건 무리다. 하지만 방향은 읽힌다. 클라우드 인프라가 실시간 연결을 HTTP 요청과 같은 1등 시민으로 취급하기 시작했다는 거다. 이게 GA가 되면 "WebSocket이라서 게이트웨이를 못 쓴다"는 결정을 다시 내릴 필요가 없어진다.

타이밍 얘기를 하자면, 지금 당장 붙이려 하면 Preview 문서 뒤지면서 설정 삽질하는 데 시간을 쓰게 된다. 6개월 뒤에 같은 작업을 하면 문서가 정리돼 있고 알려진 함정들이 스택오버플로에 쌓여 있을 거다. 기반이 안정화될 때까지 기다리는 것도 기술 선택이다. battledrones가 v1에서 4인 멀티플레이어를 안 만든 것처럼.


참고한 글


이번 주를 돌아보며

세 꼭지를 나란히 놓고 보면 공통된 맥락이 보인다.

battledrones는 v1부터 v14까지 쌓아온 기반 위에서 4인 멀티플레이어를 올렸다. Cloudflare는 range 요청 처리를 수년 만에 고쳤다. Google은 WebSocket을 게이트웨이에서 HTTP 요청과 같은 레벨로 다루기 시작했다. 세 이야기 모두 "지금 당장"이 아니라 "어느 시점에 드디어"의 구조다.

혼자 사이드 프로젝트를 굴리다 보면 지금 당장 완성된 것을 내놔야 한다는 압박이 있다. 근데 이 뉴스들을 보면서 다시 생각하게 됐다. 인프라도, 기능도, 도구도, 다 어느 버전까지는 느리게 간다. 중요한 건 그 사이에 프로젝트가 살아있느냐다. 아무리 좋은 인프라가 나와도 갖다 쓸 코드베이스가 없으면 의미가 없다.

v14가 될 때까지 버티는 게 전략이다. 그 동안 Cloudflare가 range 요청을 고치고, Google이 WebSocket 게이트웨이를 GA로 올린다. 그 흐름 위에 올라타는 건 그때 하면 된다.

이번 주는 별다른 배포 없이 구조를 들여다본 주였다. 다음 주엔 코드 한 줄이라도 더 써야겠다.

댓글 0

첫 댓글 달아줘.