핫픽스와 업데이트 전략
긴급 핫픽스와 정기 업데이트를 구분하고 전체·증분 패치 생성과 CDN 배포 전략을 설계합니다.
출시 다음 날 아침, 특정 보스를 잡으면 게임이 멈춘다는 신고가 쏟아진다고 해 봅시다. 몇 GB짜리 게임 전체를 다시 내려받게 할 수는 없고, 그렇다고 며칠을 기다리게 할 수도 없습니다. 반대로 한 달에 한 번 새 맵과 캐릭터를 더하는 업데이트는 크기가 크더라도 준비할 시간이 있습니다. 출시한 게임을 고치고 넓혀 가려면, 변경의 성격에 맞는 전달 방식과 절차를 미리 정해 두어야 합니다.
디버깅과 로그 관리로 문제를 찾았다면, 이 글에서는 그 수정을 플레이어에게 전달하는 방법을 다룹니다. 핫픽스(hotfix)와 업데이트(update)를 구분하고, 언리얼에서 패치 파일을 만들고 적용하는 방법, 여러 업데이트 전략과 고려할 점, CI/CD와의 연계를 차례로 살펴봅니다.
핫픽스와 업데이트
핫픽스는 크래시, 진행 불가 버그, 악용할 수 있는 결함처럼 게임 플레이에 심각한 영향을 주는 버그를 최대한 빨리 고쳐 배포하는 것입니다. 핫픽스를 가르는 기준은 급한 정도와 좁은 변경 범위이며, 코드를 고치는 것도 핫픽스일 수 있습니다. 그러니 핫픽스라는 이름만으로 다운로드가 작거나 재시작 없이 적용된다는 보장은 없습니다. 미리 준비해 둔 데이터 갱신 경로를 쓰거나, 플랫폼의 패치와 재시작 절차를 따르며, 변경의 종류와 지금 무엇이 로드되어 있는지에 따라 적용 시점이 달라집니다.
업데이트나 패치(patch)는 버그 수정 외에도 새 맵, 캐릭터, 아이템 같은 콘텐츠, 기능 추가, 성능 개선, 밸런스 조정처럼 넓은 변경을 담습니다. 보통 핫픽스보다 크고 적용하는 데 시간과 대역폭이 더 들며, 클라이언트 전체나 바뀐 부분을 다시 내려받게 합니다. 게임 런처나 플랫폼 스토어로 내려받아 설치합니다.
언리얼의 패치 관련 기능
패치 파일 만들기
언리얼은 프로젝트 설정에 따라 콘텐츠를 Pak 파일이나 IoStore(.utoc/.ucas) 같은 컨테이너에 담습니다. 아래 명령은 Epic의 공식 Pak 기반 패치 절차의 예이므로, IoStore로 배포하는 절차에 그대로 적용된다고 가정하지 않습니다.
먼저 기준이 될 릴리스를 만들어 보관합니다. 평소의 BuildCookRun 인자에 -build -cook -stage -pak -createreleaseversion=1.0을 더하면 기준 릴리스가 만들어지고, Releases/1.0에 생긴 메타데이터를 잘 보존해 둡니다. 패치를 만들 때는 같은 프로젝트와 플랫폼의 기본 인자에 -build -cook -stage -pak -generatepatch -basedonreleaseversion=1.0을 씁니다. basedonreleaseversion만 넣거나 cookonthefly를 켠다고 패치가 만들어지지는 않습니다.
패치에는 바뀐 에셋 패키지가 통째로 들어갑니다. 파일 안에서 바뀐 바이트만 골라 보내는 방식과는 다릅니다. UnrealPak.exe는 .pak 파일을 만들고, 압축하고, 풀어 보는 명령줄 도구로, 패치 파일을 직접 조합할 때 씁니다.
내려받은 패치 적용하기
언리얼은 실행 중에 .pak 파일을 마운트(mount)해 게임 콘텐츠로 인식하게 하는 기능을 제공합니다. 내려받은 Pak의 버전과 무결성을 확인한 뒤 지원되는 마운트 경로에 연결하는데, 마운트는 파일에 접근할 경로를 하나 더하는 단계일 뿐입니다. 이미 로드된 UObject를 저절로 바꾸거나 코드를 새로 고치지는 않습니다. 이런 적용 과정은 보통 직접 만든 패치 시스템이나 런처에서 구현합니다.
개발 중 변경과 배포 패치는 다르다
에디터에서 에셋을 고치거나 라이브 코딩으로 코드를 바꾸는 것은 배포된 게임을 갱신하는 방법이 아닙니다. 실행 코드와 클래스 구조의 변경은 해당 플랫폼의 새 바이너리 배포와 재시작 정책으로 다룹니다. 또 에셋 레지스트리(Asset Registry)는 에셋을 찾기 위한 메타데이터를 관리하는데, 새 콘텐츠가 레지스트리에 반영되고 검색되는 일과 기존 객체를 다시 불러오는 일은 따로이며, 최신 패키지를 마운트했다고 끝나지 않습니다.
숫자로 따라가 보기
Pak 기반 기준 릴리스 1.0을 보관해 두었고, 같은 프로젝트와 플랫폼에서 패치를 만든다고 합시다. 바꾼 에셋 패키지 두 개가 각각 1MiB와 3MiB라면, 압축, 메타데이터, 플랫폼의 추가 전송을 뺀 이 계산에서 바뀐 패키지의 합은 4MiB입니다. 두 패키지 안에서 한 바이트씩만 고쳤더라도 다운로드가 2바이트인 것은 아닙니다. 패키지 단위로 들어가기 때문입니다. 실제 전송량은 만든 산출물과 배포 플랫폼에서 따로 확인합니다.
또 1.0에서 전투 설정 객체를 이미 불러온 클라이언트는, 새 Pak을 마운트해도 그 객체와 그것을 쓰는 쪽이 가진 예전 값이 저절로 바뀌지 않습니다. 준비해 둔 재조회, 검증, 적용 경로를 거치거나, 다음에 시작할 때 새로 불러오는 경로가 필요합니다. 실행 코드 수정도 들어 있다면 새 바이너리, 플랫폼 설치, 재시작 정책까지 따로 다룹니다. Epic의 How to Create a Patch 문서에서 바뀐 패키지 단위와 보관한 릴리스 기준을 확인할 수 있습니다.
업데이트 전략 고르기
게임의 특성, 플랫폼, 팀의 여력에 따라 여러 업데이트 전략 가운데 고릅니다. 아래 세 전략은 한 번에 보내는 양이 점점 작아지는 순서로 놓았습니다. 전체 클라이언트, 바뀐 패키지만 담은 증분 패치, 실행 중에 바꾸는 작은 데이터 순입니다. 보내는 양이 작을수록 플레이어는 덜 기다리지만, 무엇이 바뀌었는지 추적하고 검증하는 일은 개발팀이 더 많이 떠맡게 됩니다. 마지막의 CDN은 이 가운데 어느 전략을 고르든 파일을 전달하는 경로로 함께 씁니다.
전체 클라이언트 다운로드
업데이트할 때마다 플레이어가 게임 클라이언트 전체를 다시 내려받게 하는 가장 단순한 방법입니다. 구현이 아주 간단하고 파일 손상이나 패치 관련 문제가 적지만, 플레이어에게 가장 불편하고 큰 게임에서는 엄청난 대역폭과 다운로드 시간이 듭니다. 배포 플랫폼이 바뀐 부분만 내려받게 해 주는지에 따라 실제 전송량은 달라질 수 있습니다. 게임이 아주 작거나 업데이트가 아주 드물 때 알맞습니다.
증분 패치
이전에 배포한 버전을 기준으로 바뀌거나 새로 생긴 파일만 내려받게 하는 방식이며, 언리얼의 Pak 패치는 보관한 기준 릴리스와 generatepatch, basedonreleaseversion 옵션을 함께 씁니다. 다운로드 크기가 크게 줄어 플레이어의 대역폭과 기다리는 시간을 아낄 수 있습니다. 대신 패치 시스템이 복잡해집니다. 어떤 파일이 바뀌었는지 정확히 추적하고, 파일의 무결성을 검증하고, 패치를 적용하다 생기는 오류를 처리해야 합니다. 대부분의 PC 온라인 게임과 정기 업데이트가 필요한 게임에 알맞습니다.
실행 중 적용하는 핫픽스
게임이 실행 중이거나 다시 시작할 때 설정 값, 밸런스 수치, 텍스트 같은 아주 작은 데이터 변경을 네트워크로 내려받아 적용하는 방식입니다. 코드를 직접 바꾸는 것은 대부분 불가능하며, 주로 데이터 테이블, JSON 파일, 또는 특정 코드 경로로 동적으로 불러오는 에셋에 한정됩니다.
플레이어가 게임을 완전히 다시 시작하지 않고도 수정을 바로 경험할 수 있어 긴급 수정에 효과적입니다. 하지만 구현이 가장 복잡하고, 어떤 데이터를 핫픽스할 수 있는 형태로 만들지 설계 단계부터 정해야 하며, 데이터 변조 같은 보안 문제에 노출될 수 있어 무결성 검증이 중요합니다. 서비스 중인 온라인 게임, 긴급 밸런스 조정, 이벤트를 켜고 끄는 데 알맞습니다.
언리얼에서 이런 방식을 구현할 때는 몇 가지 재료를 씁니다. 데이터 테이블과 UDataAsset으로 데이터의 정의를 코드와 나눌 수 있지만, 내려받기, 컨테이너 마운트, 기존 객체 교체, 쓰는 쪽의 재조회는 프로젝트에서 구현해야 하며, FDataTableRowHandle을 쓴다고 값이 바로 바뀌지는 않습니다. 설정 파일(.ini)도 바꾼 뒤 그 설정을 다시 읽어 적용하는 경로가 있어야 하고, 모든 속성이 실행 중에 다시 적용되는 것도 아닙니다. 에셋 매니저(Asset Manager)와 FStreamableManager로 특정 에셋을 비동기로 불러오고 내리며 핫픽스 데이터를 관리할 수 있고, 게임 서버가 최신 핫픽스 데이터를 클라이언트에 보내거나 클라이언트가 시작할 때 서버에 갱신을 요청하는 로직은 직접 만들어야 합니다.
핫픽스를 안정적으로 하려면 C++에서 데이터를 불러오고 검증하는 코드를 함께 설계해야 합니다. 구체적인 구현은 파일 입출력 및 JSON 처리의 Data Asset과 Data Table 부분을 참고합니다.
CDN
패치 파일이나 전체 클라이언트 빌드를 콘텐츠 전송 네트워크(Content Delivery Network, CDN)에 올려 두면, 전 세계의 플레이어가 빠르고 안정적으로 내려받을 수 있습니다. CDN이 플레이어와 가까운 엣지 서버에 파일을 복사해 두고 응답하는 원리는 네트워크 교재의 CDN에서 다룹니다. 대역폭이 크고 지연이 짧으며 가용성이 높아 다운로드 경험이 좋아지지만, 서비스 비용이 듭니다. 규모와 관계없이 온라인 게임 대부분에 알맞습니다.
업데이트할 때 고려할 점
새 업데이트가 이전 버전의 세이브 파일이나 게임 데이터와 호환되는지 확인해야 하며, 호환되지 않는다면 데이터를 변환하거나 초기화하는 로직이 필요합니다. 클라이언트와 서버의 버전이 어긋나지 않도록 버전을 엄격하게 관리하고, 서버는 예전 버전 클라이언트의 접속을 거부하거나 업데이트를 강제할 수 있습니다.
업데이트 내용은 패치 노트로 자세히 알려 플레이어가 무엇이 바뀌었는지 알게 합니다. 배포 뒤에 심각한 문제가 생길 때를 대비해 이전 버전으로 되돌리는 롤백(rollback) 계획도 세워 둡니다. 새 기능이나 큰 변경은 모든 플레이어에게 한꺼번에 배포하기보다 일부에게 먼저 A/B 테스트를 하거나 단계적으로 배포해 위험을 줄일 수 있고, 필수 콘텐츠만 먼저 내려받고 바로 플레이하게 해 기다리는 시간을 줄이는 게임도 있습니다.
지속적인 통합 및 배포(CI/CD)와의 연계
자동화한 CI/CD 파이프라인은 핫픽스와 업데이트의 효율을 크게 높입니다. 코드가 바뀌면 자동으로 빌드하고, 자동으로 시험하고, 패치 파일을 만들어 CDN에 올리고, 런처에 업데이트를 알려 플레이어가 내려받게 하는 흐름입니다. 이렇게 자동화하면 업데이트 주기가 짧아지고, 손으로 하다 생기는 실수가 줄고, 팀의 부담도 덜어집니다.
UNREAL C++ · CH10-3
패치 파일 교체와 실행 중 상태 반영은 다릅니다
변경 대상과 현재 로드 상태에 맞춰 적용 경로를 정합니다. 긴급한 핫픽스라는 이름만으로 작은 다운로드·무재시작 반영·데이터 롤백이 보장되지는 않습니다.
| 변경 대상 | 필요한 적용 경로 | 자동으로 되지 않는 것 |
|---|---|---|
| 실행 코드 | 플랫폼에 맞는 새 바이너리 배포·재시작 정책 | Pak 마운트만으로 실행 코드나 UCLASS 구조가 교체되지 않습니다. |
| 쿠킹 콘텐츠 | 기준 릴리스·컨테이너·레지스트리·로드 정책을 함께 맞춥니다. | 마운트한 파일이 기존 UObject와 참조를 즉시 교체하지 않습니다. |
| 설정·밸런스 값 | 검증 후 다시 읽고 소비자에게 적용하는 경로 | ini·JSON·DataTable을 바꾼 것만으로 모든 시스템이 재조회하지 않습니다. |
| 저장 데이터·프로토콜 | 읽을 수 있는 버전과 접속 허용 정책을 정합니다. | 실행 파일만 이전 버전으로 돌려도 이미 변경된 영속 데이터가 되돌아가지는 않습니다. |
- 실행 코드
- 필요한 적용 경로: 플랫폼에 맞는 새 바이너리 배포·재시작 정책자동으로 되지 않는 것: Pak 마운트만으로 실행 코드나 UCLASS 구조가 교체되지 않습니다.
- 쿠킹 콘텐츠
- 필요한 적용 경로: 기준 릴리스·컨테이너·레지스트리·로드 정책을 함께 맞춥니다.자동으로 되지 않는 것: 마운트한 파일이 기존 UObject와 참조를 즉시 교체하지 않습니다.
- 설정·밸런스 값
- 필요한 적용 경로: 검증 후 다시 읽고 소비자에게 적용하는 경로자동으로 되지 않는 것: ini·JSON·DataTable을 바꾼 것만으로 모든 시스템이 재조회하지 않습니다.
- 저장 데이터·프로토콜
- 필요한 적용 경로: 읽을 수 있는 버전과 접속 허용 정책을 정합니다.자동으로 되지 않는 것: 실행 파일만 이전 버전으로 돌려도 이미 변경된 영속 데이터가 되돌아가지는 않습니다.
Pak 예시는 기준 릴리스 메타데이터와 generatepatch·basedonreleaseversion을 함께 사용합니다. IoStore에 같은 절차를 그대로 적용하지 않습니다. 새 설치·기존 설치 업데이트·중단 재시도·세이브와 서버 버전 조합을 확인합니다.
위 그림의 표는 패치 산출물이 무엇을 바꾸느냐에 따라 게임에 반영되는 경로와, 저절로 되지 않는 일을 정리합니다. 실행 코드는 플랫폼에 맞는 새 바이너리 배포와 재시작 정책이 필요하며, Pak을 마운트한다고 실행 코드나 UCLASS 구조가 바뀌지 않습니다. 쿠킹한 콘텐츠는 기준 릴리스, 컨테이너, 레지스트리, 로드 정책을 함께 맞춰야 하고, 마운트한 파일이 기존 UObject와 참조를 바로 바꾸지는 않습니다.
설정과 밸런스 값은 검증한 뒤 다시 읽어 그 값을 쓰는 쪽에 적용하는 경로가 필요하며, ini, JSON, 데이터 테이블을 바꿨다고 모든 시스템이 다시 조회하지는 않습니다. 저장 데이터와 통신 규칙은 어떤 버전을 읽을 수 있는지와 접속을 허용할지를 정해야 하고, 실행 파일만 이전 버전으로 돌린다고 이미 바뀐 영구 데이터가 되돌아가지는 않습니다. 그림 아래의 설명처럼 Pak 예시는 기준 릴리스의 메타데이터와 generatepatch, basedonreleaseversion을 함께 쓰며, IoStore에는 같은 절차를 그대로 적용하지 않습니다.
그래서 배포 전에는 새로 설치한 경우와 기존 설치를 업데이트한 경우, 내려받다 끊긴 뒤 다시 시도한 경우, 이전 세이브와 서버 버전의 조합을 모두 확인합니다. 이 글은 배포 설계와 명령의 형태를 설명할 뿐, 패치를 실제로 만들고 설치해 확인한 결과는 아닙니다.
정리
- 핫픽스는 급한 정도와 좁은 범위로 가르며, 작은 다운로드나 재시작 없는 적용이 자동으로 보장되지 않습니다. 업데이트는 콘텐츠와 기능을 넓게 바꿉니다.
- Pak 기반 패치는
-createreleaseversion으로 기준 릴리스를 보관하고,-generatepatch -basedonreleaseversion으로 만들며, 바뀐 에셋 패키지가 통째로 들어갑니다. IoStore에는 같은 절차를 그대로 쓰지 않습니다. - Pak 마운트는 파일 접근 경로를 더할 뿐, 이미 로드된 객체나 실행 코드를 바꾸지 않으므로 재조회와 적용 경로가 따로 필요합니다.
- 전체 다운로드, 증분 패치, 실행 중 핫픽스, CDN은 구현 비용과 플레이어의 편의가 서로 다르므로 게임에 맞게 고릅니다.
- 하위 호환성, 버전 관리, 패치 노트, 롤백 계획, 단계적 배포를 준비하고, 실행 파일을 되돌려도 영구 데이터는 되돌아가지 않는다는 점을 기억합니다.
- CI/CD로 빌드부터 배포까지 자동화하되, 새 설치, 기존 설치, 재시도, 세이브와 서버 버전의 조합을 확인합니다.
연습 문제
1. 마운트했으니 값도 바뀌었을까
새 조건에서 바뀐 패키지가 2MiB와 5MiB이고, 설정 값 40을 쓰는 쪽이 그 값을 자신의 필드에 복사해 두었습니다. 내려받기와 마운트가 끝났다면, 단순 합산 모델의 전송량은 얼마이며, 그 필드의 값이 새 값 45가 되었다고 말할 수 있을까요?
해설 보기
단순 합은 2+5 = 7MiB입니다. 하지만 따로 복사해 둔 필드의 40은, 검증한 새 데이터를 읽어 그 필드를 갱신하는 코드가 실행되어야 바뀝니다. 마운트에 성공했다고 45가 된다는 보장은 없습니다.
2. 설치된 버전을 모른다
프로젝트의 런처 정책이 기준 릴리스가 일치해야 패치를 적용하도록 정해 두었는데, 어떤 플레이어의 설치 버전을 알 수 없습니다. 이 패치를 그대로 적용해도 될까요? 패치를 되돌리면 모든 것이 원래대로 돌아갈까요?
해설 보기
그대로 적용하면 안 됩니다. 설치된 기준을 확인할 수 없으면 이 런처 정책의 적용 조건도 확인되지 않은 것이므로, 지원하는 기준 검사나 복구 경로를 먼저 거칩니다. 모든 플랫폼이 같은 거부 API를 제공한다고 일반화하지도 않습니다.
되돌리는 것도 나눠 생각해야 합니다. 파일과 코드를 롤백하는 것과, 패치가 적용된 동안 이미 바뀐 세이브 같은 영구 데이터를 되돌리는 것은 별개의 일입니다.
3. 핫픽스로 코드를 고칠 수 있을까
보스를 잡으면 멈추는 크래시의 원인이 C++ 코드의 널 포인터 접근으로 밝혀졌습니다. 데이터 테이블을 고치는 실행 중 핫픽스 시스템으로 이 문제를 고칠 수 있을까요?
해설 보기
고칠 수 없습니다. 실행 중 핫픽스는 설정 값이나 데이터 테이블처럼 동적으로 다시 읽는 데이터에 한정되며, 실행 코드는 바꾸지 못합니다. Pak을 마운트해도 실행 코드나 클래스 구조가 바뀌지 않습니다.
코드의 결함은 고친 새 바이너리를 만들어 플랫폼의 패치 절차로 배포하고 재시작하게 해야 합니다. 급한 문제라면 그동안 그 보스를 잠시 막는 것처럼 데이터로 우회할 수 있는 방법을 함께 검토할 수 있습니다.
다음 글 클라우드와 서버 관리에서는 전용 서버를 빌드해 클라우드에서 운영하고 관리하는 방법을 살펴봅니다.