엔지니어링 실무

클라우드 Mac 빌드 I/O 변동을 Spotlight와 파일 감시에서 진단하기

클라우드 Mac 빌드 I/O 변동을 Spotlight와 파일 감시에서 진단하기

클라우드 Mac에서 동일한 커밋을 세 번 연속 빌드했는데 첫 번째는 정상이고, 두 번째는 갑자기 느려졌다가 세 번째에 다시 정상으로 돌아오는 경우가 있습니다. CPU 사용률이 계속 높았던 것도 아니고 네트워크 다운로드도 이미 끝났다면, 이런 변동을 사양 부족으로 오해하기 쉽습니다. 하지만 실제 원인은 Spotlight가 DerivedData를 스캔하거나, 편집기가 전체 작업 공간을 재귀적으로 감시하거나, 여러 CI 작업이 같은 캐시 디렉터리에 동시에 쓰고 있기 때문일 수 있습니다. 해결의 핵심은 모든 캐시를 먼저 비우는 것이 아니라 현장 상태를 보존하고 I/O를 정량화한 뒤 경합 범위를 하나씩 좁히는 것입니다.

먼저 비교 가능한 빌드 기준선 만들기

진단에 앞서 코드 커밋, Xcode 버전, 빌드 대상, 캐시 경로를 고정합니다. 최초 의존성 다운로드와 이후의 증분 빌드를 같은 데이터 묶음으로 비교해서는 안 되며, 빌드 도중 정리 스크립트를 실행해서도 안 됩니다.

set -o pipefail

WORKSPACE="$HOME/ci/work/app"
DERIVED="$HOME/ci/derived/app-main"
PACKAGES="$HOME/ci/packages/app"

mkdir -p "$DERIVED" "$PACKAGES"
cd "$WORKSPACE"

xcodebuild \
  -resolvePackageDependencies \
  -clonedSourcePackagesDirPath "$PACKAGES"

for run in 1 2 3; do
  /usr/bin/time -lp xcodebuild \
    -workspace App.xcworkspace \
    -scheme App \
    -configuration Debug \
    -destination 'generic/platform=iOS Simulator' \
    -derivedDataPath "$DERIVED" \
    -clonedSourcePackagesDirPath "$PACKAGES" \
    build 2>&1 | tee "$HOME/ci/build-$run.log"
done

wall time, 최대 상주 메모리, 로그의 의존성 해석 단계와 함께 성능 변동이 발생했을 때 다른 작업이 실행 중이었는지도 기록합니다. 세 번 모두 느리다면 대개 프로젝트나 도구 체인을 점검해야 합니다. 일부 실행에서만 이상이 나타난다면 백그라운드 I/O 경합일 가능성이 더 큽니다.

관찰 결과 우선 점검 항목
CPU 사용률은 낮지만 빌드가 멈춤 파일 시스템 대기, 디렉터리 잠금
mds, mdworker가 활발함 Spotlight 색인 범위
편집기를 종료하면 정상화됨 재귀적 파일 감시
병렬 작업에서만 발생함 공유 DerivedData 또는 패키지 캐시

시스템 도구로 I/O 현장 보존하기

먼저 색인 상태를 확인한 다음, 비정상적인 빌드가 진행되는 동안 파일 접근을 수집합니다. fs_usage에는 관리자 권한이 필요하고 출력량도 매우 많으므로, 우선 프로세스를 제한한 뒤 디렉터리 키워드로 필터링해야 합니다.

mdutil -s /

sudo fs_usage -w -f filesys mds mdworker_shared |
  grep -E 'DerivedData|SourcePackages|/ci/work/'

다른 터미널을 열어 관련 프로세스를 확인합니다.

ps -axo pid,ppid,%cpu,%mem,etime,command |
  grep -E 'mds|mdworker|xcodebuild|swift-frontend|SourceKit' |
  grep -v grep

mdworker가 새로 가져온 소스 코드를 잠깐 읽었을 뿐이라면 근본 원인이라고 단정할 수 없습니다. 더 유의미한 증거는 비정상 구간에서 대량의 중간 산출물을 계속 스캔하고, 그 스캔 경로가 빌드의 쓰기 경로와 겹치는 경우입니다.

증거를 수집하기 전에 rm -rf DerivedData를 실행하지 마십시오. 캐시를 비우면 다음 빌드가 오히려 더 느려질 수 있으며, “어떤 프로세스가 어떤 파일을 반복적으로 접근하는지” 판단할 단서도 사라집니다.

Spotlight 색인 범위 좁히기

시스템 볼륨의 색인을 통째로 끄는 방식은 권장하지 않습니다. 개발자가 시스템 검색을 계속 사용해야 할 수 있고, 전역 설정을 변경하면 실제로 문제가 있는 디렉터리 구조가 가려질 수도 있습니다. 더 안정적인 방법은 자주 생성되며 언제든 다시 만들 수 있는 캐시를 별도의 APFS 볼륨에 두고 해당 볼륨만 조정하는 것입니다.

캐시 볼륨의 마운트 지점을 확인한 뒤 다음 명령을 실행합니다.

mdutil -s /Volumes/CICache
sudo mdutil -i off /Volumes/CICache
mdutil -s /Volumes/CICache

