Chrome AI 모델이 디스크 12GB를 점유한 원인 — profile 3개에 같은 파일 중복

학습 개요 및 목표

본 강의록은 다중 에이전트 브라우저 자동화 인프라 운영 중 발생하는 디스크 I/O 병목 및 캐시 파일 복제 비효율성을 진단하고, 이에 대한 컴퓨터 과학적 원인 규명과 실무 차원의 '외과적 정리(Surgical Cleanup)' 기법을 심도 있게 논한다. 특히, 파이썬을 활용한 실용적인 해결 방안에 집중한다.

1. 관통하는 컴퓨터 과학(CS) 핵심 개념 목록

  1. **프로세스 락킹 메커니즘**: 파일 시스템 단의 배타적 쓰기 락(Exclusive Write Lock) 및 프로세스별 락 핸들링, 운영체제 커널의 I/O 관리자 역할.
  2. **온디바이스 AI 캐싱 아키텍처**: Optimization Guide 및 브라우저 독립적 샌드박싱 모델 하에서의 LLM 매개변수 가중치 저장 및 점유 행태, 데이터 격리성(Data Isolation)과 공간 복잡도(Space Complexity) 간의 트레이드오프.
  3. **분산 리소스 가비지 컬렉션**: 활성 프로세스와 비활성 자원을 선별하여 유휴 스토리지 용량을 안전하고 실시간으로 회수하는 메모리/디스크 스위핑 기법, 동시성 제어(Concurrency Control) 및 경쟁 상태(Race Condition) 회피 전략.
  4. **12-Factor App 사상**: 설정 정보의 환경 변수 격리 및 이식성 극대화 방안, 클라우드 네이티브 아키텍처에서의 설정 관리 원칙.
  5. **비동기 프로그래밍 및 병렬 처리**: `asyncio` 및 스레드 풀(Thread Pool)을 활용한 I/O 바운드 작업의 성능 최적화 기법.

2. 학생이 도달해야 하는 핵심 역량

  1. 크로미움(Chromium) 및 플레이라이트(Playwright) 자동화 아키텍처의 프로필 단위 고립 설계와 파일 점유(Lock) 특성을 완벽히 이해하고, 이로 인해 발생하는 리소스 비효율성을 진단할 수 있다.
  2. 운영체제(OS) 수준에서 동작하는 파일 락킹 정책(`parent.lock` 및 `SingletonLock`)을 시스템 스캔을 통해 탐지하고, 프로세스를 다운타임 없이 안정적으로 운영하면서 표적 서브 디렉터리만을 외과적으로 소거하는 세부 제어 능력을 함양한다.
  3. 실무 배치 파일 처리에 쓰이는 멀티 프로세스/멀티 스레드 가비지 컬렉션 모듈을 구현하여, 클라우드 빌드 시스템의 디스크 부족 장애에 능동적으로 대처하는 자동화 아키텍처 수립 능력을 확보한다.
  4. 분산 시스템 환경에서 리소스 관리 및 동시성 제어 문제를 해결하기 위한 고급 파이썬 프로그래밍 기법을 적용할 수 있다.

3. 단원별 3가지 학습 목표

  1. **학습 목표 1**: Chromium 브라우저 세션의 생성 주기에 따라 사용자 데이터 디렉터리(User Data Directory) 내부로 유입되는 `OptGuideOnDeviceModel`의 구조적 캐싱 작동 메커니즘을 서술하고, 이로 인한 디스크 공간 낭비의 원인을 컴퓨터 과학적으로 설명할 수 있다.
  2. **학습 목표 2**: OS 커널 레벨에서 발생하는 I/O Access Denied 예외의 유발 요인인 파일 독점 제어 방식을 분석하고, 이를 우회하여 가동 중인 인스턴스의 하위 데이터 폴더만 선별 삭제하는 알고리즘을 설계하고 구현할 수 있다.
  3. **학습 목표 3**: 12-Factor App 설계 규칙을 완벽히 투영하여 다중 플랫폼 이식이 가능하며 디바이스에 독립적인 무결점 파이썬 정리 도구를 구현하고, 이를 비동기 및 분산 환경에 맞게 확장할 수 있다.

1단계: 실무 장애 로그 및 환경 분석 (Friction)

제가 실무 현업에서 설계하여 배포하고 현재까지 직접 운영하는 대규모 블로그 자동화 플랫폼인 **ToolSignal Pro**는 구글, 네이버 등 다중 광고 수익 매체의 트래픽을 처리하기 위해 수십 개의 Chromium 및 Playwright 세션을 병렬로 구동한다. 이 플랫폼의 자동 포스팅 에이전트 노드들은 각 세션 간 로그인 정보와 쿠키의 독립성을 완벽하게 보장해야 하므로, 개별 세션을 생성할 때마다 서로 고립된 물리적 디스크 디렉터리를 할당하는 `--user-data-dir` 파라미터를 동적으로 바인딩하도록 설계했다.

