한 대의 클라우드 Mac에서 종속성 업그레이드, 프로젝트 마이그레이션, 파괴적 스크립트를 연이어 실행할 때 가장 까다로운 문제는 빌드 자체가 아니라 깨끗한 작업 공간으로 안정적으로 되돌리는 일인 경우가 많습니다. 대규모 저장소를 다시 체크아웃하면 수많은 작은 파일을 반복해서 읽어야 하고, 기존 디렉터리를 그대로 재사용하면 추적되지 않은 파일과 생성 산출물, 잘못된 권한까지 다시 유입될 수 있습니다. 작업 디렉터리가 APFS 볼륨에 있다면 검증된 기준본을 유지한 뒤, 복제 쓰기 방식으로 작업마다 수정 가능한 복사본을 만들 수 있습니다.
APFS 복제가 해결하는 문제
APFS 복제는 처음에 두 파일이 하위 데이터 블록을 공유하도록 합니다. 어느 한쪽에 쓰기가 발생해야만 파일 시스템이 변경된 부분에 새 블록을 할당합니다. 따라서 소스 코드, 종속성, 정적 도구가 포함된 기준본을 독립된 디렉터리로 빠르게 복제할 수 있으며, 이후 변경 사항이 기준본에 다시 기록되지 않습니다.
이는 하드 링크와 다릅니다. 복제된 파일은 독립된 디렉터리 항목을 가지므로 복사본을 수정해도 원본 파일은 바뀌지 않습니다. 또한 기준본과 복사본이 여전히 같은 볼륨에 있으므로 백업도 아닙니다. 볼륨이 손상되거나 기준본을 실수로 삭제하거나 시스템 전체의 데이터가 유실되면 복제로는 원격 복구 기능을 제공할 수 없습니다.
APFS 복제는 캐시 적중을 보장하는 수단이나 스냅샷 시스템, 데이터 보호 체계가 아니라 “저렴한 비용으로 작업 복사본을 만드는” 메커니즘으로 간주해야 합니다.
du -sh는 디렉터리의 논리적 크기를 표시하므로 복제로 실제 물리 공간이 얼마나 늘어났는지 직접 알려 주지 않습니다. 용량 변화를 확인할 때는 APFS 볼륨의 남은 공간도 함께 살펴보고, 작업이 계속 데이터를 쓰는 동안 적용할 정리 임계값을 설정해야 합니다.
먼저 검증 가능한 기준본 만들기
기준본은 어떤 빌드가 끝난 뒤 우연히 남은 디렉터리여서는 안 됩니다. 고정된 커밋을 체크아웃하고 종속성을 복원한 다음, 일회성 작업에만 해당하는 상태를 모두 제거해야 합니다. 최소한 작업 트리가 깨끗한지, 하위 모듈 위치가 올바른지 확인하고 기준본의 커밋을 기록합니다.
set -euo pipefail
ROOT="$HOME/ci-workspaces"
BASE="$ROOT/baseline"
mkdir -p "$ROOT"
git -C "$BASE" status --porcelain
git -C "$BASE" submodule status --recursive
git -C "$BASE" rev-parse HEAD > "$BASE/.baseline-revision"
test -z "$(git -C "$BASE" status --porcelain)"
.baseline-revision 자체가 추적되지 않은 파일이 되므로 저장소 외부에 두거나 팀에서 승인한 무시 규칙에 미리 추가해야 합니다. 더 안전한 구성은 $ROOT/metadata에 커밋 번호를 저장하고 $BASE가 항상 깨끗한 작업 트리 검사를 통과하도록 유지하는 것입니다.
기준본에 포함하지 말아야 할 항목
DerivedData, 테스트 결과 번들, 아카이브 파일, 임시 키체인, 실행 로그, 사용 중인 소켓은 기준본에 포함하지 마십시오. 이러한 항목에는 절대 경로, 프로세스 상태 또는 작업 전용 자격 증명이 들어 있는 경우가 많습니다. 종속성 디렉터리를 기준본에 포함할지는 잠금 파일로 그 내용을 확정할 수 있는지, 설치 스크립트가 시스템 수준 경로에 데이터를 쓰는지에 따라 결정해야 합니다.
기준본은 업데이트 작업만 쓸 수 있어야 합니다. 일반 빌드 계정은 기준본을 읽을 수 있지만 그 안에서 빌드 명령을 실행해서는 안 됩니다. 그렇지 않으면 한 번의 실수로 이후의 모든 복사본이 오염될 수 있습니다.
작업별로 독립된 복사본 만들기
먼저 루트 디렉터리가 실제로 APFS에 있는지 확인한 다음 작업 번호로 고유한 디렉터리를 생성합니다. 대상 경로는 존재하지 않아야 하며, 외부 입력이 삭제 경로에 그대로 결합되지 않도록 작업 번호도 안전한 문자로 제한해야 합니다.
set -euo pipefail
ROOT="$HOME/ci-workspaces"
BASE="$ROOT/baseline"
JOB_ID="${BUILD_ID:?BUILD_ID is required}"
case "$JOB_ID" in
*[!A-Za-z0-9._-]*) exit 64 ;;
esac
test "$(stat -f %T "$ROOT")" = "apfs"
WORK="$ROOT/jobs/$JOB_ID"
DERIVED="$ROOT/derived/$JOB_ID"
test ! -e "$WORK"
mkdir -p "$ROOT/jobs" "$DERIVED"
cp -cR "$BASE" "$WORK"
xcodebuild \
-workspace "$WORK/App.xcworkspace" \
-scheme App \
-derivedDataPath "$DERIVED" \
build
cp -cR은 복제 방식의 복사를 요청하지만 실제 동작은 소스 경로, 대상 경로, 파일 시스템에 따라 달라집니다. 소스 디렉터리와 대상 디렉터리는 같은 APFS 볼륨에 있어야 합니다. 서로 다른 볼륨 간에 실행한다면 이를 복제 쓰기 절차로 간주해서는 안 됩니다. 배포 전에는 대용량 파일이 포함된 테스트 디렉터리로 복사 시간과 볼륨 용량 변화를 확인할 수 있습니다.
동시 작업과 변경 가능한 상태 격리하기
작업 공간이 독립적이라고 해서 모든 상태가 격리된 것은 아닙니다. 빌드 도구는 사용자 디렉터리에 있는 캐시, 임시 디렉터리, 설정 파일을 계속 읽고 쓸 수 있습니다. 적어도 각 작업에 독립된 DerivedData와 결과 디렉터리를 할당하고, 여러 작업이 동일한 시뮬레이터 기기 세트나 같은 출력 경로를 공유하지 않도록 해야 합니다.
동시 실행 수에 맞는 용량 예산 설정하기
복사본을 처음 만들었을 때 추가되는 사용량은 적지만, 컴파일 객체와 종속성 재작성, 아카이브 작업은 새 블록을 빠르게 생성합니다. 동시 실행 상한은 기준본의 논리적 크기가 아니라 최악의 경우에 발생하는 쓰기 증가량을 기준으로 정해야 합니다. 작업을 시작하기 전에 볼륨 공간을 확인하고 빌드가 끝난 뒤 변화를 기록할 수 있습니다.
df -h "$ROOT"
du -sh "$DERIVED" "$WORK"
작업이 많은 종속성 파일을 다시 쓴다면 복제의 공간 절감 효과는 점차 줄어듭니다. 이 경우 변경되지 않는 종속성은 기준본에 두고, 자주 바뀌는 생성 디렉터리는 작업 전용 경로로 옮겨야 합니다. 복제율을 유지하려고 쓰기 가능한 캐시를 공유해서는 안 됩니다.
안전하게 정리하고 기준본을 정기적으로 재구축하기
정리 스크립트의 최우선 목표는 “빠르게 삭제하는 것”이 아니라 작업 루트 디렉터리의 범위를 절대 벗어나지 않는 것입니다. 삭제하기 전에 경로 접두사, 디렉터리 존재 여부, 작업 번호를 모두 검증해야 하며, 비어 있을 수 있는 변수에 재귀 삭제를 직접 실행해서는 안 됩니다.
set -euo pipefail
ROOT="$HOME/ci-workspaces"
WORK="$ROOT/jobs/${BUILD_ID:?BUILD_ID is required}"
DERIVED="$ROOT/derived/${BUILD_ID:?BUILD_ID is required}"
case "$WORK" in
"$ROOT"/jobs/*) rm -rf -- "$WORK" ;;
*) exit 64 ;;
esac
case "$DERIVED" in
"$ROOT"/derived/*) rm -rf -- "$DERIVED" ;;
*) exit 64 ;;
esac
기준본 업데이트는 새 디렉터리에서 진행해야 합니다. 대상 커밋을 체크아웃하고 결정론적으로 종속성을 설치한 뒤 검증을 수행하고 현재 기준본을 교체합니다. 기존 기준본에서 직접 변경 사항을 가져온 뒤 계속 사용해서는 안 됩니다. 업데이트가 중단되면 새 상태와 이전 상태가 뒤섞여 구분할 수 없는 상태가 남기 때문입니다.
최종 점검 항목은 네 가지입니다. 기준본 커밋을 추적할 수 있고 작업 트리가 깨끗하게 유지되어야 합니다. 각 작업에는 고유한 작업 공간과 생성 디렉터리가 있어야 합니다. 볼륨의 남은 공간은 최대 동시 쓰기량을 감당할 수 있어야 합니다. 정리 로직은 통제된 경로만 허용해야 합니다. 이러한 조건을 충족해야 APFS 복제가 단순히 빨라 보이는 복사 기법을 넘어 재현 가능한 엔지니어링 절차가 됩니다.
자주 묻는 질문
APFS 복제가 빌드 캐시나 백업을 대신할 수 있나요?
아니요. 같은 APFS 볼륨에서 수정 가능한 사본을 빠르게 만드는 기능이며, 변경된 블록은 추가 공간을 사용합니다. 중요한 데이터는 별도로 백업해야 합니다.
DerivedData를 기준본에 포함하지 않는 이유는 무엇인가요?
DerivedData에는 절대 경로, 인덱스와 작업별 상태가 들어 있습니다. 작업마다 별도 디렉터리를 지정해야 재현성과 격리를 유지하기 쉽습니다.
클라우드 Mac에서 이 워크플로를 계속 검증하세요
세 가지 Apple Silicon 구성 중에서 필요한 메모리, 스토리지, 노드와 대여 기간을 선택하세요.