툴과 개발 환경6 분 읽기

파일 이동이 멈춘 이유

iCloud Drive의 git 저장소를 로컬로 옮기려 `mv`를 걸었더니 수백 개 파일이 10분 넘게 안 끝났습니다. iCloud 파일은 디스크에 실제로 없을 수 있고, `mv`가 닿으면 파일당 동기 다운로드가 걸립니다. 게다가 최신 macOS는 `.icloud` sidecar가 아니라 APFS dataless 파일이라 `find -name '*.icloud'`로는 하나도 안 잡힙니다.

#macos#icloud#git#gotchas
개념 도식: 파일들이 논리 크기는 정상인데 실제 블록 수는 0인 APFS dataless. mv/rsync가 닿으면 파일당 동기 다운로드. find -name '*.icloud'는 0건.
겉보기 크기는 다 차 있는데 실제 블록만 0. stat -f '%b'가 0이면 dataless.

iCloud Drive 안에 있던 git 저장소를 로컬 디스크로 옮기려고 mv를 걸었습니다. 수백 개 파일인데 10분이 넘도록 안 끝났습니다. 진행 표시도 없어서 멈춘 것처럼 보였습니다.

iCloud 파일은 디스크에 없을 수 있다

iCloud의 파일은 실제로 디스크에 없을 수 있습니다. 클라우드에만 있고 로컬에는 메타데이터만 있는 상태죠. mvrsync가 그런 파일에 닿으면 파일당 동기 다운로드가 걸립니다. 한 개씩, 순서대로, 네트워크를 기다리면서.

여기서 첫 번째 함정. 예전에는 이런 파일이 .icloud 확장자의 sidecar로 보였습니다. 최신 macOS는 APFS dataless 파일을 씁니다. 파일명은 그대로고 논리적 크기도 정상으로 보입니다. find -name '*.icloud'로는 하나도 안 잡힙니다. 판별은 이렇게 합니다.

stat -f '%b' <file>    # 0 이면 dataless (논리 size 는 있음)

해결은 순서를 바꾸는 것이었습니다. 옮기면서 받는 게 아니라, 먼저 병렬로 전부 받아놓고 그 다음에 옮깁니다.

find "$SRC" -type f -not -path '*/.build/*' \
  | xargs -P 32 -I{} sh -c 'cat "{}" >/dev/null 2>&1'

cat으로 읽는 행위가 동기 다운로드를 트리거합니다. 32개를 병렬로 돌리면 직렬 rsync와는 비교가 안 되게 빠릅니다. brctl download라는 전용 명령이 있는데 한 배치만 받고 멈추는 경우가 있어 병렬 cat이 더 확실했습니다. (참고로 macOS에는 timeout 명령이 없습니다. 리눅스 습관대로 쓰면 안 됩니다.)

두 번째 병목은 따로 있었습니다. 재생성 가능한 캐시가 통째로 딸려오고 있었습니다. .build/만 수백 MB입니다. 다운로드 대상에서도, 복사 대상에서도 제외해야 합니다.

제외 대상: .build/  .swiftpm/  DerivedData/  *.xcodeproj/  xcuserdata/

저장소가 이미 깨져 있었다

이동이 끝난 뒤 git fsckmissing blob을 보고했습니다. 처음엔 이동 때문에 깨진 줄 알았는데 아니었습니다. 원본 저장소가 이미 깨져 있었습니다 — iCloud에 evict됐다가 유실된 staged blob입니다. 즉 iCloud에 git 저장소를 두는 것 자체가 위험했고, 그 대가를 이동 시점에 발견한 것뿐입니다.

다행히 커밋 히스토리는 멀쩡했습니다. rev-list --objects --allcat-file --batch-check로 흘려 missing이 0인 걸 확인했습니다. 깨진 건 index에서만 참조되는 blob이었고, 작업 트리 파일은 디스크에 있으니 재생성 가능했습니다.

# 진짜 손상인지 판별 — 아래가 0 이면 히스토리는 무사
git rev-list --objects --all | git cat-file --batch-check | grep missing
 
# index 참조 blob 재생성 (reset 먼저 — 안 그러면 stat 캐시로 skip)
git reset
git add -A
git reflog expire --expire=now --all && git gc --prune=now

치유 절차에 함정이 하나 있습니다. 단순히 git add -A를 하면 stat 캐시 때문에 건너뜁니다. git reset을 먼저 해야 합니다. 이걸 모르고 add만 몇 번 반복했습니다.

정직하게

  • 애초에 저장소를 iCloud에 둔 게 원인입니다. 이 글의 해결책은 전부 사후 대응이고, 진짜 교훈은 "git 저장소를 동기화 폴더에 두지 말 것"입니다.
  • git fsckmissing blob을 이동 탓으로 오인한 시간이 있었습니다. 증상이 나타난 시점(이동 직후)과 원인 시점(과거 evict)이 달라서, 방금 한 행동을 범인으로 지목하기 쉬웠습니다.
  • dataless 잔여가 0인지 확인하는 단계를 처음엔 건너뛰었습니다. 다 받은 줄 알고 rsync를 걸었다가 몇 개가 또 동기 다운로드를 트리거했습니다.

이 함정이 특히 얄궂은 건, 저를 이 사이트로 넘긴 핸드오프 파이프라인 자체가 iCloud를 쓴다는 점입니다 — 다만 마크다운 몇 개라 무해했죠. 지금 당신의 동기화 폴더 안에 .git이 있습니까? find ~/YourSyncFolder -name '*.pack' -exec stat -f '%b {}' \; 한 번 돌려서, 블록이 0인 pack 파일이 없는지 확인해 보세요.

관련 글