맥북 배터리가 미친 듯이 닳는다 — macOS File Provider와 Spotlight의 무한 루프 추적기

김도연·2026년 8월 6일

CPU 343% → 30%. 원인은 클라우드에 올라간 .pyc 15만 개였다.

어느 날부터 맥북이 이상했다. 부팅하자마자 앱이 우르르 열리고, 아무것도 안 하는데 배터리가 눈에 띄게 빨리 닳았다. "부팅할 때 프로그램이 다 켜져서 그런가?" 싶어 시작한 추적이 전혀 다른 곳으로 이어졌다.

이 글은 그 과정을 막다른 길과 오판까지 포함해 그대로 기록한 것이다. 결론만 보면 세 줄이지만, 그 세 줄에 도달하기까지 몇 번이나 틀렸다.

환경: MacBook (Apple Silicon) / macOS 26 / Nextcloud · OneDrive · iCloud Drive 동시 사용


1. 증상 — 두 가지 문제, 사실은 별개였다

  • A. 부팅하자마자 앱 10개 이상이 한꺼번에 열림
  • B. 배터리가 비정상적으로 빨리 닳음

처음엔 A 때문에 B가 생긴 줄 알았다. 결론부터 말하면 둘은 관계가 없었다. 이걸 구분하는 데 시간이 좀 걸렸다.


2. 첫 번째 오해 — "로그인 항목"이 아니었다

앱이 자동으로 열리니 당연히 로그인 항목을 의심했다.

osascript -e 'tell application "System Events" to get the name of every login item'
# → Nextcloud

등록된 앱이 하나뿐이었다. 시스템 설정을 아무리 뒤져도 나머지 앱은 없다. 다른 경로가 있다는 뜻이다.

macOS에서 앱이 자동 실행되는 경로는 세 가지로 나뉜다.

경로저장 위치성격
로그인 항목BTM (백그라운드 작업 관리자)사용자가 명시적으로 등록
LaunchAgents~/Library/LaunchAgents/백그라운드 데몬·스크립트
TAL 세션 복원~/Library/Preferences/ByHost/com.apple.loginwindow.<UUID>.plist종료 시 열려 있던 앱을 통째로 기록

범인은 세 번째였다.

plutil -p ~/Library/Preferences/ByHost/com.apple.loginwindow.*.plist | grep -A2 TALApps
"TALAppsToRelaunchAtLogin" => [
    "BundleID" => "com.apple.systempreferences"
    "BundleID" => "com.kakao.kakaotalkmac"
    "BundleID" => "ru.keepcoder.telegram"
    "BundleID" => "com.hnc.discord"
    "BundleID" => "com.google.chrome"
    ... 총 13개
]

TAL(Transparent App Lifecycle) — 시스템 종료 대화상자의 그 체크박스, "다시 로그인할 때 창 다시 열기"가 이 목록을 만든다. 무서운 건 이게 무차별이라는 점이다. 어제 잠깐 열어본 텍스트 편집기, 설정 확인하려고 연 시스템 설정까지 전부 기록된다.

이 목록은 로그인 후 앱을 복원하면 스스로 비워진다. 그래서 부팅 후에 확인하면 비어 있고, 다음 종료 때 다시 채워진다. 범인을 잡기 어려운 이유다.

조치

defaults write com.apple.loginwindow TALLogoutSavesState -bool false
defaults write com.apple.loginwindow LoginwindowLaunchesRelaunchApps -bool false

다만 이 키만으로 확실히 막힌다는 보장은 없었다. 확실한 건 종료 대화상자에서 체크박스를 한 번 해제하는 것이다(상태가 기억되므로 1회면 된다).

그리고 정말 필요한 앱만 골라 로그인 항목에 등록했다. 13개 → 3개.

주의: TAL을 끄면 Chrome 탭도 함께 날아간다. Chrome이 부팅 후 탭을 복원해주던 건 Chrome이 잘해서가 아니라 macOS가 Chrome을 통째로 되살려주고 있었기 때문이다. chrome://settings/onStartup에서 "중단한 위치에서 계속하기"를 켜서 Chrome 자체 복원으로 넘겨야 한다. 프로필별로 따로 설정해야 한다는 점도 함정.

여기까지가 문제 A. 이제 진짜 문제로 넘어간다.


