원격 빌드 노드를 며칠 동안 연속으로 실행하면 프로젝트 코드가 변경되지 않았는데도 Xcode 빌드가 갑자기 실패하는 경우가 있습니다. 로그에는 build.db를 열 수 없거나 디스크 이미지 형식이 잘못되었거나 데이터베이스가 손상되었다는 오류가 나타나며, 빌드를 다시 실행해도 같은 단계에서 멈춥니다. 이때 DerivedData 전체를 삭제하면 간단히 해결되는 것처럼 보이지만, 인덱스와 모듈 캐시, 재사용 가능한 빌드 결과물까지 함께 사라져 이후 빌드가 느려집니다. 또한 문제 분석에 가장 중요한 장애 당시의 상태도 잃게 됩니다.
NUMACS 클라우드 Mac에서 이러한 문제를 처리할 때는 프로젝트 진입점을 고정하고, 로그를 보존하고, 실행 중인 빌드 프로세스가 없는지 확인한 다음, 데이터베이스 무결성을 검사하고, 마지막으로 해당 프로젝트의 XCBuildData만 다시 만드는 순서가 더 안전합니다.
먼저 빌드 데이터베이스가 실제 원인인지 확인하기
Xcode 화면에 표시된 마지막 한 줄만 보고 판단해서는 안 됩니다. 먼저 동일한 디렉터리에서 동일한 scheme과 configuration으로 명령줄 빌드를 실행하고 전체 출력을 저장합니다.
set -o pipefail
mkdir -p "$HOME/build-evidence"
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Debug \
-destination 'generic/platform=iOS Simulator' \
build 2>&1 | tee "$HOME/build-evidence/xcodebuild.log"
status=${PIPESTATUS[0]}
echo "$status" > "$HOME/build-evidence/exit-code.txt"
exit "$status"
특히 build.db, database, malformed, disk image, XCBuildData를 검색합니다. 로그가 소스 코드 컴파일 오류, 의존성 해석 실패 또는 디스크 공간 부족을 명확히 가리킨다면 해당 문제부터 해결해야 합니다. 모든 빌드 실패를 데이터베이스 문제로 간주해서는 안 됩니다.
복구를 시작하기 전에 최소한 전체 로그, 종료 코드, 오류 발생 시각을 보존하십시오. 정리 작업이 끝나면 데이터베이스의 원래 상태는 일반적으로 재현할 수 없습니다.
현재 프로젝트에서 실제로 사용하는 DerivedData 찾기
하나의 클라우드 Mac에 이름이 같은 여러 프로젝트의 DerivedData가 남아 있을 수 있습니다. 디렉터리 수정 시각만 보고 추측하면 잘못된 데이터를 삭제하기 쉽습니다. xcodebuild에서 현재 빌드 설정을 가져온 뒤 BUILD_DIR을 기준으로 프로젝트의 DerivedData 루트 디렉터리를 계산해야 합니다.
BUILD_DIR=$(
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Debug \
-showBuildSettings |
awk -F ' = ' '$1 ~ /^[[:space:]]*BUILD_DIR$/ {print $2; exit}'
)
DERIVED_ROOT=$(dirname "$(dirname "$BUILD_DIR")")
XCBD="$DERIVED_ROOT/Build/Intermediates.noindex/XCBuildData"
printf 'DerivedData: %s
XCBuildData: %s
' \
"$DERIVED_ROOT" "$XCBD"
test -d "$XCBD"
파이프라인에서 -derivedDataPath를 사용한다면 사용자 디렉터리를 다시 검색하지 말고 명시된 경로를 그대로 사용해야 합니다. 그래야 대화형 세션과 무인 작업에서 스크립트가 동일한 결과를 얻을 수 있습니다.
처리 대상 파일 기록하기
정리하기 전에 데이터베이스와 관련 파일의 크기 및 수정 시각을 기록합니다.
find "$XCBD" -maxdepth 1 -type f \
-exec stat -f '%Sm %z %N' -t '%Y-%m-%dT%H:%M:%S%z' {} \; \
> "$HOME/build-evidence/xcbuilddata-files.txt"
이 목록을 통해 장애 직전에 데이터베이스가 다시 작성되었는지 확인할 수 있으며, 같은 문제가 반복될 때 동일한 경로에서 발생했는지도 비교할 수 있습니다.
프로세스 점유 여부를 확인하고 build.db를 읽기 전용으로 검사하기
실행 중인 빌드가 데이터베이스를 사용하고 있다면 바로 삭제해서는 안 됩니다. 먼저 관련 프로세스와 파일 핸들을 확인합니다.
pgrep -alf 'Xcode|xcodebuild|XCBBuildService' || true
lsof "$XCBD/build.db" || true
현재 작업에 속한 빌드가 아직 실행 중이면 파이프라인의 정상적인 취소 절차를 통해 중지하고 자식 프로세스가 종료될 때까지 기다려야 합니다. 머신 전체에서 이름이 같은 프로세스를 무차별적으로 종료하지 마십시오. 다른 작업 디렉터리에서 정상적인 작업이 계속 실행 중일 수 있습니다.
아무 프로세스도 사용하지 않는다는 사실을 확인한 뒤 SQLite 데이터베이스를 읽기 전용 모드로 검사합니다.
sqlite3 "file:$XCBD/build.db?mode=ro" \
'PRAGMA quick_check;'
결과가 ok라면 데이터베이스 구조에서 명확한 손상이 발견되지 않은 것입니다. 로그로 돌아가 권한, 디스크 공간, 프로젝트 설정을 계속 점검해야 합니다. 명령이 0이 아닌 종료 코드를 반환하거나 데이터베이스를 읽지 못하거나 무결성 오류를 출력하는 경우에만 재구축 단계로 진행합니다.
| 검사 결과 | 다음 단계 |
|---|---|
lsof에 실행 중인 빌드가 표시됨 |
작업을 정상적으로 중지한 뒤 다시 검사 |
quick_check가 ok를 반환함 |
데이터베이스를 보존하고 다른 오류 조사 |
| 점유 중인 프로세스가 없고 무결성 검사에 실패함 | 대상 XCBuildData 재구축 |
| 경로가 존재하지 않음 | workspace, scheme, DerivedData 매개변수 확인 |
XCBuildData만 최소 범위로 재구축하기
손상된 디렉터리를 즉시 영구 삭제하지 말고 먼저 증거 보존 디렉터리로 이동합니다. 복사 시간을 줄일 수 있도록 동일한 파일 시스템 안에서 이동해야 합니다.
STAMP=$(date '+%Y%m%d-%H%M%S')
QUARANTINE="$HOME/build-evidence/XCBuildData-$STAMP"
mv "$XCBD" "$QUARANTINE"
mkdir -p "$(dirname "$XCBD")"
이어서 동일한 빌드 매개변수로 빌드를 다시 실행합니다. Xcode는 새로운 XCBuildData와 build.db를 생성하며, DerivedData 안의 다른 디렉터리는 그대로 유지됩니다.
set -o pipefail
xcodebuild \
-workspace App.xcworkspace \
-scheme App \
-configuration Debug \
-destination 'generic/platform=iOS Simulator' \
build 2>&1 | tee "$HOME/build-evidence/recovery-build.log"
최소 범위 재구축 후에도 빌드가 실패하더라도 곧바로 삭제 범위를 넓히지 마십시오. 먼저 이전 로그와 새 로그를 비교하여 오류가 여전히 데이터베이스를 가리키는지 확인합니다. 오류가 의존성, 권한 또는 소스 코드 문제로 바뀌었다면 데이터베이스 문제는 해결된 것이므로 이후에는 새 오류에 맞춰 대응해야 합니다.
두 번의 빌드로 검증을 완료하고 재발 방지하기
한 번의 성공만으로 상태가 안정적이라고 판단할 수는 없습니다. 첫 번째 빌드는 새 데이터베이스를 생성하고, 두 번째 증분 빌드는 해당 데이터베이스를 다시 읽고 갱신할 수 있는지 검증합니다. 두 빌드 모두 동일한 workspace, scheme, configuration, destination, DerivedData 경로를 사용해야 하며, 각각의 로그와 종료 코드를 별도로 저장해야 합니다.
검증할 때는 다음 항목을 확인합니다.
- 새로운
build.db가 생성되었으며 읽기 전용quick_check가ok를 반환합니다. - 클린 빌드와 증분 빌드가 모두 정상적으로 완료됩니다.
- 로그에 데이터베이스 형식 또는 XCBuildData 읽기 오류가 더 이상 나타나지 않습니다.
- 빌드 결과물이 예상한 configuration과 대상 디렉터리에서 생성됩니다.
- 복구가 확인되기 전까지 장애 당시의 자료가 삭제되지 않습니다.
재발 방지의 핵심은 캐시를 정기적으로 비우는 것이 아니라 각 작업 디렉터리에 명확한 DerivedData 경로를 할당하고, 작업을 취소할 때 빌드 자식 프로세스가 종료될 때까지 기다리는 것입니다. 실행 노드에서는 사용 가능한 디스크 공간도 지속적으로 확인하여 데이터베이스 기록 중 공간 부족으로 불완전한 파일이 남지 않도록 해야 합니다.
같은 경로에서 손상이 반복된다면 매번 빌드 전에 정리 명령을 실행하도록 만들지 말고, 손상 직전의 비정상 종료, 디스크 상태, Xcode 버전을 기록해야 합니다. 캐시를 매번 초기화하면 실제 원인이 가려질 뿐 아니라 증분 빌드의 이점도 사라집니다.
최종적으로 이 절차는 증거 보존, 경로 확인, 읽기 전용 검사, 최소 범위 재구축이라는 네 단계로 정리할 수 있습니다. 이 단계로도 복구되지 않을 때만 더 넓은 범위의 DerivedData 정리를 고려해야 합니다. 이렇게 하면 장애 복구 시간을 줄이는 동시에 재발 원인을 분석하는 데 필요한 정보도 충분히 보존할 수 있습니다.
자주 묻는 질문
build.db 오류가 나면 DerivedData 전체를 바로 삭제해도 되나요?
첫 조치로는 권하지 않습니다. 관련 빌드를 멈추고 로그를 보존한 다음 해당 프로젝트의 XCBuildData만 삭제해 데이터베이스를 다시 만드는 편이 안전합니다.
데이터베이스 손상과 프로세스 점유를 어떻게 구분하나요?
lsof로 build.db를 연 프로세스가 있는지 먼저 확인합니다. 점유가 없는데 읽기 전용 SQLite quick_check가 실패하면 손상 가능성이 높습니다.
복구가 완료됐는지 어떻게 검증하나요?
동일한 workspace, scheme, configuration과 DerivedData 경로로 클린 빌드와 증분 빌드를 차례로 실행하고 종료 코드와 결과를 보관합니다.
NUMACS 클라우드 Mac
빌드 작업을 전용 물리 워크스테이션으로 옮기세요
두 가지 Apple Silicon 구성을 모두 전용 물리 머신으로 제공하며 가상 머신이 아닙니다. 일·주·월·분기 단위로 대여할 수 있고 싱가포르, 일본 도쿄, 한국 서울, 홍콩 노드를 지원합니다.