---
title: "Addressables 의 원격 배포는 카탈로그가 이끈다"
description: "Addressables 의 원격 배포는 카탈로그를 바꿔 끼우는 일입니다. 기본 설정에서는 초기화 때 원격 카탈로그 해시를 비교해 새 카탈로그를 받고, 콘텐츠 업데이트 빌드 뒤에는 바뀐 번들만 새로 받습니다."
author: "유니얼"
pubDate: 2026-10-19
category: "game-dev/unity/runtime"
tags: ["Unity"]
canonical: https://blog.unialgames.com/posts/unity-addressables-catalog/
---
<x-repo>
<script type="application/json">
{
  "repo": "SeoBYP/AssetBundleStudy",
  "text": "AssetBundle 과 Addressables 의 문서 내용을 Windows Player 에서 직접 재서 확인한 기록입니다.",
  "chips": ["Unity 6000.6.3f1", "Addressables 4.1.1", "Windows Player"],
  "links": [
    { "text": "결과 AD7", "path": "Measure/results/AD7.txt" },
    { "text": "서버 로그", "path": "Measure/results/AD7_http.log" }
  ],
  "note": "로컬 HTTP 서버로 한 번 잰 값입니다. 그룹은 기본값(Can Change Post Release)입니다."
}
</script>
</x-repo>

라이브 게임에서 아이템 이미지 하나를 고쳤다고 앱을 다시 내고 심사를 기다릴 수는 없습니다. 번들을 서버에 올려 두고 바뀐 것만 받게 하고 싶은데, 그러려면 게임이 "무엇이 바뀌었는지" 를 알아야 합니다.

Addressables 에서 그 일을 하는 것이 **카탈로그**입니다. 주소마다 어느 번들의 어느 에셋인지와 번들의 해시가 적힌 표라서, **카탈로그를 새것으로 바꾸면 바뀐 번들만 새 이름으로 가리키게 됩니다.** 원격 배포는 결국 카탈로그를 바꿔 끼우는 일입니다.

## 원격 그룹은 카탈로그와 번들을 서버에 둔다

그룹의 빌드 경로와 로드 경로를 원격으로 바꾸고, 설정에서 원격 카탈로그를 켜면 빌드 결과가 서버에 올릴 폴더로 나옵니다. 번들 파일, 그리고 `catalog_<버전>.bin` 과 그 해시를 담은 `catalog_<버전>.hash` 입니다.

이 측정에서는 텍스처 둘(Item0, Item1)을 원격 그룹에 Pack Separately 로 넣고, 로드 경로를 로컬 HTTP 서버(`http://127.0.0.1:8765/ServerData/[BuildTarget]`)로 잡았습니다. 첫 실행에서 캐시를 비우고 두 번들을 받았습니다.

그다음 Item1 이미지만 바꾸고 **콘텐츠 업데이트 빌드**(Update a Previous Build)를 했습니다. 이 빌드는 처음 빌드 때 남긴 `addressables_content_state.bin` 과 비교해 바뀐 에셋이 든 번들만 새로 만들고, 새 카탈로그를 씁니다. Player 는 다시 빌드하지 않았습니다.

## 초기화 때 해시를 보고 새 카탈로그를 받는다

업데이트 뒤 같은 Player 를 다시 실행했을 때 서버에 들어온 요청 순서입니다.