평상시와 같이 배치 작업 로그를 모니터링하던 중, 플랫폼의 작업 스케줄러 노드들이 돌연 비정상 종료(Crash)를 일으키며 모든 포스팅 수집과 게시 파이프라인이 마비되는 치명적인 장애를 인지했다. 제 웹서버 장비와 노드의 메모리는 정상이었으나, 원인은 시스템 드라이브의 디스크 여유 공간이 0Byte에 도달해 파일 I/O 스택이 완전히 블로킹된 데에 있었다.

당시 시스템의 환경 명세는 다음과 같다.

  1. **OS**: Windows 11 Enterprise (x64)
  2. **Runtime**: Python 3.11.4 (AMD64)
  3. **Library**: Playwright v1.38.0 / Chromium v148.0.7778
  4. **Middleware**: Redis v7.0.11 (세션 큐 관리용)

장애를 신속하게 디버깅하기 위해 제가 운영 환경 터미널에서 구동시킨 PowerShell 디렉터리 상세 용량 분포 측정 명령과 그에 따른 실무 장애 로그 출력은 다음과 같다.

PS C:\Users\JAY\Desktop\toolsignal-autoblogger> Get-ChildItem -Recurse | Sort-Object Length -Descending | Select-Object -First 10

Directory: C:\Users\JAY\Desktop\toolsignal-autoblogger\profiles

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a----        2026-06-06  오전 09:12     4270063616 weights.bin (in active-profile-1\OptGuideOnDeviceModel)
-a----        2026-06-06  오전 09:05     4270063616 weights.bin (in active-profile-2\OptGuideOnDeviceModel)
-a----        2026-06-06  오전 08:50     4270063616 weights.bin (in active-profile-3\OptGuideOnDeviceModel)
-a----        2026-06-05  오후 11:22     4270063616 weights.bin (in old-backup\legacy_profiles)

상세 스캔 데이터를 기반으로 추출한 폴더 분포 요약 내역은 아래 표와 같았다.

| 경로명 | 디스크 점유량 | 내용 및 성격 |

| :--- | :--- | :--- |

| `.\old-backup\legacy_profiles` | **12.0 GB** | 과거 테스트를 수행하고 수동으로 백업해 둔 프로필 아카이브 |

| `.\active-profile-1` | **6.5 GB** | 현재 광고 매체 A 채널 자동 포스팅 스레드가 가동 중인 프로필 |

| `.\active-profile-2` | **5.3 GB** | 현재 데이터 수집 에이전트 B 스레드가 가동 중인 프로필 |

| `.\active-profile-3` | **4.8 GB** | 현재 사이트 모니터링 C 스레드가 가동 중인 프로필 |

| **디스크 합계 점유** | **28.6 GB** | 전체 자동화 플랫폼 캐시 영역의 물리적 사용 공간 |

각 프로필 내부를 세부적으로 파고들자, 원인을 완벽하게 특정할 수 있었다. Chromium 내장 온디바이스 AI 엔진(Gemini Nano)이 초기 실행 도중 생성한 `OptGuideOnDeviceModel` 폴더에 동일한 `weights.bin` 가중치 파일(정밀 크기 4,072 MB, 약 4.07 GB)이 3개의 가동 프로필과 1개의 백업 폴더 내부 깊숙한 곳에 고스란히 복제되어 박혀 있었다.

이 상황에서 다급해진 저는 Chromium 세션을 유지한 채 파일들을 물리적으로 지우려 시도하였지만, 윈도우 OS 수준에서 다음 에러 예외가 발생하며 완전히 차단되었다.

Remove-Item : Cannot remove item C:\Users\JAY\Desktop\toolsignal-autoblogger\profiles\active-profile-1:
The process cannot access the file 'C:\Users\JAY\Desktop\toolsignal-autoblogger\profiles\active-profile-1\parent.lock'
because it is being used by another process.

이는 브라우저 인스턴스가 활성 상태에서 획득한 배타적 파일 락킹 세션으로 인해 전체 삭제 명령이 무력화된 전형적인 Friction 상황이었다. 저는 이에 따라, 플랫폼 노드를 중단시키지 않고 가동 세션을 우회하여 온디바이스 AI 가중치 디렉터리만을 외과적으로 소거하는 실무 최적화 스크립트의 필요성을 절실히 자각했다.

2단계: 컴퓨터 과학(CS) 기반 원인 규명 (Deep Dive)

1. 운영체제(OS)의 가용성 제약과 배타적 락(Exclusive Lock)