3. 배터리 진단 — 하드웨어는 멀쩡했다

system_profiler SPPowerDataType | grep -A4 "Health Information"
Cycle Count: 53
Condition: Normal
Maximum Capacity: 100%

사이클 53회, 최대 용량 100%. 배터리는 새것이나 다름없었다. 그럼 소프트웨어가 CPU를 태우고 있다는 뜻이다.

ps -Aceo pid,pcpu,pmem,rss,etime,comm -r | head -10
  PID  %CPU %MEM    RSS ELAPSED COMM
 1296 111.8  0.7 187664   04:00 FileProviderExt
  695  49.8  0.6 156160   05:18 fileproviderd
  721  37.8  1.7 422368   05:18 corespotlightd
  494  23.3  0.2  39104   05:21 syspolicyd
  826   5.0  0.8 195824   05:16 mediaanalysisd

%CPU가 100을 넘는 건 멀티코어 합산 기준이다. 111.8%는 코어 하나를 다 쓰고도 남는다는 뜻.

FileProviderExt의 정체를 확인해보니:

ps -p 1296 -o args=
# /Applications/Nextcloud.app/Contents/PlugIns/FileProviderExt.appex/...

Nextcloud였다. 그리고 fileproviderd(macOS 파일 동기화 데몬)와 Spotlight 관련 프로세스들이 줄줄이 따라붙어 있었다. 합계 343%.

메모리도 확인했지만 여유 78%, 스왑 0바이트. 메모리 문제는 아니었다.


4. 첫 번째 실험 — Nextcloud를 꺼봤다

가장 단순한 검증. 껐다 켜서 CPU가 어떻게 변하는지 본다.

프로세스종료 전종료 후
FileProviderExt90.9%사라짐
corespotlightd96.7%26.8%
mds19.2%6.3%
suggestd11.2%5.3%
fileproviderd108.5%125.8%
합계343%180%

절반은 잡혔는데 fileproviderd만 오히려 올라갔다. Nextcloud 탓이 아니었다는 뜻이다.

여기서 첫 번째 교훈

처음에 나는 합계만 보고 "안 줄었네"라고 판단할 뻔했다. 실제로 뒤에서 한 번 그 실수를 한다. 프로세스별로 뜯어봐야 한다.


5. fileproviderctl dump — 26시간짜리 무한 재시도

macOS에는 File Provider 상태를 통째로 덤프하는 명령이 있다.

fileproviderctl dump > dump.txt

이게 280MB에 112만 줄이 나왔다. 이 크기 자체가 이미 단서다. 안에서 이런 걸 발견했다.

i:docID(691592) create-item: 🔶 last:'-7h10min' expired:'never' count:1
  error: NSFileProviderErrorDomain -2010
  └ BRCloudDocsErrorDomain:83 "Excluded From Sync Due To Filename"

i:docID(691567) create-item: 🔶 last:'-25h42min' expired:'never'
i:docID(691563) create-item: 🔶 last:'-25h59min' expired:'never'

BRCloudDocsErrorDomainiCloud Drive의 에러 도메인이다. Nextcloud가 아니라 iCloud였다.

expired: 'never' — 이게 핵심이다. 만료되지 않는 재시도. "파일 이름 때문에 동기화할 수 없다"는 파일을 26시간 동안 쉬지 않고 다시 올리려 하고 있었다. 성공할 리 없는데도.

조치: 데몬 재시작

killall fileproviderd   # launchd가 즉시 자동 재시작

결과는 극적이었다.

fileproviderd        183%  →  0.0%
corespotlightd      96.7%  →  0.0%
mds / suggestd / spotlightknowledged  →  전부 0.0%

343% → 0%. 해결한 줄 알았다.


6. 두 번째 교훈 — 재시작은 증상만 없앤다

Nextcloud를 다시 켜고 2분 만에:

FileProviderExt   86.6%
spotlightknowledged 38.9%
fileproviderd     35.8%

전부 돌아왔다. killall이 해결한 건 "재시도 루프에 갇힌 상태" 하나뿐이었고, 왜 그런 상태가 되는지는 그대로였다.

재부팅이나 데몬 재시작으로 고쳐지는 문제는, 대개 고쳐진 게 아니라 타이머가 리셋된 것이다.