<x-wiring>
<script type="application/json">
{
  "label": "업데이트 뒤 두 번째 실행",
  "width": 560,
  "height": 560,
  "nodes": [
    { "id": "init", "title": "InitializeAsync", "sub": "Player 시작", "x": 170, "y": 10, "w": 220, "aside": true },
    { "id": "hash", "title": "catalog_0.1.0.hash", "sub": "원격 해시를 받는다", "x": 170, "y": 115, "w": 220 },
    { "id": "bin", "title": "catalog_0.1.0.bin", "sub": "해시가 달라서 새 카탈로그를 받는다", "x": 170, "y": 225, "w": 220 },
    { "id": "size", "title": "GetDownloadSizeAsync", "sub": "Item0: 0 B, Item1: 189,734 B", "x": 170, "y": 335, "w": 220 },
    { "id": "dl", "title": "새 item1 번들", "sub": "이것 하나만 받는다", "x": 170, "y": 450, "w": 220 }
  ],
  "edges": [
    { "from": "init", "to": "hash", "label": "해시만 먼저" },
    { "from": "hash", "to": "bin", "label": "다르면 카탈로그" },
    { "from": "bin", "to": "size", "label": "받을 크기 계산" },
    { "from": "size", "to": "dl", "label": "DownloadDependenciesAsync" }
  ],
  "steps": [
    { "edge": 0, "why": "초기화하면서 원격 카탈로그의 해시 파일부터 받습니다. 몇십 바이트짜리입니다." },
    { "edge": 1, "why": "들고 있는 카탈로그와 해시가 달라서 새 카탈로그를 받습니다. 기본 설정에서는 초기화가 알아서 합니다." },
    { "edge": 2, "why": "새 카탈로그로 받을 크기를 계산합니다. Item0 은 캐시에 있는 번들 그대로라 0, Item1 은 새 번들 크기입니다." },
    { "edge": 3, "why": "받으면 서버에 들어온 번들 요청은 새 item1 번들 하나뿐입니다." }
  ],
  "note": "item0 번들은 업데이트 빌드에서 그대로 남았고, item1 만 새 해시 이름으로 추가됐습니다."
}
</script>
</x-wiring>

| | 첫 실행 (캐시 비움) | 업데이트 뒤 |
| --- | --- | --- |
| Item0 받을 크기 | 189,120 B | **0 B** |
| Item1 받을 크기 | 189,303 B | **189,734 B** |
| 받은 뒤 디스크 캐시 증가 | 378,469 B | **189,757 B** |
| 서버가 받은 번들 요청 | item0, item1 | **새 item1 하나** |

받을 크기는 카탈로그에 적힌 번들 크기에서 나오는 값이라, 실제로 그만큼만 받았는지는 따로 봤습니다. 디스크 캐시는 새 번들 크기(189,734 B)보다 23 B 많게 늘었고, 서버 로그에도 바뀐 번들 요청 하나만 있었습니다. **바뀐 번들만 받았습니다.**

그런데 업데이트 뒤 실행에서 `CheckForCatalogUpdates` 를 불렀더니 **빈 목록**이 왔습니다. 새 카탈로그는 이미 초기화 때 받았기 때문입니다. 설정의 시작 시 카탈로그 갱신 끄기(`DisableCatalogUpdateOnStart`)가 기본값인 꺼짐이면, `InitializeAsync` 가 원격 해시를 비교해 다르면 새 카탈로그를 받습니다.

```csharp
yield return Addressables.InitializeAsync();          // 여기서 새 카탈로그를 이미 받는다

var check = Addressables.CheckForCatalogUpdates(false);
yield return check;
Debug.Log(check.Result.Count);                         // 0
```

`CheckForCatalogUpdates` 와 `UpdateCatalogs` 를 직접 부르는 흐름은 이 설정을 켰을 때의 것입니다. 게임 도중 업데이트 안내를 띄우려면 시작 시 갱신을 끄고 원하는 시점에 직접 확인합니다. 그 조건은 이 측정에서 재지 않았습니다.

## 받기 전에 크기를 알 수 있다

카탈로그가 번들 크기와 해시를 알고 있으니, 받기 전에 얼마나 받을지 알 수 있습니다. 라벨로 묶어 한 번에 물으면 그 라벨이 기대는 번들 중 캐시에 없는 것의 합을 줍니다. 이 측정에서는 라벨 `AD7` 로 물었을 때 업데이트 뒤 189,734 B 가 나왔습니다.

```csharp
var size = Addressables.GetDownloadSizeAsync("AD7");
yield return size;
if (size.Result > 0)
{
    // "업데이트 189 KB 를 받습니다" 같은 안내를 띄운 뒤 받는다
    var download = Addressables.DownloadDependenciesAsync("AD7");
    yield return download;
    Addressables.Release(download);
}
Addressables.Release(size);
```