소스 코드, 문서, 검색이 필요한 자료는 계속 색인 대상으로 유지합니다. DerivedData, 패키지 다운로드 캐시, 테스트 첨부 파일, 아카이브 임시 디렉터리는 해당 볼륨에 배치합니다. 나중에 색인을 다시 활성화하려면 다음 명령을 실행합니다.

sudo mdutil -i on /Volumes/CICache
sudo mdutil -E /Volumes/CICache

서명 자료, 장기 보관 산출물, 임시 캐시를 동일한 정리 경계 안에 섞지 마십시오. 색인 제외는 스캔 경합만 해결할 뿐, 권한 제어나 데이터 분류를 대신하지 않습니다.

재귀적 파일 감시와 공유 캐시 관리하기

편집기, 코드 생성기, 개발 서버는 저장소 루트 디렉터리를 감시하는 경우가 많습니다. 감시 규칙에 .git, DerivedData, 테스트 첨부 파일 또는 패키지 캐시가 포함되어 있으면 빌드할 때마다 의미 없는 이벤트가 수만 건씩 발생할 수 있습니다.

감시 범위 축소하기

감시 대상을 소스 코드와 설정 디렉터리로 제한하고, 다음 항목은 명시적으로 제외합니다.

의심되는 편집기나 에이전트를 먼저 종료한 다음 동일한 기준선으로 다시 실행합니다. 변동이 사라졌다면 프로세스를 하나씩 다시 실행해 보는 편이 모든 도구의 설정을 한꺼번에 바꾸는 것보다 원인 프로세스를 확인하기 쉽습니다.

병렬 작업에 독립적인 쓰기 경로 할당하기

직렬 빌드는 프로젝트 캐시를 재사용할 수 있지만, 병렬 CI에서는 여러 작업이 같은 DerivedData에 쓰게 해서는 안 됩니다. 경로에는 최소한 저장소와 작업 식별자가 포함되어야 합니다.

SAFE_REPO="${REPO_NAME//[^a-zA-Z0-9_-]/_}"
SAFE_JOB="${JOB_ID//[^a-zA-Z0-9_-]/_}"

export DERIVED_DATA="$HOME/ci/derived/$SAFE_REPO/$SAFE_JOB"
export PACKAGE_CACHE="$HOME/ci/packages/$SAFE_REPO"

mkdir -p "$DERIVED_DATA" "$PACKAGE_CACHE"

패키지 캐시는 읽기 작업에 한해 공유할 수 있지만, 의존성을 업데이트할 때는 쓰기 경합이 발생할 수 있습니다. 동시 실행이 많은 환경에서는 제어된 단계에서 의존성 해석을 먼저 완료한 다음, 빌드 작업이 안정된 해석 결과를 사용하도록 할 수 있습니다.

정리 작업과 회귀 검증 분리하기

정리 스크립트는 실행 중인 xcodebuild를 피해야 합니다. 또한 매일 최상위 디렉터리를 무조건 비우는 대신 작업별 디렉터리에서 오래된 캐시만 삭제해야 합니다. 먼저 삭제 후보만 출력합니다.

DERIVED_ROOT="$HOME/ci/derived"

if ! pgrep -x xcodebuild >/dev/null; then
  find "$DERIVED_ROOT" \
    -mindepth 2 -maxdepth 2 \
    -type d -mtime +7 -print
fi

디렉터리 계층과 보존 기간을 확인한 뒤 삭제 작업은 별도 작업으로 분리합니다. 검증할 때는 고정된 기준선으로 다시 세 번 실행하면서 fs_usage도 함께 관찰합니다. 한 번의 빌드가 유난히 빠른 것은 합격 기준이 아닙니다. 색인 프로세스가 빌드 디렉터리를 더 이상 지속적으로 스캔하지 않고, 병렬 작업이 쓰기 경로를 공유하지 않으며, 연속 빌드의 단계별 시간 분포가 일관되게 유지되어야 합니다.

마지막으로 Xcode 버전, 커밋 번호, DerivedData 경로, 패키지 캐시 경로, 활성 상태인 감시 프로세스, 색인 상태를 빌드 진단 기록에 남깁니다. 이후 같은 변동이 다시 발생하면 캐시 삭제부터 반복하며 추측하는 대신 환경 차이를 먼저 비교할 수 있습니다.

자주 묻는 질문

시스템 볼륨 전체에서 Spotlight를 꺼야 하나요?

기본 해결책으로 권장하지 않습니다. 먼저 mds 또는 mdworker가 빌드 디렉터리를 계속 읽는지 확인하고, 경합이 입증된 경우에만 별도 캐시 볼륨의 색인 설정을 조정하는 편이 안전합니다.

병렬 CI 작업이 하나의 DerivedData를 공유해도 되나요?

동일 프로젝트의 순차 빌드는 재사용할 수 있지만, 병렬 작업은 저장소나 브랜치 또는 작업 ID별로 경로를 분리해야 동시 쓰기, 잠금 경합, 예측하기 어려운 캐시 무효화를 줄일 수 있습니다.

전용 물리 Mac mini

다음 개발 파이프라인을 위한 클라우드 Mac 선택

고정 구성 3종과 아시아 노드 4곳을 비교하고, 실제 워크로드에 맞춰 대여 기간을 선택하세요. 각 대여에는 가상 머신이 아닌 전용 물리 장비 1대가 제공됩니다.

구성 선택 후 대여하기