새 맥북을 사서 마이그레이션 어시스턴트로 기존 맥의 데이터를 그대로 옮겼습니다. 겉보기엔 모든 게 멀쩡한데, 사진 앱에서 사진 한 장을 편집하려고 하니 다운로드 진행 원이 거의 다 찬 상태로 몇 분째 멈춰 있습니다.
회전 한 번 하려고 했을 뿐인데 사진을 회전시키는 중... 진행창이 뜬 채로 끝나지 않고, 취소 버튼조차 없습니다.
디스크 용량은 800GB 넘게 남았고 인터넷도 멀쩡한데 말이죠.
이 글은 M1 맥북에어에서 M5 맥북에어로 마이그레이션한 직후 겪은 iCloud 사진 동기화 멈춤 현상을 시스템 로그로 추적해 원인을 찾고 해결한 기록입니다. 그리고 그 과정에서 제가 두 번이나 잘못된 결론을 내렸던 이야기도 함께 남깁니다. 한 번은 원인 자체를 오진했고, 다른 한 번은 그 오진 때문에 사진 편집본을 영구히 날릴 뻔했습니다. 솔직히 이쪽이 더 값진 교훈이었습니다.
1. 문제의 증상
- 상황: M1 → M5 맥북에어로 마이그레이션 어시스턴트 사용. 다음 날 사진 정리 중 발생.
- 증상 1: 사진 앱에서 편집 모드로 진입하면 원본 다운로드 진행 원이 99% 부근에서 멈춘 채 수 분간 진행되지 않음.
- 증상 2: 사진을 회전시키면
사진을 회전시키는 중...모달이 뜬 채 영원히 끝나지 않음. 취소 수단 없음. - 공통점: 두 동작 모두 원본(Original)을 필요로 하는 작업이라는 것.
참고로 이 맥은 iCloud 사진 + "Mac 저장 공간 최적화" 설정이었고, 보관함에는 사진이 2만 3천여 장 있었습니다. 이 모드에서는 원본이 iCloud에만 있고 로컬에는 축소본만 두기 때문에, 편집할 때마다 원본을 그때그때 내려받아야 합니다. 갓 마이그레이션한 맥에는 로컬 원본이 사실상 하나도 없습니다. 여기서 문제가 터졌습니다.
2. 원인이 아니었던 것들 (먼저 배제하기)
"느리다"는 증상은 원인 후보가 너무 많습니다. 흔히 의심하는 것부터 하나씩 지워 나갔습니다.
# 디스크 여유 공간
df -h / /System/Volumes/Data
# iCloud 연결 상태 및 응답 속도
curl -s -o /dev/null -w "%{http_code} time=%{time_total}s\n" --max-time 10 https://www.icloud.com
# 저전력 모드 / 발열 스로틀
pmset -g | grep -i lowpower
pmset -g therm
| 확인 항목 | 결과 | 판정 |
|---|---|---|
| 디스크 여유 공간 | 824GB 여유 | 원인 아님 |
| iCloud 연결 | 응답 50ms, 정상 | 원인 아님 |
| 저용량 데이터 모드 | 꺼짐 | 원인 아님 |
| 저전력 모드 | 꺼짐 | 원인 아님 |
| 발열/전력 스로틀 | 기록 없음 | 원인 아님 |
전부 정상이었습니다. 겉으로 보이는 지표로는 아무 문제가 없으니, 이제 시스템 로그를 직접 볼 차례입니다.
3. 시스템 로그로 진짜 원인 찾기
log라는 내장 명령이 이미 존재합니다. 그냥 log show ...를 입력하면
zsh: too many arguments 에러가 납니다. 반드시 절대 경로 /usr/bin/log로 호출해야 합니다.
이것 때문에 처음에 로그가 안 나와서 한참 헤맸습니다.
3-1. 사진 관련 데몬 확인
macOS 사진 앱은 혼자 동작하지 않습니다. 세 개의 데몬이 역할을 나눠 맡습니다.
cloudphotod— iCloud 사진 동기화 엔진(CPL). 업로드/다운로드를 담당합니다. 이번 사건의 범인입니다.photolibraryd— 보관함 데이터베이스 관리. 에셋 메타데이터와 리소스 요청을 처리합니다.photoanalysisd— 사진 분석. 인물 인식, 장면 분류 등 백그라운드 분석을 수행합니다.
# 사진 관련 프로세스 상태 확인
ps axo pid,%cpu,%mem,etime,comm | grep -Ei "cloudphotod|photolibraryd|photoanalysisd|Photos"
# cloudphotod 로그 확인 (반드시 /usr/bin/log)
/usr/bin/log show --last 15m --predicate 'process == "cloudphotod"' --style compact
3-2. 결정적 단서 1 — 다운로드 캐시 불일치
(libcopyfile.dylib) open on ~/Pictures/사진 보관함.photoslibrary/resources/cpl/
cloudsync.noindex/storage/filecache/ASY/cplASYx6wh4RwlgxxUbkaGjn7qILOPl.jpeg:
No such file or directory
filecache는 iCloud에서 받아온 파일을 보관함으로 옮기기 전에 잠시 두는 임시 캐시입니다. 그런데 데몬이 찾는 파일이 없다고 나옵니다. 실제로 그 디렉터리를 열어보니 파일이 0개였습니다.
즉 다운로드는 됐는데 보관함으로 옮기는 마지막 단계에서 실패하고 있었던 겁니다. 진행 원이 99%에서 멈추던 지점이 정확히 여기였습니다.
3-3. 결정적 단서 2 — 다운로드 작업이 계속 취소됨
Finished operation ... shortened-name=Resources Download, qos=UserInitiated,
stateFlags=executing|cancelled
with error: CKError "Operation Cancelled" (20)
편집 화면이 요청한 원본 다운로드(qos=UserInitiated)가 완료되지 못하고 계속 취소되고 있었습니다. 그리고 결정적으로, 어느 순간부터는 신규 다운로드 요청 자체가 0건이 되었습니다. UI의 진행 원만 돌고 있을 뿐 실제로는 아무 일도 일어나지 않는 상태였던 거죠.
3-4. 근본 원인 — 마이그레이션으로 인한 보관함 재구축
defaults read com.apple.cloudphotod
lastOSBuildUpgradeDate = 2026-08-16 07:16:45
previousOSBuildVersion = 25F84 → lastSeenOSBuildVersion = 25G76
previousCPLVersion = CloudPhotoLibrary-851.0.103
lastSeenCPLVersion = CloudPhotoLibrary-860.0.170
previousDBVersion = 93 → lastSeenDBVersion = 94
lastOSBuildUpgradeDate라는 이름 때문에
저는 처음에 "이 맥이 오늘 OS 업그레이드됐구나"라고 읽었습니다. 틀렸습니다.
실제 의미는 "이 보관함이 마지막으로 열린 곳은 빌드 25F84였고, 지금은 25G76에서 열리고 있다"입니다.
즉 보관함이 더 최신 빌드의 맥으로 옮겨졌다는 뜻이지, 이 맥이 업그레이드됐다는 뜻이 아닙니다.
실제로 하드웨어와 마이그레이션 이력을 확인해 보면 명확합니다.
# 하드웨어 확인
system_profiler SPHardwareDataType | grep -iE "Model Identifier|Chip"
# 마이그레이션 어시스턴트 실행 이력
/usr/bin/log show --last 24h \
--predicate 'process CONTAINS "Migration"' --style compact | head
Model Identifier: Mac17,3 Chip: Apple M5
Setup Assistant [SystemMigration] Will try to obtain Source system
systemmigrationd: Starting
이 관점으로 보면 앞서 이상하게 여겼던 것들이 전부 자연스럽게 설명됩니다.
| 관찰된 현상 | 마이그레이션 관점의 설명 |
|---|---|
| 로컬 원본 파일이 사실상 0개 | 새 맥이므로 아직 아무것도 안 받은 상태 |
filecache가 완전히 비어 있음 | 새 맥이므로 당연 |
| 라이브러리 전체 재동기화 진행 | iCloud에서 처음부터 받는 중 |
photoanalysisd가 전체 재분석 | 새 맥에서 인물·장면 인식을 처음부터 수행 |
| 사진 한 장 다운로드에 100~200초 | 초기 동기화가 네트워크 대역을 대부분 사용 |
4. 1차 조치 — 동기화 데몬 재시작
가장 먼저 할 일은 멎어버린 cloudphotod를 되살리는 것입니다. launchd가 자동으로 다시 띄워주므로 그냥 종료시키면 됩니다.
osascript -e 'quit app "Photos"'
sleep 3
killall cloudphotod
filecache ... No such file or directory오류가 0건으로 사라짐- 멎어 있던 큐가 재가동 —
Will download 35 tasks등 정상 처리 재개
photolibraryd는 SIGTERM을 무시합니다. 하지만 굳이 강제 종료(SIGKILL)할 필요 없습니다.
보관함 DB를 다루는 데몬이라 위험 부담만 있고, 취소 루프의 원인은 cloudphotod 쪽이었습니다.
5. 2차 조치 — 사진 보관함 복구
데몬 재시작만으로 부족하다면 보관함 복구(Repair Library)가 정공법입니다. 마이그레이션으로 어긋난 데이터베이스를 재구축합니다.
- 사진 앱을 완전히 종료합니다.
Option(⌥)+Command(⌘)를 누른 상태로 사진 앱을 실행합니다.- "보관함 복구" 창이 뜨면 복구를 클릭합니다.
⌥⌘와 함께 클릭하면 "다른 앱 모두 가리기" 동작으로 먹혀서 복구 창이 뜨지 않습니다.
키를 먼저 누른 채로 앱이 실행되기 시작해야 사진 앱이 그 키 상태를 읽습니다.
Finder의 응용 프로그램 폴더에서 키를 누른 채 더블클릭하고, 복구 창이 뜰 때까지 계속 누르고 계세요.
# 복구 진행 상황은 로그로 확인할 수 있습니다
/usr/bin/log show --last 5m --predicate 'process == "photolibraryd"' --style compact \
| grep -iE "rebuild|Migration"
Asset journal rebuild started
JournalManager[Rebuild]: notified rebuild starting
Asset journal rebuild finished, duration: 69.5s
CSU EntityStore 로드 실패 오류도 함께 사라졌습니다.
6. 여기서 제가 두 번째로 크게 틀렸습니다
복구 직후 다시 회전을 시도했더니 또 멈췄습니다. 로그를 보니 새로운 오류가 보였습니다.
resource availability request replying to client with success: N,
duration: 0.002s,
error: PHPhotosErrorDomain Code=3164
resource: type: img-prim, version: adj
version: adj는 편집이 적용된 렌더링본입니다. 그리고 0.002초 만에 즉시 실패합니다. 네트워크를 타보지도 않고 "그런 리소스 없음"을 즉답하는 것이죠. 원본(version: org)은 한 건도 실패하지 않았습니다.
저는 여기서 이렇게 결론 내렸습니다. "이 사진들의 편집 결과물이 iCloud에서 영구 유실됐다. 복구로도 못 고친다. 원본으로 되돌리기(Revert to Original)밖에 답이 없다."
검증: 정말 유실됐는지 실제로 확인해 보기
다행히 되돌리기를 바로 실행하지 않고, PhotoKit으로 직접 가져와 보는 검증 도구를 먼저 만들었습니다. 핵심은 이 부분입니다.
let opts = PHImageRequestOptions()
opts.version = .current // 편집이 적용된 렌더링본
opts.isNetworkAccessAllowed = true
opts.deliveryMode = .highQualityFormat
mgr.requestImageDataAndOrientation(for: asset, options: opts) { data, _, _, info in
// 실제로 데이터가 오는지, 오류가 나는지 확인
}
PHImageManager는 결과 핸들러를 메인 큐로 보냅니다.
커맨드라인 도구에서 메인 스레드를 세마포어로 막아두면 런루프가 돌지 않아 콜백이 영원히 오지 않습니다.
작업은 백그라운드 큐에서 하고, 메인 스레드는 RunLoop.main.run()으로 돌려야 합니다.
이걸 몰라서 "전부 시간초과"라는 엉터리 결과를 한 번 받았습니다.
제대로 고쳐서 다시 돌린 결과입니다.
BEBDAE1D-… 정상 0.015초 1,703,918 바이트 ← 회전이 멈췄던 바로 그 사진
7B36D4A5-… 정상 0.003초 1,293,741 바이트
20FA7435-… 정상 0.003초 1,324,283 바이트
8C480550-… 정상 0.003초 1,915,673 바이트
E7A0E85C-… 정상 0.002초 1,582,528 바이트
cloudphotod가 백그라운드로 계속 받아 채우면서 저절로 해소되었습니다.
로그도 이를 뒷받침했습니다. Code=3164 오류는 특정 시각에 몰려서 발생한 뒤 그 이후로는 단 한 건도 나오지 않았습니다.
- 필드 이름을 곧이곧대로 믿지 말 것.
lastOSBuildUpgradeDate를 "OS 업그레이드"로 읽어서 원인을 통째로 오진했습니다. 하드웨어 모델과 마이그레이션 로그를 함께 봤다면 처음부터 알 수 있었습니다. - 되돌릴 수 없는 조치 전에는 전제를 검증할 것. 로그의 오류 메시지는 "지금 이 순간의 상태"일 뿐, 그것이 영구적이라는 보장은 없습니다. 특히 마이그레이션 직후의 과도기에는 일시적인 실패가 정상적으로 발생합니다.
7. "멈춘 것"과 "그냥 느린 것" 구분하기
모든 문제가 해결된 뒤에도 회전에는 여전히 시간이 걸렸습니다. 어떤 사진은 2분 11초가 걸렸죠. 하지만 이건 고장이 아니라 정상 동작입니다. 겉보기 증상은 똑같기 때문에 구분법이 필요합니다.
| 구분 | 진짜 멈춤 (고장) | 그냥 느림 (정상) |
|---|---|---|
cloudphotod CPU | 0%로 고정 | 수십 % 로 변동 |
| 다운로드 요청 로그 | 0건 | Will download 계속 발생 |
| 액션 완료 신호 | completed:0 영구 | completed:1 (수십~수백 초) |
| 로그 오류 | filecache 누락, Code=3164 | 없음 |
# 가장 간단한 판별법: cloudphotod의 CPU를 보라
ps axo pid,%cpu,comm | grep cloudphotod | grep -v grep
cloudphotod의 CPU가
몇 %라도 움직이면 받는 중이니 기다리면 됩니다.
0%로 굳어 있으면 멈춘 것이니 데몬을 재시작하세요.
8. 근본 대책 — 애초에 기다리지 않기
편집할 때마다 몇 분씩 기다리는 근본 원인은 "Mac 저장 공간 최적화" 설정입니다. 원본이 iCloud에만 있으니 편집 시점에 매번 받아와야 하는데, 새로 마이그레이션한 맥은 로컬에 받아둔 원본이 거의 없어 이 대기가 특히 길어집니다.
- 전환 직후 전체 원본을 내려받으므로 수 시간에서 며칠이 걸리고 디스크를 많이 씁니다.
- 대신 그 이후로는 편집·회전 시 대기가 완전히 사라집니다.
- 보관함 크기만큼 디스크 여유가 필요합니다. 사진 2만 3천여 장 기준 수십 GB 이상을 감안하세요.
9. 결론 및 요약 체크리스트
- 먼저 배제하라: 디스크, 네트워크, 저전력 모드, 발열 스로틀. 대부분 여기서 걸리지 않습니다.
- 로그를 보라:
/usr/bin/log show --predicate 'process == "cloudphotod"'(zsh에서는 반드시 절대 경로로 호출) - 1차 조치: 사진 앱 종료 후
killall cloudphotod. 대부분 이걸로 풀립니다. - 2차 조치:
⌥⌘를 누른 채로 사진 앱 실행 → 보관함 복구. - 절대 서두르지 마라: "원본으로 되돌리기"는 되돌릴 수 없습니다. 마이그레이션 직후의 일시적 오류를 영구 유실로 오해하기 쉽습니다.
- 가장 확실한 방법: 새 맥이라면 초기 동기화가 끝날 때까지 기다리세요. 사진 정리를 자주 한다면 "원본을 이 Mac에 다운로드"로 전환하는 것이 근본 대책입니다.
tmutil latestbackup 명령으로 최근 백업이 있는지 볼 수 있습니다.
이번 작업 중에 백업 대상이 마운트되지 않는 상태였다는 걸 뒤늦게 발견했는데,
만약 잘못된 판단으로 되돌리기를 실행했다면 복구할 방법이 전혀 없었을 겁니다.
새 맥으로 옮긴 직후는 백업 설정이 아직 안 잡혀 있을 가능성이 높으니 특히 주의하세요.
사진 앱이 멈춘 것처럼 보인다고 무작정 기다리거나, 반대로 성급하게 파괴적인 조치를 취하기 전에 로그로 지금 무슨 일이 벌어지고 있는지 먼저 확인해 보세요. 대부분의 경우 답은 이미 로그에 적혀 있습니다. 다만 그 로그를 제대로 읽는 것은 또 다른 문제라는 걸, 저는 이번에 두 번의 오진으로 배웠습니다.