Vrew %TEMP% 18GB 자동 정리 — 5-layer 가드와 OS file lock 의 한계
1편에서 Vrew 의 autosave_backup 폴더를 PowerShell + Task Scheduler 로 30 분마다 자동 정리한 이야기를 정리했었습니다. 그 글을 마친 뒤로 같은 PC (winbox 라고 부르고 있습니다) 에서 VrewAutosaveBackupCleanup task 가 안정적으로 돌고 있었습니다. autosave_backup 폴더는 35 MB 근방에서 더 늘지 않았고, 9.5 시간 가동 중이던 Vrew 도 영향을 받지 않았습니다. 한동안은 이 문제가 끝났다고 생각했습니다.
며칠 뒤 지인이 보내준 %TEMP% 폴더 스샷에 18.3 GB 라는 숫자가 찍혀 있는 것을 봤을 때, autosave 는 정리했는데 다른 영역에서 또 누적되고 있다는 사실이 분명해졌습니다. 같은 PC, 같은 앱이었습니다. 한 번 닫았다고 생각한 문이 옆에 또 하나 열려 있었습니다.
%TEMP% 진단 — 99.7% 가 Vrew 출처
1편에서 했던 진단 패턴을 거의 그대로 가져왔습니다. %TEMP% 의 하위 항목을 종류별로 묶고 용량을 합산했습니다.
$temp = $env:TEMP
$folders = Get-ChildItem -LiteralPath $temp -Directory -Filter 'vrew-asset_*' -Force
$folderSize = ($folders | ForEach-Object {
(Get-ChildItem $_.FullName -Recurse -File -Force -ErrorAction SilentlyContinue |
Measure-Object Length -Sum).Sum
} | Measure-Object -Sum).Sum
$uuidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\.mp4$'
$looseMp4 = Get-ChildItem -LiteralPath $temp -File -Filter '*.mp4' -Force |
Where-Object { $_.Name -match $uuidPattern }
$looseSize = ($looseMp4 | Measure-Object Length -Sum).Sum
결과를 표로 정리했더니 의외로 깔끔하게 나뉘었습니다.
| 범주 | 항목 수 | 크기 | 비중 |
|---|---|---|---|
| vrew-asset_* 폴더 | 758 개 | 12.7 GB | 69.4% |
| loose UUID mp4 | 732 개 | 5.5 GB | 30.3% |
| Vrew .log | 18 개 | 7.8 MB | 0.04% |
| Windows .tmp 등 | ~458 개 | ~45 MB | 0.24% |
| Vrew 합계 | 1,505 개 | 18.2 GB | 99.7% |
%TEMP% 18.3 GB 중 사실상 전부가 Vrew 였습니다. loose UUID mp4 (8-4-4-4-12 hex 패턴) 의 mtime 을 보니 11:36 ~ 11:56 의 20 분 안에 집중돼 있었습니다. clip 분할이나 자막 작업 흔적으로 추정되는 패턴이었습니다. 반면 vrew-asset_* 폴더 안 mp4 의 mtime 은 10:31 ~ 13:10 사이로 길게 흩어져 있었는데, 그 사이가 그 날 작업 시간대였습니다.
첫 시도 — 1편 패턴을 그대로 답습
1편의 4 겹 가드 패턴이 잘 통했으니, 이번에도 똑같이 해도 될 것 같았습니다. process guard (Vrew 가 실행 중이면 무조건 SKIP) + mtime + size + try/catch 의 4-layer 가드를 짜고 winbox 에서 dry-run 을 돌렸습니다.
dry-run 결과 자체는 정확했습니다. 758 폴더 / 12.7 GB 가 후보로 잡혔고, Phase A (Vrew 가동 중) 에서는 process guard 가 1 순위로 SKIP, -IgnoreProcessGuard 를 붙인 Phase B 에서는 후보 그대로 나왔습니다. 1편과 정확히 같은 그림이었습니다.
문제는 dry-run 이 끝난 뒤에 보였습니다. winbox 의 지인은 영상 편집 특성상 Vrew 를 거의 닫지 않습니다. 1편의 autosave_backup 폴더는 Vrew 가 켜져 있어도 백업 파일을 잠그지 않았기 때문에 회수가 가능했지만, %TEMP% 의 자산은 Vrew 가 계속 들고 있을 가능성이 있었습니다. process guard 가 1 순위로 SKIP 을 깔아 두면, 자동 회수가 영원히 0 GB 라는 의미였습니다. 1편의 성공이 2편에서 그대로 통하지 않았습니다.
두 번째 시도 — process guard 폐기, 5-layer 가드
그래서 process guard 의 1 순위 SKIP 을 폐기했습니다. 대신 다섯 겹의 가드로 안전망을 다시 짰습니다.
| # | 가드 | default | 역할 |
|---|---|---|---|
| 1 | Vrew session 마커 | – | Vrew StartTime 캡처 (이전 세션 잔여물 식별) |
| 2 | Age guard | -MinAgeMinutes 120 |
mtime ≥ 120 분 후보만 통과 |
| 3 | Vrew session 가드 | – | mtime < StartTime (이전 세션) 추가 후보 |
| 4 | Total size guard | -MinTotalMB 1024 |
합계 1 GB 미만이면 SKIP |
| 5 | Per-folder try/catch | – | OS lock 발생 시 자동 skip |
가설은 이랬습니다. "mtime 이 오래된 폴더 = active 가능성 낮음 = lock 풀려 있을 가능성 높음." mtime 이 두 시간 이상 지난 자산이라면 Vrew 가 그 사이에 한 번쯤 file handle 을 놓을 거라고 생각했습니다.
Vrew session 마커는 다음과 같이 캡처했습니다. 여러 프로세스 (Electron 메인 + helper 들) 중 가장 먼저 시작된 시각을 잡으면 "이번 세션이 언제 시작됐는지" 를 알 수 있습니다.
$vrew = @(Get-Process -Name 'vrew' -ErrorAction SilentlyContinue)
$vrewStart = $null
if ($vrew.Count -gt 0) {
try {
$vrewStart = ($vrew |
Where-Object { $_.StartTime } |
Sort-Object StartTime |
Select-Object -First 1).StartTime
} catch {
$vrewStart = $null
}
}
그리고 age 가드와 session 가드를 OR 로 묶었습니다. 둘 중 하나라도 통과하면 후보입니다.
$cutoff = (Get-Date).AddMinutes(-$MinAgeMinutes)
$stale = @($candidates | Where-Object {
$byAge = $_.LastWriteTime -lt $cutoff
$bySession = ($null -ne $vrewStart) -and ($_.LastWriteTime -lt $vrewStart)
$byAge -or $bySession
})
구조적으로는 깔끔하다고 생각했습니다. PR 도 통과시키고 winbox 에 배포한 뒤, 직접 invoke 로 실측을 돌려봤습니다.
실측 충격 — 7 시간 전 폴더까지 전부 lock
Task 우회로 cleanup 함수를 직접 호출했습니다. dry-run 이 아닌 실 invoke 였습니다. 결과는 다음과 같았습니다.
candidates: 755 folders / 12.69 GB
reclaimed : 0 folders / 0 GB
locked : 755 folders
duration : 2.3 sec
exit : 0
755 폴더가 전부 IOException 으로 lock skip 됐습니다. 회수는 0 GB. 2.3 초만에 끝났는데, lock check 만 빠르게 돌고 모든 후보가 skip 됐기 때문이었습니다. 가설이 깨졌습니다. mtime 이 7 시간 전인 폴더까지 Vrew 가 전부 쥐고 있었습니다.
정확히 어떤 파일이 잠겨 있는지 확인하기 위해 FileShare.None 으로 직접 열어보는 진단 코드를 짰습니다.
foreach ($f in $files) {
try {
$s = [System.IO.File]::Open($f.FullName, 'Open', 'ReadWrite', 'None')
Write-Output "FREE $($f.Name)"
$s.Close(); $s.Dispose()
} catch {
Write-Output "LOCK $($f.Name)"
}
}
각 vrew-asset_* 폴더 안에는 단일 <uuid>.mp4 가 한 개씩 들어 있었는데, 큰 폴더 (2.8 GB) 든 작은 폴더 (0 MB, 빈 파일) 든 동일하게 LOCK 으로 떨어졌습니다. mtime 이나 파일 크기와 무관한 결과였습니다.
본질은 그제서야 분명해졌습니다. Vrew 는 timeline 에 import 된 mp4 를 decode 캐시 형태로 폴더 안에 풀어두고, 그 timeline 이 살아있는 한 모든 fd 를 영구적으로 들고 있습니다. mtime 이 7 시간 전이든 1 분 전이든, 그 자산이 어딘가의 timeline 에 한 번이라도 올라간 적이 있으면 풀리지 않는 구조였습니다.
안전성 vs 운영 효과의 패러독스
여기서 묘한 결과가 나왔습니다. 5-layer 가드는 약속한 일을 정확히 해냈습니다. 데이터 손실이 0 이었고, Vrew 의 5 개 프로세스 어느 쪽도 freeze 되지 않았습니다. 다만 회수도 0 GB 였습니다.
| 항목 | 결과 |
|---|---|
| 5-layer 가드 안전성 | 데이터 손실 0, Vrew freeze 없음 |
| 5-layer 가드 운영 효과 | 회수 0 GB (PR #1 의 process guard 1 순위 SKIP 과 동일) |
| 추가 비용 | 매 30 분 발화마다 LOCK 로그 755 줄 노이즈 |
돌이켜 생각해보면, 5-layer 가드를 어렵게 짰는데 결과적으로는 PR #1 의 "Vrew 가 켜져 있으면 SKIP" 한 줄과 운영적으로 동일한 그림이었습니다. process guard 가 application-level 에서 했던 역할을 OS file lock 이 OS-level 에서 동일하게 해주고 있었던 셈입니다. 1편에서 "마지막 안전망" 이라고 적었던 OS file lock 이 이번에는 사실상 유일한 안전망이 됐습니다.
세 번째 발견 — loose UUID mp4 통합
그래도 한 가지 짚이는 게 남아 있었습니다. 진단 표의 두 번째 줄, loose UUID mp4 5.5 GB 가 cleanup 의 scope 밖이었습니다. 폴더만 보고 있었기 때문이었습니다. UUID 정규식 가드를 한 줄 추가해서 통합했습니다.
$folderCandidates = @(Get-ChildItem -LiteralPath $TempRoot -Directory `
-Filter 'vrew-asset_*' -Force)
$uuidMp4Pattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\.mp4$'
$fileCandidates = @(Get-ChildItem -LiteralPath $TempRoot -File -Filter '*.mp4' -Force |
Where-Object { $_.Name -match $uuidMp4Pattern })
$candidates = @() + $folderCandidates + $fileCandidates
UUID 패턴 + %TEMP% 위치 한정의 이중 조건으로 좁혔습니다. 사용자의 다른 파일이 우연히 같은 이름 규칙을 가질 가능성을 차단하기 위해서였습니다. Remove-Item -Recurse -Force 는 폴더든 단일 파일이든 모두 동작하기 때문에 처리 경로는 한 줄로 통합할 수 있었습니다.
그런데 통합을 끝내고 다시 dry-run 을 돌려본 사이, 11 분 전에는 732 개였던 loose mp4 가 0 개로 줄어 있었습니다. Vrew 가 자체적으로 정리한 것이었습니다. 이 두 자산의 lifecycle 이 사실 달랐다는 게 그제서야 보였습니다.
| 자산 | Vrew 의 정리 동작 |
|---|---|
| vrew-asset_* 폴더 안 mp4 (timeline 원본 decode 캐시) | timeline 살아있는 한 영구 fd, 정리 안 함 |
| loose UUID mp4 (clip 분할 / 자막 임시) | 작업 단계 끝나면 Vrew 가 자체 해제하고 삭제 |
그렇다면 PR #4 는 무의미한가 싶었는데, 그렇지는 않았습니다. Vrew 가 비정상 종료되거나 작업이 중간에 끊기면 loose mp4 가 정리되지 못한 채 남습니다. PR #4 는 그 잔존 시나리오를 덮어주는 안전망 역할을 합니다.
의외의 발견 — "닫았다고 생각" 했는데 백그라운드 잔존
지인이 어느 시점에 "Vrew 를 끄고 작업관리자에서 봤는데 아직 cleanup 회수가 안 됐다" 고 알려왔습니다. 화면에 Vrew 창이 보이지 않는데 task 가 여전히 LOCK 만 쌓고 있는 상황이었습니다. 진단을 한 단계 더 들어가봤습니다.
Get-Process -Name 'vrew' |
Select-Object Id, ProcessName, `
@{n='MainWnd';e={if ($_.MainWindowHandle -ne 0) {'YES'} else {'NO'}}}, `
@{n='CPU(s)';e={[math]::Round($_.CPU,1)}}, `
@{n='MemMB';e={[math]::Round($_.WorkingSet64/1MB,0)}}
출력은 5 개의 vrew.exe 프로세스가 전부 MainWindowHandle = 0 으로 잡혀 있었습니다. 화면에 띄워진 창은 없지만 프로세스는 살아있는 상태였습니다. 부모-자식 관계를 따라가보니 Electron 의 전형적인 구조였습니다.
Vrew.exe (main process)
├─ Vrew.exe --type=gpu-process
├─ Vrew.exe --type=utility (Network Service)
├─ Vrew.exe --type=utility (Audio Service)
└─ Vrew.exe --type=renderer ← 23% CPU 사용 중
그 중 renderer 한 개 (PID 22596) 가 누계 CPU 4.8 시간, 메모리 1.4 GB, 현재 CPU 23% 를 점유하고 있었습니다. 9 시간 전 시작된 프로세스인데 지금도 일하고 있는 모양새였습니다. 백그라운드에서 export 인코딩이 돌고 있을 가능성이 가장 높아 보였습니다.
"Vrew 를 닫았다" 와 "Vrew 가 종료됐다" 는 다른 이야기였습니다. 시스템 트레이로 최소화돼 있거나, 메인 창은 닫혔지만 백그라운드 작업이 계속 돌고 있는 상태일 수 있습니다. 어느 쪽이든 fd 는 그대로였습니다.
OS lock 을 강제로 풀면 안 되는 이유
여기서 "그러면 OS 레벨에서 fd 를 끊으면 되지 않냐" 는 생각이 자연스럽게 떠오릅니다. Windows 에는 그런 도구가 실제로 몇 가지 있습니다. 다만 어느 것도 안전하지는 않았습니다.
| 방법 | 작동 | 부작용 |
|---|---|---|
| handle.exe (Sysinternals) fd kill | OS 레벨에서 핸들을 끊음 | Vrew 디코더 ERROR_INVALID_HANDLE → renderer 크래시 + 진행 중 autosave 데이터 손실 |
taskkill /F vrew |
모든 fd 자동 해제 | 진행 중 export 강제 중단 + 미저장 변경분 손실 |
| MoveFileEx + DELAY_UNTIL_REBOOT | 다음 부팅 시 삭제 예약 | 즉시 회수 0, 재부팅까지 폴더 그대로 |
| Restart Manager API | 앱에 "fd 풀어라" 신호 | Vrew 가 RM 을 지원하지 않음 (Electron 일반) |
결국 어느 방법으로도 "안전하면서 즉시 회수" 가 되는 길은 없었습니다. 데이터 손실을 감수하고 끊거나, 재부팅까지 기다리거나, 사용자가 직접 Vrew 를 정상 종료하도록 안내하거나의 셋 중 하나였습니다. 자동화의 본질적 한계가 거기에 있었습니다.
정리 — OS file lock 이 결국 가장 강력한 안전망이었다
1편에서 적었던 문장 중에 "OS file lock 이 사실상 마지막 안전망입니다" 가 있었습니다. 그 때는 process guard 와 시간 가드와 KeepCount 가 본 무대이고, OS lock 은 보험 정도로 적었습니다. 2편을 통과하면서 그 순서가 거꾸로 됐습니다. 시간 가드도 session 가드도 size 가드도 결국 OS lock 한 줄의 안전망 위에서만 의미가 있었습니다.
그렇다면 5-layer 가드 작업이 무의미했던 것은 아닌가 싶기도 합니다. 회수 효과만 보면 그렇습니다. 다만 안전성을 입증한 부분은 따로 의미가 있다는 생각이 들었습니다. 매 30 분 발화마다 task 가 안전하게 SKIP 로그만 쌓고 끝납니다. 어느 시점에 Vrew 가 정상 종료되거나 timeline 이 닫히면, 그 다음 30 분 boundary 에서 12.7 GB 가 일괄 회수됩니다. 그 전까지는 가만히 기다리는 task 가 되는 셈입니다.
사용자 안내도 한 줄 바뀌었습니다. 처음에는 "회수가 안 되면 Vrew 를 한 번 종료해주세요" 였는데, 위의 백그라운드 잔존 사례를 본 뒤로는 "시스템 트레이 (시계 옆 ^) 를 확인하고, 거기에 Vrew 가 있으면 우클릭 종료하고, 그래도 회수가 안 되면 작업관리자의 vrew.exe 도 마저 작업 끝내기" 로 좀 더 길어졌습니다. MainWindowHandle = 0 인 helper 프로세스까지 사용자 눈에 보이게 안내하는 쪽이 결국 정확했습니다.
1편을 마치면서 "공식이 안 주는 옵션을 외부에서 메우기" 라고 적었던 기억이 납니다. 2편에서는 그 한 줄이 "공식이 안 풀어주는 핸들은 외부에서도 못 풀더라" 로 한 번 더 echo 됐습니다. 다만 한계를 안다는 것 자체도 다음 사람에게는 작은 단축키가 된다는 생각이 들었습니다. 같은 문제로 5-layer 가드 같은 것을 짜기 전에, "OS lock 까지 닿으면 거기까지가 한계" 라는 한 줄을 미리 알아둘 수 있다면 그 시간만큼은 아낄 수 있을 것 같습니다.
스크립트는 1편과 같은 비공개 레포에서 함께 운영 중입니다. 5-layer 가드 코드와 통합 후보 수집 코드는 본문 단락에 그대로 옮겨두었으니, 같은 문제를 마주하신 분은 그 조각만으로도 비슷한 도구를 만드실 수 있을 것 같습니다. 전체가 필요하시면 1편처럼 댓글이나 이메일로 가볍게 문의 주시면 되겠습니다.
관련 글
Vrew autosave_backup 96GB 자동 정리 — Windows 자동화 함정 5가지
Vrew 의 autosave_backup 폴더가 Windows 의 %APPDATA% 에 96.5GB 까지 누적되는 사례를 PowerShell + Task Scheduler 로 30 분마다 자동 정리했습니다. UTF-8 BOM, vertical tab, SSH ACL 같은 Windows 자동화 함정 다섯 가지도 함께 정리합니다.
Vrew 내보내기가 0%에서 멈출 때 — 로그가 알려준 진짜 원인
Vrew 내보내기가 특정 프로젝트만 0%에서 멈췄습니다. 처음엔 투명 GIF의 알파 채널이 원인이라 확신했지만, 과거 성공 로그에도 투명 GIF가 멀쩡히 들어 있었습니다. 가설을 버리고 성공·실패 GIF를 나란히 비교하니 진짜 차이는 길이였습니다. 안 되는 사례만이 아니라 되는 사례를 함께 봐야 진짜 변수가 드러난다는 것을, 로그를 따라가며 다시 배운 기록입니다.
Playwright MCP 19개 프로세스 사고, LaunchAgent HTTP 상주로 통합한 회고
Claude Code 대화 세션을 닫으면 stdio Playwright MCP 도 같이 종료될 줄 알았습니다. 8개 세션을 띄워두고서야 19개 프로세스 1.6GB 점유를 측정했고, LaunchAgent HTTP 상주 모델로 통합한 운영 회고입니다.