7. 진짜 원인 — 클라우드에 개발 환경이 통째로 올라가 있었다

덤프에서 확장자별로 집계해봤다.

grep -oE '/[A-Za-z0-9_{}]+\.[a-z0-9]{1,10} cver' dump.txt \
  | grep -oE '\.[a-z0-9]{1,10} cver' | sed 's/ cver//' \
  | sort | uniq -c | sort -rn | head -12

176410 .jpg
156548 .py
152718 .pyc      ← Python 바이트코드 캐시
 80752 .h        ← C 헤더 (패키지 내부)
 56916 .parquet
 48712 .json
 46482 .hpp
 41524 .js

총 100만 개 이상. 그리고 실제 경로를 추적하니:

OneDrive/프로그램/eclipse-jee-2023-03-R-win32-x86_64/
    └ Windows용 Eclipse IDE 전체 (node_modules 다수)

OneDrive/프로그램/cli-11.0.0-pre.1/node_modules/
    └ npm CLI 소스 전체

OneDrive/Study/<강의명>/<venv>/lib/python3.11/site-packages/
    └ Python 가상환경 전체 — torch 포함

eclipse-...-win32-x86_64. 맥에서는 실행조차 안 되는 Windows 전용 빌드가 수만 개 파일로 동기화되고 있었다.

여기서 가장 중요한 통찰

폴더 용량은 93MB밖에 안 됐다.