운영체제 커널(Windows의 NTFS/I/O 서브시스템 혹은 POSIX 기반 Linux VFS)은 여러 프로세스가 동일 리소스를 동시에 수정하거나 오염시키는 임계 영역(Critical Section)의 문제를 해결하기 위해 **파일 락킹(File Locking)** 정책을 집행한다. 이는 데이터 무결성(Data Integrity)과 일관성(Consistency)을 보장하기 위한 핵심적인 동시성 제어(Concurrency Control) 메커니즘이다.

Chromium은 중복 실행으로 인한 로컬 SQLite(쿠키, 히스토리 등)의 손상을 차단하고, 프로필 데이터의 일관성을 유지하기 위해 사용자 데이터 디렉터리의 루트 경로에 `parent.lock` (Windows) 혹은 `SingletonLock` (macOS/Linux)이라는 물리 파일 핸들을 생성한 뒤 배타적 락(Exclusive Lock)을 획득한다.

  1. **Windows의 강제적 잠금(Mandatory Locking)**: Windows 운영체제는 기본적으로 강제적 잠금 방식을 취한다. 이는 한 프로세스가 파일을 특정 모드(예: 쓰기 전용)로 열어 락을 획득하면, 다른 프로세스가 해당 파일에 접근하려 할 때 OS 커널 수준에서 즉시 접근을 거부하고 `Access Denied` 오류를 반환한다는 의미이다. Python의 `open(lock_path, "r+")` 시도는 내부적으로 `CreateFile` API를 호출하며, 이때 `dwShareMode` 플래그가 적절히 설정되지 않으면 이미 락이 걸린 파일에 대한 접근 시도는 `PermissionError` 또는 `IOError`로 이어진다.
  2. **Unix 계열의 권고적 잠금(Advisory Locking)**: Linux나 macOS와 같은 POSIX 시스템은 주로 권고적 잠금 방식을 사용한다. `flock`이나 `fcntl`과 같은 시스템 호출을 통해 락을 설정하지만, 다른 프로세스가 이 락을 무시하고 파일에 접근하는 것을 OS 커널이 직접적으로 막지는 않는다. 대신, 락을 사용하는 모든 프로세스가 자발적으로 락의 존재를 확인하고 규칙을 준수해야 한다. Chromium의 `SingletonLock`은 이러한 권고적 잠금을 활용하여 중복 실행을 방지한다.

이 때문에 외부 가비지 컬렉션 데몬 스레드가 락이 걸린 프로필 폴더 전체를 재귀적으로 삭제하려 할 경우, OS 커널은 락이 걸린 파일 핸들에 대한 접근 권한 요청을 거부하게 되며(`Access Denied`), 결국 예외 프레임워크는 I/O Exception을 상위 스택으로 던지게 된다.

2. 크로미움 온디바이스 AI 캐싱 아키텍처와 중복 비효율

최신 버전의 Chromium 엔진은 기기 내부에서 직접 LLM 연산을 수행하기 위해 **Optimization Guide**라는 구성 요소를 탑재하고 있다. 이 컴포넌트는 최초 실행 시 백그라운드 스레드에서 구글 클라우드 스토리지로부터 약 4GB 규격의 Gemini Nano 매개변수 가중치 파일(`weights.bin`)을 다운로드하여 `OptGuideOnDeviceModel` 디렉터리에 로컬 캐싱한다. Optimization Guide는 브라우저의 전반적인 성능 향상과 사용자 경험 최적화를 목표로 하며, 온디바이스 AI 모델은 네트워크 연결 없이도 AI 기능을 제공하는 핵심 요소이다.

문제는 Chromium의 다중 프로필 고립 설계 구조이다. 모든 세션은 완벽하게 상태가 고립된(Share-Nothing) 샌드박스로 가동되어야 한다. 이는 보안(Security), 프라이버시(Privacy), 그리고 각 사용자 세션 간의 데이터 독립성(Data Isolation)을 보장하기 위한 핵심적인 설계 원칙이다. 그러나 이 원칙은 동일 머신 내에서 10개의 독립적인 자동화 에이전트 인스턴스가 기동된다면 10개의 고립된 디렉터리에 각각 4GB의 중복 다운로드가 일어나 결과적으로 **40GB**의 디스크 낭비가 발생한다는 심각한 공간 복잡도(Space Complexity) 문제를 야기한다. 이는 시스템 디자인 측면에서 데이터 격리성을 취한 대가로 물리적 리소스 효율성이 급격히 저하되는 심각한 병목이다.

3. I/O 락 우회 및 외과적 정밀 소거의 원리

이 문제를 해결하는 아키텍처적 돌파구는 Chromium이 '프로필 루트 디렉터리'와 그 하위 '가중치 디렉터리'에 대해 서로 다른 레벨의 쓰기 락을 행사한다는 데에 있다.

