Mole 앱 제거 목록이 클릭하자마자 뜨기까지
Mole의 앱 제거 화면에서 앱을 클릭하면 오른쪽이 잠깐 비었다가 '스캔 중'이 뜨고, 0.5초에서 1초쯤 지나서야 잔여 파일이 나타났습니다. Xcode는 클릭할 때마다 3~4초, ChatGPT는 6초가 걸렸고, ChatGPT를 펼쳐서 스크롤하면 뚝뚝 끊기기도 했습니다. 이 시간이 실제로 어디에 쓰이는지 제대로 보고 싶었고, 오픈 소스 앱 제거 도구 몇 개가 같은 문제를 어떻게 다루는지도 살펴봤습니다. 변경은 10월 5일에 마무리되어 Mole 1.16.0 정식 버전에 들어갔고, 아래는 그때 측정한 내용과 제가 내린 선택입니다.
느린 건 앱 자체가 아니었습니다
처음에는 앱이 클수록 느릴 거라고 생각했는데, 측정해 보니 그렇지 않았습니다. 잔여 파일이 6개뿐인 6.6 MB짜리 작은 도구도 첫 클릭에 0.56초 정도 걸렸습니다. 시간 대부분은 클릭할 때마다 매번 하던 몇 가지 고정 작업에 들어갔습니다. 아래는 제 M5 Pro에서 Release 빌드로 세 번 측정한 중앙값입니다.
- Homebrew cask 카탈로그 읽기. 5.6 MB 파일이고 매번 200 ms쯤 걸렸는데, 앱을 Homebrew로 설치했는지 판단하는 데만 쓰였습니다.
- 앱 코드 서명 검증. 그것도 두 번 했고, 실행 파일 1 MB당 약 1 ms라서 MindNode는 클릭마다 260 ms가 들었습니다.
- 가장 느린 스캔 항목은 최근 사용한 문서 목록이었습니다. 775개 항목에 230 ms쯤 걸렸고, 거의 전부 경로를 하나씩 확인하는 데 쓰였습니다.
여기에 앱마다 사정이 더해졌습니다. Xcode의 설치 영수증은 41 MB에 경로가 12만 5천 개 적혀 있었고, 클릭할 때마다 다시 파싱했습니다. ChatGPT는 임시 폴더를 약 15,000개 남기는데, 하나하나 정말 ChatGPT 것인지 따로 확인해야 했습니다.
바꾼 부분
- cask 카탈로그는 클릭을 기다리지 않고 앱 제거 화면을 여는 순간 백그라운드에서 읽어 둡니다.
- 클릭할 때는 서명 정보만 읽고 검증은 하지 않으며, 파일 식별 정보로 그 앱을 고정합니다. 전체 서명 검증은 목록이 뜬 뒤 백그라운드에서 돌리고, 삭제 전에는 반드시 끝나야 합니다.
- 최근 사용한 문서는 먼저 이름으로 거르고, 이름이 맞는 것만 전체 소유권 검사를 거칩니다.
- 라이브러리 아래 루트들은 병렬로 나열하고, 컨테이너 식별 정보는 메타데이터 파일별로 캐시하며, 보조 프로그램 ID 접두사는 스캔 한 번에 한 번만 계산합니다.
- 설치 영수증은 영수증 파일별로 캐시해서, Xcode를 두 번째 클릭할 때는 그 41 MB를 다시 파싱하지 않습니다.
- 이름만으로 소유자가 증명되는 임시 폴더는 빠른 경로를 탑니다. 실제로 삭제할 때는 여전히 경로를 하나씩 확인합니다.
- 오래 걸리는 스캔을 먼저 시작하고, 수백 개의 작은 작업은 그 뒤에 줄을 세웁니다.
- 마우스를 올렸을 때 시작한 미리 스캔은 포인터가 벗어나면 멈추고, 앱 제거 전에 시작한 스캔이 제거 후에 오래된 결과를 다시 써 넣는 일도 없어졌습니다.
서명 검증은 그냥 빼 버리는 게 가장 쉬운 방법이었지만, 저는 지우지 않고 클릭 뒤로 옮기는 쪽을 택했습니다. 이제 백그라운드 검증이 통과하지 못해도 앱 자체는 그대로 제거되고, 서명 정보를 통해서만 찾은 종류의 잔여 파일만 남습니다. 개발자 팀 ID로 매칭한 컨테이너가 그런 예입니다. 결과에는 '앱 서명을 확인할 수 없음'으로 표시되고, 지우지 않은 항목일 뿐 실패로 치지 않습니다. 스캔 뒤에 앱이 다른 앱으로 바뀌었다면 여전히 제거를 거부합니다.
얼마나 빨라졌나
앱 제거 화면을 연 지 약 1.5초 뒤의 첫 클릭이고, 역시 Release 빌드입니다.
| 앱 | 변경 전 | 변경 후 |
|---|---|---|
| Shottr | 556 ms | 137 ms |
| MindNode | 1233 ms | 169 ms |
| Raycast | 716 ms | 171 ms |
| Parallels Desktop | 871 ms | 194 ms |
| VS Code | 584 ms | 232 ms |
| 605 ms | 238 ms | |
| Xcode | 3689 ms | 384 ms |
| ChatGPT | 6134 ms | 1530 ms |
자주 쓰는 앱 대부분이 이제 250 ms 안에 들어와서, 잔여 파일이 '스캔 중'을 거치지 않고 한 번에 나타납니다. 백그라운드 서명 검증은 목록이 뜬 뒤 12~434 ms 안에 끝납니다.
ChatGPT의 1만 5천 줄
클릭이 빨라진 뒤에도 ChatGPT를 펼쳐 스크롤하면 여전히 끊겼습니다. 세어 보니 15,175줄이었고, 그중 15,143줄이 T/.com.openai.codex.XXXXXX 같은 임시 폴더로 모두 1.0 GB였으며, 앱을 실행할 때마다 몇 개씩 더 생겼습니다. 제가 만드는 Hina도 비슷해서 344줄 중 323줄이 테스트 실행이 남긴 Hina-<Suite>-<UUID>.plist였고, 다 합쳐도 0.01 MB였습니다. 두 경우 모두 무작위 이름을 가진 한 종류가 목록을 거의 다 채우고 있었습니다.
줄 수만이 원인은 아니었습니다. 펼친 줄은 다시 그릴 때마다 모든 잔여 파일을 다시 정렬했고, 마우스를 가만히 두어도 포인터 아래로 줄이 지나가면 앱 줄 전체가 다시 그려져서 그때마다 정렬이 또 돌았습니다. 실제 ChatGPT 데이터로 측정하니 한 번 다시 그리는 시간이 309~354 ms에서 56~58 ms로, Hina는 33~36 ms에서 17~19 ms로 줄었고, ChatGPT를 펼친 높이도 3200 pt에서 1225 pt로 줄었습니다.
이제 카테고리, 폴더, 소유자, 기본 선택 상태가 같고 이름이 앱이 생성한 부분에서만 다른 줄들은 한 줄로 합칩니다. 그 부분은 무작위 접미사, UUID, 긴 16진수 문자열, 충돌 보고서의 타임스탬프입니다. 합친 줄은 카테고리 이름을 유지하고, 경로의 바뀌는 부분을 *로 바꾼 뒤 개수와 전체 크기를 붙이며, 체크박스는 하나이고 펼쳐지지 않습니다. ChatGPT는 13개 카테고리 33줄로, Hina는 24줄로 줄었습니다.
자기 이름이 있는 형제 폴더는 그대로 한 줄씩입니다. ChatGPT의 Electron 캐시 7개인 GPUCache, Code Cache, Crashpad 같은 것들입니다. 이것까지 합치면 Hina는 7줄 정도만 남아서 오히려 깨끗이 정리된 것처럼 보이지 않았고, 십여 줄이 적당한 양이었습니다. T/<id>.helper처럼 여섯 글자 단어가 들어간 이름도 한 줄로 따로 둡니다. 여섯 글자만으로는 무작위 접미사인지 구분할 수 없기 때문입니다.
예전에 시도한 두 가지 방식으로는 이번에도 돌아가지 않았습니다. 하나는 이미 펼친 앱 안에 접을 수 있는 그룹을 한 겹 더 넣는 방식으로, 9월 30일에 시도했다가 10월 2일에 뺐습니다. 접기 안에 또 접기가 있었고, 비슷한 경로가 그룹과 낱개 줄 양쪽으로 나뉘었기 때문입니다. 다른 하나는 카테고리마다 한 줄만 두는 방식으로, 9월 4일에 썼다가 일주일 뒤 되돌렸습니다. 서로 다른 위치의 항목을 한 줄 뒤에 숨겼기 때문입니다.
다른 앱 제거 도구는 어떻게 하나
Pearcleaner는 루트 약 62개를 병렬로 나열하고 이름으로 매칭하며, 소유권 증명도 캐시도 없습니다. 빠른 이유는 하는 일이 적어서입니다. Tencent Lemon은 화면에 들어가는 순간 모든 앱의 잔여 파일을 계산하고 하루에 한 번 갱신하므로 클릭은 즉시 반응합니다. 대신 열 때마다 큰 백그라운드 스캔이 돌고, 상주 부담을 늘리지 않는다는 Mole의 방향과 맞지 않습니다. MangoDisk, Uninstally, Mole CLI는 고정 루트에 제한된 병렬 처리를 씁니다.
살펴본 도구 중 Mole이 이미 쓰고 있는 getattrlistbulk와 readdir보다 빠른 저수준 방법을 가진 것은 없었습니다. Spotlight도 시험해 봤는데 한 번 조회에 0.3~0.4초가 걸렸고 잔여 파일 대부분을 놓쳐서, VS Code의 4개 중 1개만 찾았습니다. searchfs는 APFS에서도 동작하지만 한 번에 15초쯤 걸립니다. 결국 이미 있는 스캔에서 반복되는 작업을 걷어내는 것으로 정리됐습니다. 삭제하기 전에 이 잔여 파일 목록을 어떻게 읽으면 되는지는 Mac 앱 잔여 파일, 지우기 전에 목록부터 확인하세요에서 이어집니다.