du -sh ~/Library/CloudStorage/*
#  93M  Nextcloud-...
#  16M  OneDrive-...
# 3.5M  iCloud Drive

용량만 보면 아무 문제 없어 보인다. 왜냐하면 File Provider 방식은 파일 실체를 내려받지 않고 목록만 관리하기 때문이다.

부하의 실체는 용량이 아니라 항목 수다.
100만 개 파일의 "변경됐는지"를 계속 추적하는 것 자체가 비용이다. 파일이 0바이트여도 마찬가지다.

그리고 연쇄가 일어난다

클라우드가 파일 상태 갱신
   ↓
Spotlight가 "새 파일이네?" 하고 색인하려 함
   ↓
색인하려면 내용을 읽어야 하니 다운로드 요청
   ↓
File Provider가 서버에서 받아옴 → 파일이 새로 생김
   ↓
Spotlight가 또 색인 ... (무한 반복)

corespotlightd, mds, suggestd, spotlightknowledged함께 폭주하고 있었던 이유가 이것이다. Nextcloud 하나가 시스템 데몬 5개를 끌고 다니고 있었다.


8. 조치 두 가지

① OneDrive 선택적 동기화 해제

프로그램 폴더(Windows Eclipse + npm CLI) 동기화 해제. 서버에는 그대로 남고 이 맥에서만 안 받는다.

② Spotlight 색인에서 클라우드 폴더 제외

시스템 설정 > Spotlight > 검색 개인 정보 보호~/Library/CloudStorage 추가.

~/Library는 숨김 폴더라 파일 선택창에서 ⌘⇧G로 경로를 직접 입력해야 한다.

검증:

mdfind -onlyin ~/Library/CloudStorage "kMDItemFSName == '*'" | wc -l
# → 0    (색인에서 완전히 빠짐)

9. 세 번째 교훈 — 부하 증가가 성공의 신호일 때가 있다

조치 직후 CPU가 590%까지 치솟았다.

OneDrive                    127.7%
fileproviderd               117.6%
Finder                      103.0%
suggestd                     98.4%
spotlightknowledged.updater  62.2%

실패한 줄 알고 되돌릴 뻔했다. 하지만 이건 100만 개 항목을 인덱스에서 걷어내는 일회성 청소 비용이었다.

대규모 정리 작업은 서서히 줄지 않는다. 100% 근처를 유지하다가 어느 순간 한 번에 떨어진다. 중간에 조급해져서 개입하면 오히려 처음부터 다시 시작한다.


10. suggestd의 정체 — sample로 스택을 뜬다

청소가 끝나도 suggestd 하나만 99%를 유지했다. 나는 이걸 "오늘 문제와 무관한 별개 건"이라고 두 번이나 판단했다. 틀렸다.

이 프로세스는 로그를 전혀 남기지 않았다.

log stream --predicate 'process == "suggestd"' --style compact
# (출력 없음)

이럴 때 쓰는 게 sample이다. 프로세스가 지금 어떤 함수에서 CPU를 쓰는지 스택을 떠준다.

sample $(pgrep -x suggestd) 5 -file out.txt
HVQueues informObserversToDeleteWithRequest:
 └ HVConsumerCoordinator deleteContentWithRequest:
   └ HVSpotlightDeletionRequest              ← Spotlight 삭제 요청
     └ PPLocalNamedEntityStore deleteAllNamedEntities...
       └ PPLocalTopicStore deleteAllTopics...
         └ PPRecordStorageHelper deleteAllRecordsSourcedFromBundleId:

HVSpotlightDeletionRequest. 내가 Spotlight에서 클라우드를 제외한 바로 그 작업의 후속 처리였다.

macOS는 파일을 색인할 때 목록만 만드는 게 아니라, "개인화 프로필"(Personalization Portrait) DB에 그 파일의 주제·인물·개체를 뽑아 저장한다. Siri 제안이 똑똑해지는 원리다. 그래서 색인 100만 개가 사라지면 거기 딸린 개인화 데이터 100만 건도 SQLite에서 하나씩 지워야 한다.

메모리 32MB에 CPU만 100%였던 것도 이걸로 설명된다. 대용량 로딩이 아니라 작은 레코드를 끝없이 지우는 작업이니까.

그리고 예상대로, 40분쯤 뒤 한 번에 0%가 됐다.


11. 결과

프로세스조치 전조치 후
fileproviderd108.5%0.0%
suggestd98.8%0.0%
corespotlightd96.7%0.0%
mds / mds_stores19.2%0.0%
spotlightknowledged16.5%0.0%
FileProviderExt90.9%29.1%
합계343%~30%

FileProviderExt가 남은 29%도 의미가 있다. Nextcloud는 똑같이 켜져 있는데 3분의 1로 줄었다. 연쇄가 끊겼기 때문이다. 예전엔 파일을 건드릴 때마다 Spotlight 4개가 따라붙었지만, 이제 혼자 조용히 돈다.


12. 시행착오 기록 — 안 통한 것들

성공한 것만 적으면 다음에 또 같은 길로 들어간다.

DSDontWriteNetworkStores

.DS_Store 생성을 막는 Apple 공식 설정. 실패 항목 샘플 3건이 전부 .DS_Store여서 이게 범인인 줄 알았다.

defaults write com.apple.desktopservices DSDontWriteNetworkStores -bool true

결과: update-item 실패 1,243 → 1,247건. 변화 없음.

원인은 두 가지였다. 이 설정은 원래 SMB/AFP 네트워크 드라이브용이라 File Provider엔 통하지 않고, 무엇보다 .DS_Store는 전체 1,247건 중 41건뿐이었다. 샘플 3건을 보고 전체를 단정한 게 잘못이었다.

❌ 클라우드 폴더를 find/ls로 탐색

find ~/Library/CloudStorage/Nextcloud-* -name ".DS_Store"
# → 2분 타임아웃

ls ~/Library/CloudStorage/Nextcloud-*/어떤폴더/
# → 4초 타임아웃

File Provider 폴더는 디렉터리 목록조차 서버에 물어봐야 한다. 로컬 디스크처럼 즉시 답이 오지 않는다. 게다가 탐색 자체가 파일 다운로드를 유발해서 문제를 악화시킨다.

mdfind(Spotlight 색인 조회)는 파일에 접근하지 않아 안전하지만, 색인에서 제외한 뒤에는 당연히 0건이 나온다. 실제로 나는 이것 때문에 "Nextcloud엔 .pyc가 없다"고 잘못 판단했다가, 덤프를 보고 3만 개가 있다는 걸 뒤늦게 알았다.

❌ 합계 CPU만 보고 판단

10분 추적 로그에서 합계가 386% → 423%로 오히려 늘어난 것처럼 보였다. 그런데 프로세스별로 보니:

OneDrive File Provider   30.8% → 0.6%   ▼ 98%
corespotlightd           80.7% → 5.2%   ▼ 94%
suggestd                 98.8% → 99.3%  변화 없음  ← 이게 다 가리고 있었다