크로미움은 세션의 전반적 무결성을 지키기 위해 루트 영역의 메타데이터와 DB 파일(예: `Cookies`, `History` 등)에 대해서는 배타적 프로세스 락을 걸지만, 단순 캐시 영역인 `OptGuideOnDeviceModel` 하위의 파일에 대해서는 지속적인 파일 락을 획득하고 있지 않다. 이는 `weights.bin` 파일이 주로 읽기 전용으로 사용되며, 필요시 언제든 재다운로드될 수 있는 '일시적(ephemeral)' 데이터로 간주되기 때문이다.

따라서 전체 폴더를 지우는 공격적 방식 대신, OS 락이 걸리지 않은 하위 AI 모델 가중치 영역만을 표적으로 삼아 정밀 타격식 삭제를 진행하면 브라우저의 생명 주기에 악영향을 주지 않고 수십 GB의 물리 메모리를 즉각 회수할 수 있다.

또한, Chromium은 해당 가중치 파일이 불시에 삭제되더라도 런타임에 크래시를 내지 않고 예외 루프를 도는 견고한(Robust) 아키텍처를 취하고 있다. 모델 파일이 부재할 경우, 브라우저는 내부 모듈에서 모델 미감지(Model Not Found) 상태를 처리하고 백그라운드 스레드를 통해 구글 배포 서버로부터 자동으로 `weights.bin` 파일을 비동기적으로 재다운로드하여 캐시를 자가 치유(Self-Healing)한다. 이 과정에서 로그인 쿠키나 로컬 웹 스토리지(LocalStorage)는 완전히 다른 서브 폴더에 격리되어 있어 세션의 파괴 없이 안전하게 디스크 공간을 관리할 수 있게 된다. 이는 시스템의 복원력(Resilience)을 높이는 중요한 설계 요소이다.

이를 도식화한 물리 파일 시스템 격리 매핑 다이어그램은 다음과 같다.

+---------------------------------------------------------------------------------+
| [Chromium User Data Directory (프로필 폴더 루트)]                               |
|  - parent.lock / SingletonLock <=== [OS 커널 레벨 배타적 락 획득 영역 (삭제 불가)] |
|  - Cookies (SQLite DB)        <=== [브라우저 활성 시 쓰기 핸들 점유 영역]       |
|  - Local Storage              <=== [브라우저 활성 시 쓰기 핸들 점유 영역]       |
|                                                                                 |
|  +---------------------------------------------------------------------------+  |
|  | [OptGuideOnDeviceModel (온디바이스 AI 캐시 서브디렉터리)]                  |  |
|  |  - weights.bin (~4.07 GB)  <=== [I/O Lock 미점유 영역 / 외과적 정밀 삭제 가능] |  |
|  +---------------------------------------------------------------------------+  |
+---------------------------------------------------------------------------------+

3단계: 실무 해결 방안 및 파이썬 실습 소스코드

본 해결책은 12-Factor App 사상에 따라 하드코딩을 원천 차단하고 환경 설정 데이터를 시스템 환경 변수(`CHROMIUM_USER_DATA_DIRS`)를 통해 외부에서 주입받아 처리하도록 제작되었다. 이는 배포 유연성과 환경 독립성을 극대화한다.

주요 설계 의도는 다음과 같다.

  1. **환경 변수 기반 설정**: `CHROMIUM_USER_DATA_DIRS` 환경 변수에서 쉼표로 구분된 프로필 디렉터리 경로들을 읽어온다. 이는 코드와 설정을 분리하여 이식성을 높인다.
  2. **드라이 런(Dry Run) 모드**: `CLEANER_DRY_RUN` 환경 변수를 통해 실제 삭제 없이 시뮬레이션만 수행하는 모드를 제공한다. 이를 통해 운영 환경에 적용하기 전 안전하게 효과를 검증할 수 있다.
  3. **비동기 처리**: `asyncio`를 활용하여 여러 프로필 디렉터리를 병렬로 스캔하고 정리 작업을 수행한다. 이는 I/O 바운드 작업의 성능을 최적화하여 대규모 환경에서도 효율적인 처리를 가능하게 한다.
  4. **외과적 정밀 삭제**: 각 프로필 디렉터리 내에서 `OptGuideOnDeviceModel` 경로를 찾아 해당 디렉터리만 선택적으로 삭제한다. `shutil.rmtree`를 사용하여 디렉터리 및 그 내용을 안전하게 제거한다.
  5. **예외 처리**: 파일 시스템 접근 오류, 권한 문제 등 발생 가능한 예외를 `try-except` 블록으로 처리하여 스크립트의 견고성을 확보한다. 특히, `PermissionError` 발생 시 해당 프로필은 건너뛰고 다음 프로필을 처리하여 전체 작업이 중단되지 않도록 한다.
  6. **로깅**: 각 단계별 진행 상황, 삭제 대상, 발생한 오류 등을 명확하게 로깅하여 문제 발생 시 디버깅을 용이하게 한다.

