Voxel Streams 및 World Snapshots: 고성능 User Generated Content Backend 아키텍처 설계
핵심 요약
이 기사는 Voxel 기반 게임에서 고성능 User Generated Content(UGC) Backend 아키텍처를 구축하는 데 필요한 복잡한 엔지니어링을 살펴봅니다. 데이터 Payload 관리 및 Egress 비용과 같은 기술적 장애물을 다루며 World Snapshots, Delta Compression 및 콘텐츠 주소 지정 가능 스토리지와 같은 솔루션을 제안합니다. 이러한 패턴을 구현하거나 horizOn과 같은 전문 플랫폼을 사용함으로써 개발자는 확장 가능하고 커뮤니티 중심적인 게임 세계를 구축할 수 있습니다.
플레이어들이 Voxel RPG에서 40시간 동안 공중 대성당을 지었는데, 이제 그것을 10,000명의 낯선 사람들과 공유하고 싶어 합니다. 여러분의 Backend는 4TB의 Egress 비용을 감당할 준비가 되었습니까? 대부분의 개발자는 User-Generated Content(UGC)를 단순한 파일 업로드 문제로 취급하지만, Enshrouded와 같은 Voxel 기반 세계를 다룰 때 기술적 현실은 훨씬 더 복잡합니다. 단순한 파일 호스팅이 아니라 성능, 비용 효율성 및 보안을 유지해야 하는 분산 세계 상태 동기화 시스템(world-state synchronization)을 설계해야 하기 때문입니다.
인디 게임의 '게화(Carcinisation)': 게임에서 플랫폼으로의 전환
최근 Enshrouded의 'Forging the Path' 업데이트는 플레이어가 자신의 세계 상태를 패키징하여 커뮤니티와 공유할 수 있는 기능인 Adventure Sharing을 도입했습니다. 이러한 움직임은 업계에서 종종 'Roblox 사건의 지평선'이라 불리는 성장 트렌드를 강조합니다. 게임은 더 이상 정적인 경험이 아니라 창작을 위한 플랫폼이 되고 있습니다. 이러한 게임의 '게화'는 장르에 관계없이 장기적인 Player Retention을 원한다면 결국 User Generated Content Backend 아키텍처의 기술적 부채를 해결해야 함을 의미합니다.
Voxel에 크게 의존하는 Enshrouded와 같은 게임의 경우 도전 과제는 두 배입니다. Voxel은 무한한 창의성과 완전한 파괴를 가능하게 하지만 엄청난 양의 데이터를 생성합니다. 고도로 디테일한 베이스 하나가 50MB의 원시 Voxel 데이터를 쉽게 초과할 수 있습니다. 여기에 100,000명의 플레이어를 곱하면 게임이 버전 1.0에 도달하기도 전에 스토리지 비용만으로 스튜디오가 파산할 수 있습니다.
Voxel 데이터 Payload 문제
이를 위한 Backend를 어떻게 설계할지 이해하려면 먼저 실제로 무엇이 공유되는지 살펴봐야 합니다. Voxel 엔진에서 세계는 일반적으로 Chunk(예: 16x16x16 또는 32x32x32)로 나뉩니다. 각 Chunk에는 Voxel ID, 조명 데이터, 그리고 상자나 제작대와 같은 엔티티의 Metadata가 포함되는 경우가 많습니다.
플레이어가 '모험을 공유'할 때 게임은 'World Snapshot'을 수행해야 합니다. 이것은 단순히 세이브 폴더를 복사하여 붙여넣는 것이 아닙니다. 여기에는 다음이 필요합니다.
- Pruning(정리): 공유 버전에 포함될 필요가 없는 일시적인 데이터(드롭된 아이템이나 활성 몬스터 AI 상태 등)를 제거합니다.
- Serializing(직렬화): 메모리 내 Octree 또는 그리드 구조를 Flat Buffer로 변환합니다.
- Compression(압축): Payload를 줄이기 위해 LZ4 또는 Zstandard와 같은 알고리즘을 적용합니다.
이를 올바르게 처리하지 않으면 The Unreal Engine Multiplayer Sync Bug Ruining Your World States And How To Fix It에 설명된 것과 동일한 문제에 직면하게 되며, 클라이언트와 공유 스냅샷 사이의 세계 상태 불일치로 인해 구조가 손상되거나 '고스트' Voxel이 발생할 수 있습니다.
Delta Compression: 변경된 내용만 전송
UGC 아키텍처에서 흔히 저지르는 실수는 플레이어가 공유된 'Adventure'를 약간 업데이트할 때마다 전체 세계 상태를 업로드하는 것입니다. 대신 Delta Compression을 구현해야 합니다. Voxel Chunk의 현재 상태를 'Base World'(절차적 시드)와 비교하여 수정된 내용(Delta)만 저장하면 됩니다.
기본 세계에서 Chunk (10, 5, 2)가 단단한 산인데 플레이어가 그곳에 터널을 뚫었다면, Backend는 'Stone' Voxel을 대체한 'Air' Voxel만 기록하면 됩니다. 이를 통해 10MB Chunk를 몇 킬로바이트로 줄일 수 있습니다.
Backend 파이프라인 설계
클라이언트가 압축된 Snapshot 또는 Delta를 생성하면 Backend가 이를 이어받습니다. 강력한 User Generated Content Backend 아키텍처는 Ingestion Layer(수집 계층), Storage Layer(저장 계층), Discovery Layer(탐색 계층)의 세 가지 주요 레이어로 구성됩니다.
1. Ingestion Layer (검증 및 바이러스 검사)
클라이언트를 절대 믿지 마십시오. 악의적인 사용자가 실제로는 0으로 가득 찬 2GB 파일(Zip Bomb)이거나 Voxel 디직렬화기를 악용하도록 설계된 Payload인 'Snapshot'을 업로드할 수 있습니다. 수집 서버는 다음을 수행해야 합니다.
- 전체 스트림을 수락하기 전에 파일 헤더와 크기를 검증합니다.
- Payload를 샌드박스 디직렬화기에서 실행하여 서버가 충돌하지 않는지 확인합니다.
- 중복 업로드를 방지하기 위해 고유한 Content Hash(예: SHA-256)를 생성합니다.
2. Storage Layer (Blobs vs. Metadata)
바이너리 데이터(Voxel Blob)를 검색 가능한 데이터(플레이어 이름, 모험 태그, 평점)와 분리하십시오.
- Blobs: S3 호환 객체 스토리지를 사용하십시오. 트래픽이 많은 게임의 경우 Content Delivery Network(CDN)를 사용하여 이러한 Blob을 에지에서 캐싱하십시오.
- Metadata: 인덱싱을 위해 PostgreSQL과 같은 관계형 데이터베이스를 사용하십시오. 이를 통해 플레이어는 1밀리초 미만의 레이턴시로 '하이 판타지' 또는 '레벨 10-20' 모험을 검색할 수 있습니다.
3. Discovery Layer (실시간 업데이트)
새로운 모험이 게시되면 다른 플레이어들이 즉시 볼 수 있기를 원할 것입니다. 클라이언트가 30초마다 API를 폴링(Polling)하게 하는 대신 WebSockets를 사용하여 새 콘텐츠 알림을 푸시하십시오. 이를 설정하는 방법에 대한 자세한 내용은 Ditch Http Polling An Unreal Engine Websockets Tutorial For Real Time Backends 가이드를 확인하십시오.
구현: C# UGC 관리자 예시
다음은 Unity 기반 게임에서 UGC 업로드 관리자를 구성하는 방법에 대한 간략한 예시입니다. 이 코드는 압축 및 멀티파트 업로드 프로세스를 처리하여 대용량 Voxel 파일이 느린 연결에서 타임아웃되지 않도록 합니다.
using System;
using System.IO;
using System.IO.Compression;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Networking;
public class AdventureUploader : MonoBehaviour
{
private const string API_URL = "https://api.yourgame.com/v1/ugc/upload";
public async Task ShareAdventure(string adventureId, byte[] rawVoxelData, AdventureMetadata metadata)
{
// 1. 대역폭 절약을 위해 데이터를 로컬에서 압축
byte[] compressedData = await CompressData(rawVoxelData);
Debug.Log($"Compressed world state from {rawVoxelData.Length / 1024}KB to {compressedData.Length / 1024}KB");
// 2. 멀티파트 폼 생성
WWWForm form = new WWWForm();
form.AddField("adventureId", adventureId);
form.AddField("title", metadata.Title);
form.AddBinaryData("worldBlob", compressedData, "world.vox", "application/octet-stream");
// 3. Backend로 전송
using (UnityWebRequest www = UnityWebRequest.Post(API_URL, form))
{
var operation = www.SendWebRequest();
while (!operation.isDone) await Task.Yield();
if (www.result != UnityWebRequest.Result.Success)
{
Debug.LogError($"UGC Upload Failed: {www.error}");
}
else
{
Debug.Log("Adventure shared successfully!");
}
}
}
private async Task<byte[]> CompressData(byte[] data)
{
using (var outputStream = new MemoryStream())
{
using (var gZipStream = new GZipStream(outputStream, CompressionMode.Compress))
{
await gZipStream.WriteAsync(data, 0, data.Length);
}
return outputStream.ToArray();
}
}
}
[Serializable]
public class AdventureMetadata
{
public string Title;
public string Description;
public string CreatorId;
}
자체 구축 비용
이 파이프라인을 수동으로 설계하는 것은 상당한 작업입니다. 다음을 관리해야 합니다.
- Load Balancers: 유명 스트리머가 모험을 공유할 때 발생하는 트래픽 급증을 처리합니다.
- Database Sharding: Metadata 테이블의 행이 수백만 개에 달할 때를 대비합니다.
- CDN Invalidations: 플레이어가 모험을 업데이트할 때 이전 버전이 캐시에서 제공되지 않도록 보장합니다.
이를 직접 구축하려면 일반적으로 시니어 엔지니어의 2~3개월 전담 시간이 소요됩니다. 여기서 horizOn은 엄청난 가치를 제공합니다. 바이너리 Blob 스토리지 및 Metadata 인덱싱을 위한 '배관'을 구축하는 대신 horizOn은 사전 설계된 UGC 모듈을 제공합니다. Metadata Schema만 정의하면 horizOn이 글로벌 배포, 파일 검증 및 확장을 자동으로 처리합니다. 이를 통해 S3 버킷 정책을 걱정하는 대신 'Adventure' 게임플레이를 재미있게 만드는 데 집중할 수 있습니다.
UGC Backend 아키텍처를 위한 5가지 모범 사례
- 콘텐츠 주소 지정 가능 스토리지(CAS) 구현: Voxel 데이터의 해시를 파일 이름으로 사용하십시오. 두 플레이어가 동일한 베이스를 공유하는 경우 물리적 파일을 한 번만 저장하여 엄청난 양의 스토리지를 절약할 수 있습니다.
- 비동기 수집(Asynchronous Ingestion) 사용: 플레이어가 Backend에서 파일을 처리하는 동안 기다리게 하지 마십시오. 즉시 '202 Accepted'를 반환하고 백그라운드 워커를 사용하여 검증 및 썸네일 생성을 처리하십시오.
- 계층형 스토리지 전략(Tiered Storage Strategy): 가장 인기 있는 상위 100개 모험은 'Hot' 캐시(Redis/CDN)에 보관하고, 오래되고 플레이되지 않는 모험은 'Cold' 스토리지(S3 Glacier)로 이동하여 비용을 낮게 유지하십시오.
- 스키마 버전 관리: 게임을 업데이트함에 따라 Voxel 포맷이 변경됩니다. Backend는 업로드할 때마다
format_version정수를 저장하여 이전 모험을 원활하게 마이그레이션하거나 지원 중단할 수 있어야 합니다. - 모든 것에 속도 제한(Rate Limit) 적용: UGC는 게임 서버를 DDoS 공격하는 가장 쉬운 방법입니다. 단일 IP 또는 PlayerID가 콘텐츠를 업로드하거나 검색할 수 있는 빈도에 대해 엄격한 속도 제한을 구현하십시오.
결론: 공유 세계의 미래
Enshrouded가 보여주듯이 인디 게임의 기술적 기준이 높아지고 있습니다. 플레이어들은 자신의 창작물을 원활하게 공유할 수 있기를 기대하며, 'Adventure Sharing'은 사치가 아니라 빠르게 표준 기능이 되고 있습니다. 개발 초기 단계부터 강력한 User Generated Content Backend 아키텍처에 집중함으로써 인프라가 감당할 수 없을 정도로 커뮤니티가 성장했을 때 발생하는 고통스러운 리팩토링을 피할 수 있습니다.
서버 관리의 번거로움 없이 멀티플레이어 Backend를 확장할 준비가 되셨습니까? horizOn을 무료로 사용해 보거나 API 문서를 확인하여 대량의 UGC 및 세계 상태 유지 관리를 어떻게 처리하는지 확인해 보십시오.