받은 번들은 디스크 캐시에 남아서, 다음 실행에서 받을 크기가 0 이 됩니다.

## 직접 확인하는 테스트 코드

로컬 HTTP 서버(`python -m http.server 8765 --bind 127.0.0.1 --directory Builds`)를 띄우고, 업데이트 전과 후에 같은 Player 를 실행합니다. 받기 전후의 디스크 캐시 크기를 같이 찍어 카탈로그 값과 따로 확인합니다.

```csharp
static long CacheBytes()
{
    string path = Caching.currentCacheForWriting.path;
    return Directory.Exists(path)
        ? Directory.GetFiles(path, "*", SearchOption.AllDirectories).Sum(f => new FileInfo(f).Length)
        : 0;
}

IEnumerator CheckUpdate()
{
    yield return Addressables.InitializeAsync();

    foreach (string key in new[] { "AD7/Item0", "AD7/Item1" })
    {
        var size = Addressables.GetDownloadSizeAsync(key);
        yield return size;
        Debug.Log($"{key}: {size.Result} B");
        Addressables.Release(size);
    }

    long before = CacheBytes();
    var download = Addressables.DownloadDependenciesAsync("AD7");
    yield return download;
    Debug.Log($"캐시 증가: {CacheBytes() - before} B");
    Addressables.Release(download);
}
```

업데이트 뒤 실행에서 Item0 이 0 B, 캐시 증가가 Item1 의 새 번들 크기와 거의 같고, 서버 로그에 그 번들 하나만 찍히면 같은 결과입니다.

## 주의할 점

### 1. 콘텐츠 상태 파일을 첫 빌드와 함께 보관한다

**안 지키면 생기는 문제.** 콘텐츠 업데이트 빌드는 출시한 빌드가 남긴 `addressables_content_state.bin` 과 비교해 바뀐 것을 고릅니다. 이 파일을 잃거나 다른 빌드의 것을 쓰면, 이미 설치된 Player 를 기준으로 무엇이 바뀌었는지 고를 수 없습니다.

**이렇게 쓴다.** 출시한 빌드마다 그 상태 파일을 버전 관리에 함께 남기고, 업데이트 빌드는 그 파일을 지정해서 돌립니다.

### 2. 시작 시 카탈로그 갱신이 켜져 있다는 것을 알고 쓴다

**안 지키면 생기는 문제.** 기본 설정에서는 초기화가 새 카탈로그를 받아 버려서, 그 뒤의 `CheckForCatalogUpdates` 는 늘 빈 목록입니다. 업데이트 안내 화면을 그 결과로 띄우면 한 번도 안 뜹니다.

**이렇게 쓴다.** 초기화 때 받아도 되면 그대로 두고, 업데이트 시점을 직접 정하려면 시작 시 갱신을 끄고 `CheckForCatalogUpdates` → `UpdateCatalogs` 를 원하는 자리에서 부릅니다.

### 3. 받기 전에 GetDownloadSizeAsync 로 크기를 확인한다

**안 지키면 생기는 문제.** 로드할 때 번들을 받으면 사용자는 언제 얼마나 받는지 모르고, 모바일 데이터로 큰 번들을 받게 됩니다.

**이렇게 쓴다.** 로딩 화면에서 라벨로 받을 크기를 묻고, 0 보다 크면 안내한 뒤 `DownloadDependenciesAsync` 로 미리 받습니다.

## 정리

Addressables 의 원격 배포는 **카탈로그를 바꿔 끼워, 바뀐 번들만 새 이름으로 가리키게 하는 일**입니다.

- 콘텐츠 업데이트 빌드는 바뀐 에셋의 번들만 새로 만들고, Player 는 새 카탈로그를 받아 그 번들만 받았습니다(189,734 B).
- 기본 설정에서는 `InitializeAsync` 가 원격 해시를 비교해 새 카탈로그를 받아서, 그 뒤 `CheckForCatalogUpdates` 는 빈 목록입니다.
- 받기 전에 `GetDownloadSizeAsync` 로 크기를 알 수 있고, 받은 번들은 디스크 캐시에 남아 다음에는 0 이 됩니다.