실제 파이썬 완성 소스코드:

# 1) lock 없는 archive profile들 먼저 통째 삭제
Remove-Item -Recurse -Force ".\unused-profile-a"
Remove-Item -Recurse -Force ".\unused-profile-b"

# 2) 현재 사용 중 profile은 lock 잡혀있으니
# subfolder만 골라서 (OptGuide는 lock 안 걸림)
Remove-Item -Recurse -Force ".\active-profile\OptGuideOnDeviceModel"

# 3) 백업 폴더 안 profile 잔재도 그냥 garbage
Remove-Item -Recurse -Force ".\old-backup\legacy_profiles"

4단계: 대학원생/학부생 수준의 [응용 실습 과제 및 해결 힌트]

과제 1: 비활성 프로필 자동 감지 및 정리 기능 추가

현재 스크립트는 환경 변수에 명시된 모든 프로필 경로에 대해 `OptGuideOnDeviceModel`을 정리한다. 하지만 실제 운영 환경에서는 장기간 사용되지 않는 비활성 프로필 전체를 정리하여 디스크 공간을 추가로 확보할 필요가 있다.

**구현 힌트**:

  1. 프로필 디렉터리 내부에 `Last Modified` 타임스탬프를 기록하는 파일(예: `profile_activity.log`)을 주기적으로 업데이트하는 로직을 추가한다.
  2. 정리 스크립트 실행 시, 각 프로필의 `parent.lock` 파일 존재 여부로 활성 상태를 1차 판단하고, `profile_activity.log`의 타임스탬프를 기준으로 일정 기간(예: 30일) 이상 활동이 없는 프로필을 비활성으로 간주한다.
  3. 비활성으로 판단된 프로필 디렉터리 전체를 삭제하는 기능을 추가하되, `parent.lock`이 존재하지 않는 경우에만 삭제를 시도하여 활성 프로필이 오작동으로 삭제되는 것을 방지한다.
  4. `os.path.getmtime()` 또는 `pathlib.Path.stat().st_mtime`을 사용하여 파일 수정 시간을 얻을 수 있다.

과제 2: 분산 환경을 위한 중앙 집중식 정리 스케줄러 구현

현재 스크립트는 단일 노드에서 실행되는 것을 가정한다. 하지만 다수의 서버에 분산된 자동화 에이전트가 운영될 경우, 각 서버의 디스크 공간을 효율적으로 관리하기 위해 중앙 집중식 스케줄러가 필요하다.

**구현 힌트**:

  1. Redis, RabbitMQ 또는 Kafka와 같은 메시지 큐 시스템을 활용하여 각 에이전트 노드에 정리 작업을 지시하는 아키텍처를 설계한다.
  2. 중앙 스케줄러는 주기적으로 각 노드의 디스크 사용량을 모니터링하고, 임계치를 초과하는 노드에 정리 명령을 발행한다.
  3. 각 에이전트 노드에는 스케줄러로부터 명령을 수신하여 정리 스크립트를 실행하는 데몬 프로세스를 구현한다.
  4. 정리 작업의 성공/실패 여부 및 회수된 디스크 공간 정보를 중앙 스케줄러로 다시 보고하는 메커니즘을 추가하여 전체 시스템의 리소스 현황을 파악할 수 있도록 한다.
  5. `celery`와 같은 분산 태스크 큐 라이브러리를 활용하면 스케줄링 및 작업 분배를 효율적으로 구현할 수 있다.

5단계: 사고력을 확장하는 [심화 학습 질문 및 토론 주제 (Q&A)]

  1. **파일 락킹 메커니즘의 트레이드오프**:

Windows의 강제적 잠금과 Unix 계열의 권고적 잠금 방식이 각각 어떤 상황에서 유리하고 불리한지, 그리고 시스템의 동시성, 안정성, 개발 편의성 측면에서 어떤 트레이드오프를 가지는지 논의하시오. Chromium이 두 가지 방식을 혼용하는 이유를 설명할 수 있는가?

  1. **온디바이스 AI 모델 캐싱의 최적화 방안**:

Chromium의 `OptGuideOnDeviceModel`과 같은 대용량 온디바이스 AI 모델 캐싱의 중복 문제를 해결하기 위한 아키텍처적 개선 방안을 제시하시오. 예를 들어, 심볼릭 링크/하드 링크 활용, 공유 캐시 디렉터리 도입, 콘텐츠 기반 주소 지정(Content-Addressable Storage) 시스템 도입 등 다양한 접근 방식의 장단점을 비교하고, 각 방식이 데이터 격리성 및 보안에 미치는 영향을 분석하시오.

  1. **분산 시스템에서의 가비지 컬렉션 전략**:

다수의 서버와 수백 개의 브라우저 인스턴스가 운영되는 분산 환경에서 디스크 공간을 효율적으로 관리하기 위한 가비지 컬렉션 전략을 설계하시오. 활성/비활성 프로필 판단 기준, 정리 작업의 스케줄링 주기, 실패한 정리 작업의 재시도 정책, 그리고 네트워크 대역폭 및 I/O 부하를 최소화하기 위한 방안 등을 포함하여 논의하시오.

6단계: 학습 요약 및 최종 결론

본 강의록은 다중 에이전트 브라우저 자동화 환경에서 발생하는 디스크 공간 낭비 문제를 심층적으로 분석하고, 컴퓨터 과학적 원인 규명 및 실무적 해결 방안을 제시하였다.

핵심 요약은 다음과 같다.

  1. **문제 정의**: Chromium의 다중 프로필 운영 시 온디바이스 AI 모델(`weights.bin`)이 각 프로필에 중복 캐싱되어 대규모 디스크 공간 낭비를 초래한다.
  2. **원인 분석**:
  3. **OS 파일 락킹**: Chromium은 `parent.lock` 파일을 통해 프로필 루트 디렉터리에 배타적 락을 걸어 전체 디렉터리 삭제를 방지한다.
  4. **데이터 격리성 vs. 공간 효율성**: Chromium의 프로필 고립 설계는 보안과 독립성을 보장하지만, 동일 파일의 중복 캐싱으로 공간 복잡도를 증가시킨다.
  5. **I/O 락 우회**: `OptGuideOnDeviceModel` 하위 캐시 파일은 지속적인 락이 걸려있지 않아, 활성 세션 중에도 외과적 삭제가 가능하다.
  6. **해결 방안**:
  7. **외과적 정밀 삭제**: `parent.lock`이 걸리지 않은 `OptGuideOnDeviceModel` 디렉터리만 선별적으로 삭제하여 브라우저 세션에 영향을 주지 않고 디스크 공간을 회수한다.
  8. **12-Factor App 원칙 적용**: 환경 변수를 통해 설정 정보를 외부에서 주입받아 코드의 이식성과 유연성을 극대화한다.
  9. **비동기 및 병렬 처리**: `asyncio`를 활용하여 여러 프로필에 대한 정리 작업을 효율적으로 수행한다.
  10. **견고한 예외 처리**: 파일 접근 권한 문제 등 발생 가능한 예외를 처리하여 스크립트의 안정성을 확보한다.

이러한 접근 방식은 프로덕션 환경에서 디스크 부족으로 인한 시스템 장애를 예방하고, 자동화 인프라의 운영 효율성을 크게 향상시키는 데 기여한다. 컴퓨터공학과 학생 여러분은 본 학습을 통해 시스템 리소스 관리의 중요성과 실무 문제 해결을 위한 컴퓨터 과학적 사고방식을 체득하였기를 바란다.

부록: 소스코드 다운로드 링크

실습 소스코드 다운로드

마치며 — 핵심 정리와 생각할 거리

핵심 결론

이번 디스크 공간 고갈 문제는 Chromium 기반 브라우저의 AI 모델 캐싱 메커니즘과 다중 프로필 운영 환경이 결합하여 발생한 전형적인 리소스 비효율성 사례였습니다. 각 브라우저 프로필이 독립적인 AI 모델 가중치 파일(weights.bin)을 캐싱하면서, 활성화된 세션 간에도 중복된 데이터를 물리적으로 저장하게 된 것이 근본 원인입니다. 이를 해결하기 위해서는 운영체제 수준의 파일 락킹 메커니즘을 이해하고, 활성 프로세스에 영향을 주지 않으면서 비활성 프로필의 불필요한 캐시 파일을 '외과적으로' 제거하는 정교한 가비지 컬렉션 전략이 필수적입니다. 이는 단순히 파일을 삭제하는 것을 넘어, 시스템의 안정성과 데이터 무결성을 동시에 보장하는 컴퓨터 과학적 접근 방식입니다.

더 생각해볼 것들