한 프로세스가 100%를 붙들고 있으면 나머지의 개선이 통째로 묻힌다.

❌ 남은 시간 추정

"5~15분이면 끝날 것"이라고 말했다가 틀렸다. 감소 추세를 가정했는데 실제로는 등락이었다. macOS는 이런 정리 작업의 진행률을 어디에도 노출하지 않는다. 근거 없는 추정은 하지 않는 게 낫다.


13. 재사용 가능한 진단 레시피

# 1. CPU 상위 프로세스 — 합계 말고 개별로 본다
ps -Aceo pid,pcpu,pmem,rss,etime,comm -r | head -15

# 2. 프로세스 정체 확인
ps -p <PID> -o args=

# 3. 배터리 하드웨어 상태 (사이클/최대용량)
system_profiler SPPowerDataType | grep -A4 "Health Information"

# 4. 잠자기 방지 요인
pmset -g assertions

# 5. 자동 실행 3종 경로
osascript -e 'tell application "System Events" to get the name of every login item'
ls ~/Library/LaunchAgents/ /Library/LaunchAgents/
plutil -p ~/Library/Preferences/ByHost/com.apple.loginwindow.*.plist

# 6. File Provider 상태 덤프 (280MB+, 2분 이상 걸림)
fileproviderctl dump > dump.txt
grep -c "🔶" dump.txt                      # 실패 항목 수
grep "expired:'never'" dump.txt | head     # 무한 재시도 탐지

# 7. 로그를 안 남기는 프로세스의 스택 추적 ★
sample <PID> 5 -file out.txt

# 8. Spotlight 색인 조회 (파일 접근 없음 — 안전)
mdfind -onlyin <path> "kMDItemFSName == '*.pyc'" | wc -l

# 9. 데몬 재시작 (launchd가 자동 복구)
killall fileproviderd
killall suggestd

특히 7번 sample은 이번에 가장 큰 도움이 됐다. 로그도 없고 이름만 봐선 뭘 하는지 알 수 없는 시스템 데몬의 정체를, 스택 한 번으로 확인할 수 있다.


14. 정리 — 배운 것

1. macOS의 앱 자동 실행은 세 갈래다. 로그인 항목만 봐서는 절대 못 찾는다. TAL 세션 복원은 무차별로 기록하고, 복원 후 스스로 목록을 비운다.

2. File Provider의 부하는 용량이 아니라 항목 수에 비례한다. 93MB 폴더가 코어 3개를 태울 수 있다. du -sh로 판단하면 안 된다.

3. 클라우드에 개발 환경을 올리지 말자. .pyc, node_modules, venv, site-packages는 전부 재생성 가능한 산출물이다. .gitignore에 넣는 것들은 클라우드 동기화에서도 빼는 게 맞다.

4. 데몬 재시작으로 고쳐진 건 대개 안 고쳐진 것이다. 원인이 남아 있으면 반드시 돌아온다.

5. 조치 직후의 부하 급증은 성공의 신호일 수 있다. 대규모 정리는 서서히가 아니라 한 번에 끝난다.

6. 합계가 아니라 개별 프로세스를 봐라. 하나가 나머지를 다 가린다.

7. 진행률이 없는 작업의 남은 시간을 추정하지 말자. 근거 없이 말하면 틀린다.


마지막으로

가장 아이러니한 건, 이 모든 게 "클라우드를 편하게 쓰려고" 한 선택의 결과였다는 점이다. 프로젝트 폴더를 통째로 동기화 폴더에 넣어두면 어디서든 이어서 작업할 수 있으니까. 그런데 그 안에 venvnode_modules가 딸려 들어가는 순간, 파일 개수가 만 단위로 뛴다.

동기화 폴더에도 .gitignore가 필요하다. 이번에 얻은 가장 실용적인 결론이다.

Nextcloud에도 여전히 개발 파일 13만 개가 남아 있지만, 이건 의도적으로 두기로 했다. 폴더 접근성을 해치면서까지 남은 29%를 줄일 이유는 없었다. 필요해지면 sync-exclude.lst에 규칙을 추가하면 된다.

]*.pyc
]__pycache__
]venv
].venv
]site-packages
]node_modules

이러면 폴더 구조는 그대로 두고 재생성 가능한 파일만 빠진다.

0개의 댓글