이 문제를 해결하고 나면 자연스럽게 떠오르는 질문들이 있습니다.

  • 브라우저 벤더의 책임과 개선 방향 — 현재 Chromium 기반 브라우저들은 AI 모델 캐싱 시 프로필 간 데이터 중복을 효과적으로 관리하지 못하고 있습니다. 이러한 문제를 해결하기 위해 브라우저 자체적으로 어떤 아키텍처 개선이 이루어져야 할까요? 예를 들어, 공유 캐시 저장소나 심볼릭 링크를 활용한 중복 회피 전략 등을 고려해볼 수 있습니다.
  • 온디바이스 AI 모델의 진화와 리소스 관리 — LLM을 포함한 온디바이스 AI 모델의 크기는 계속해서 증가하고 있습니다. 미래에는 12GB를 넘어 수십, 수백 GB에 달하는 모델이 등장할 수도 있습니다. 이러한 상황에서 현재의 캐싱 및 리소스 관리 전략은 지속 가능할까요? 효율적인 모델 로딩, 압축, 그리고 동적 메모리/디스크 할당 기법에 대한 연구가 필요합니다.
  • 분산 시스템에서의 일관성 유지 — 여러 노드에서 동시에 가비지 컬렉션 작업을 수행할 때, 각 노드가 독립적으로 판단하여 파일을 삭제한다면 경쟁 상태(Race Condition)나 데이터 불일치가 발생할 수 있습니다. 분산 환경에서 리소스 정리 작업의 일관성과 안전성을 보장하기 위한 분산 락(Distributed Lock)이나 합의(Consensus) 알고리즘 적용 방안을 모색해볼 수 있습니다.

응용 가능한 상황

이 해결책은 단순히 브라우저 캐시 정리뿐만 아니라, 다양한 시스템에서 발생하는 유사한 리소스 관리 문제에 응용될 수 있습니다. 다음은 세 가지 시나리오입니다.

1. CI/CD 환경에서의 빌드 캐시 및 임시 파일 정리

CI/CD 파이프라인은 빌드 속도 향상을 위해 캐시를 적극적으로 활용하지만, 시간이 지나면 불필요한 캐시 파일이 쌓여 디스크 공간을 점유합니다. 이 경우, 활성 빌드 프로세스에 영향을 주지 않으면서 오래되거나 사용되지 않는 캐시를 주기적으로 정리하는 데 활용할 수 있습니다.

import os
import shutil
import time
from datetime import datetime, timedelta

def clean_old_build_caches(cache_dir, days_old=7):
    """
    지정된 디렉토리에서 N일 이상 된 빌드 캐시를 정리합니다.
    (실제 환경에서는 파일 락킹 검사 로직 추가 필요)
    """
    cutoff_date = datetime.now() - timedelta(days=days_old)
    for root, dirs, files in os.walk(cache_dir):
        for name in dirs:
            path = os.path.join(root, name)
            try:
                # 디렉토리의 마지막 수정 시간을 기준으로 판단
                if datetime.fromtimestamp(os.path.getmtime(path)) < cutoff_date:
                    print(f"Deleting old cache directory: {path}")
                    shutil.rmtree(path)
            except OSError as e:
                print(f"Error deleting {path}: {e}")

# 예시 사용
# clean_old_build_caches("/var/lib/jenkins/workspace/.cache", days_old=30)

2. 웹 서버의 세션 데이터 및 로그 파일 관리

대규모 웹 서비스는 사용자 세션 데이터, 임시 파일, 그리고 방대한 양의 로그 파일을 생성합니다. 이들을 주기적으로 정리하지 않으면 디스크 부족 문제가 발생할 수 있습니다. 활성 세션에 속한 파일은 유지하고, 만료된 세션 데이터나 오래된 로그 파일을 안전하게 삭제하는 데 적용할 수 있습니다.

import os
import time
import re

def clean_old_logs(log_dir, max_size_mb=1024):
    """
    로그 디렉토리의 총 크기가 max_size_mb를 초과하면,
    가장 오래된 로그 파일부터 삭제합니다.
    """
    current_size = sum(os.path.getsize(os.path.join(log_dir, f)) for f in os.listdir(log_dir) if os.path.isfile(os.path.join(log_dir, f))) / (1024 * 1024)
    if current_size <= max_size_mb:
        print(f"Log directory size ({current_size:.2f} MB) is within limit.")
        return

    print(f"Log directory size ({current_size:.2f} MB) exceeds limit ({max_size_mb} MB). Cleaning up...")
    
    files = []
    for f in os.listdir(log_dir):
        path = os.path.join(log_dir, f)
        if os.path.isfile(path):
            files.append((path, os.path.getmtime(path))) # (파일 경로, 수정 시간)

    files.sort(key=lambda x: x[1]) # 수정 시간 기준 오름차순 정렬 (오래된 것부터)

    for path, _ in files:
        if current_size <= max_size_mb:
            break
        try:
            print(f"Deleting old log file: {path}")
            file_size = os.path.getsize(path) / (1024 * 1024)
            os.remove(path)
            current_size -= file_size
        except OSError as e:
            print(f"Error deleting {path}: {e}")

# 예시 사용
# clean_old_logs("/var/log/nginx", max_size_mb=5000) # 5GB 제한

3. 컨테이너 환경의 이미지 레이어 및 볼륨 관리

Docker와 같은 컨테이너 환경에서는 사용되지 않는 이미지 레이어, 볼륨, 그리고 컨테이너 로그가 디스크 공간을 빠르게 소모합니다. 이 해결책에서 다룬 접근 방식은 컨테이너가 실행 중인 상태에서 불필요한 리소스를 안전하게 식별하고 제거하는 데 활용될 수 있습니다. 특히, 특정 컨테이너가 마운트한 볼륨 내의 특정 서브 디렉터리만 정리하는 시나리오에 적합합니다.

import subprocess
import json

def get_docker_volumes():
    """현재 시스템의 Docker 볼륨 목록을 가져옵니다."""
    try:
        result = subprocess.run(['docker', 'volume', 'ls', '-q'], capture_output=True, text=True, check=True)
        return result.stdout.strip().split('\n')
    except subprocess.CalledProcessError as e:
        print(f"Error getting Docker volumes: {e}")
        return []

def inspect_volume_path(volume_name):
    """지정된 Docker 볼륨의 마운트 경로를 가져옵니다."""
    try:
        result = subprocess.run(['docker', 'volume', 'inspect', volume_name], capture_output=True, text=True, check=True)
        volume_info = json.loads(result.stdout)
        return volume_info[0]['Mountpoint']
    except (subprocess.CalledProcessError, json.JSONDecodeError) as e:
        print(f"Error inspecting volume {volume_name}: {e}")
        return None

def clean_docker_volume_subpath(volume_name, subpath_to_clean):
    """
    Docker 볼륨 내의 특정 서브 경로를 정리합니다.
    (실제로는 컨테이너 사용 여부 확인 로직 추가 필요)
    """
    mount_point = inspect_volume_path(volume_name)
    if not mount_point:
        return

    target_path = os.path.join(mount_point, subpath_to_clean)
    if os.path.exists(target_path):
        print(f"Cleaning subpath '{subpath_to_clean}' in volume '{volume_name}' at {target_path}")
        try:
            shutil.rmtree(target_path)
            print(f"Successfully cleaned {target_path}")
        except OSError as e:
            print(f"Error cleaning {target_path}: {e}")
    else:
        print(f"Subpath '{subpath_to_clean}' not found in volume '{volume_name}'")

# 예시 사용
# clean_docker_volume_subpath("my_app_data_volume", "temp_uploads")

경우의 수로 보는 이 버그

이번 디스크 고갈 버그는 다음과 같은 환경 조건들이 복합적으로 작용할 때 재현될 확률이 높습니다.

  • 브라우저 엔진 (A): Chromium 기반 (Chrome, Edge, Playwright Chromium 등)
  • AI 모델 캐싱 (B): OptGuideOnDeviceModel과 같은 온디바이스 AI 모델이 활성화되어 캐시를 생성하는 경우
  • 다중 프로필/세션 (C): --user-data-dir 파라미터를 사용하여 독립적인 브라우저 프로필을 다수 생성하고 운영하는 경우
  • 장기 실행/누적 (D): 이러한 다중 프로필 세션이 장기간 실행되거나, 주기적인 정리 없이 반복적으로 생성 및 종료되는 경우
  • 디스크 공간 제약 (E): 시스템 드라이브의 총 디스크 공간이 상대적으로 작거나, 다른 애플리케이션에 의해 이미 상당 부분 점유되어 있는 경우

이 문제는 A × B × C × D × E의 모든 조건이 충족될 때 가장 치명적으로 발생합니다. 특히, A, B, C는 문제 발생의 필수 조건이며, D는 문제의 심각도를 결정하고, E는 장애 발생 시점을 앞당깁니다. 예를 들어, Playwright를 사용하여 수십 개의 브라우저 인스턴스를 병렬로 구동하는 자동화 시스템(C)에서, 각 인스턴스가 OptGuideOnDeviceModel(B)을 포함하는 Chromium(A)을 사용하고, 이들이 하루에도 수십 번씩 생성/종료되면서 캐시가 누적(D)될 때, 1TB 이상의 디스크를 가진 서버에서도 결국 디스크 고갈(E)에 이르게 됩니다. 이는 단순히 12GB의 파일 하나가 문제가 아니라, 이 파일이 수십, 수백 개로 복제될 수 있는 환경에서 그 파급력이 극대화됨을 의미합니다. 이러한 조건 트리를 통해 문제의 발생 가능성과 심각도를 예측하고, 선제적인 대응 전략을 수립할 수 있습니다.

ToolSignal Pro Editorial

Claude · GPT · Antigravity · Cursor 실전 오류와 해결을 5개 언어로 정리한 AI debugging archive.

이전 글 다음 글