홈시리즈멘토링

© 2026 정기창. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 정기창. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

RAM 32GB가 남는데 프로세스가 죽었다 — macOS 스왑과 Jetsam OOM

정기창·2026년 7월 12일

요즘 저는 로컬에서 음성을 합성해 영상에 얹는 파이프라인을 만들고 있습니다. TTS로 음성을 만들고 ffmpeg로 최종 영상을 렌더링하는 구조인데, 외부 API는 한 줄도 쓰지 않고 전부 제 맥북 안에서 돌아갑니다. 그런데 음성을 만들어 주는 TTS가 어느 날 아무런 설명도 없이 죽어버렸습니다.

돌이켜보면 이 사건은 제가 오랫동안 잘못 알고 있던 상식 하나를 바로잡아 준 계기였습니다. 램이 충분히 남아 있어도 프로세스는 메모리 문제로 죽을 수 있다는 사실입니다. 조금 더 정확히 말하면, 이번에 진짜로 부족했던 것은 램이 아니라 디스크였습니다.

램이 32GB나 남는데 프로세스가 죽었습니다

문제가 된 것은 chatterbox라는 TTS 엔진이었습니다. 음성 하나를 생성하는 서브프로세스가 실행 도중 갑자기 종료됐고, 로그에는 이런 한 줄만 남아 있었습니다.

died with <Signals.SIGKILL: 9>

SIGKILL(9)은 프로세스가 스스로 정리할 틈도 없이 커널이 강제로 끝내버리는 신호입니다. 예외를 던지고 죽은 게 아니라 바깥에서 목이 잘린 것이라, 스크립트 안에서는 왜 죽었는지 알 수 있는 단서가 거의 남지 않습니다. 당시 저는 TTS 모델 로드와 ffmpeg 렌더를 같은 스크립트 안에서 연달아 돌리고 있었고, 그 시점 부팅 디스크는 97%까지 차 있었습니다. 남은 공간은 17GB였습니다.

메모리가 부족한 게 아니었습니다

SIGKILL이 뜨면 가장 먼저 떠오르는 건 메모리 부족입니다. 그래서 저도 처음에는 TTS 모델이 램을 통째로 삼킨 게 아닌가 의심했습니다. 하지만 제 맥북은 M2 Pro에 램이 32GB이고, Activity Monitor를 봐도 물리 메모리는 넉넉하게 남아 있었습니다. 숫자만 보면 죽을 이유가 없었습니다.

한참을 들여다본 뒤에야, 문제의 축이 램이 아니라 디스크에 있다는 생각이 들었습니다. 디스크가 거의 꽉 차 있으면, 운영체제가 메모리 압박을 스왑으로 넘기려 해도 넘길 공간이 없습니다. 그리고 넘길 곳이 없어진 순간, macOS는 프로세스를 살려두는 대신 죽이는 쪽을 택합니다.

macOS는 스왑을 그때그때 디스크에 만듭니다

여기서 두 가지 원리가 맞물립니다. 첫째, macOS의 스왑은 미리 잡아둔 고정 크기 파티션이 아니라 동적 스왑입니다. 메모리가 부족해지면 부팅 볼륨의 /System/Volumes/VM 아래에 필요한 만큼 스왑파일을 그때그때 만들어 페이지를 밀어냅니다. 바꿔 말하면, 디스크에 여유가 없으면 스왑파일 자체를 만들지 못합니다.

둘째, 그렇게 스왑도 못 늘리는 상황이 되면 Jetsam이 개입합니다. Jetsam은 원래 iOS에서 메모리를 관리하던 장치인데, 지금은 macOS에도 이식되어 있습니다. 메모리 압박이 임계에 닿으면 Jetsam은 우선순위가 낮거나 메모리 사용량(footprint)이 가장 큰 프로세스를 골라 SIGKILL로 정리합니다. 제 TTS 프로세스는 그 순간 가장 덩치가 큰 후보였고, 그래서 선택됐습니다.

숫자로 확인한 스왑의 정체

추측만으로는 개운하지 않아서 /usr/bin/time -l로 실제 사용량을 재봤습니다. 결과는 조금 뜻밖이었습니다.

측정peak RSS비고
chatterbox 로드만4.7GB모델만 올린 상태
로드 + 음성 생성(10초 1장)5.0GBpeak memory footprint는 약 38GB로 표시

실제 상주 메모리(RSS)는 5GB인데 "peak memory footprint"가 38GB로 찍힌 대목이 눈에 걸렸습니다. 물리 램이 32GB인데 그걸 넘는 값이 나온 것입니다. 이건 오류가 아니라, MPS(Metal) 계열이 유니파이드 메모리를 회계하는 방식 때문입니다. GPU와 CPU가 메모리를 공유하는 구조라 footprint 지표가 물리 램을 넘겨 표시될 수 있고, 실제로 상주하는 양은 RSS 쪽인 5GB가 맞습니다.

더 결정적인 숫자는 스왑이었습니다. 음성을 생성하는 동안 시스템 스왑 총량이 3GB에서 13GB로, 그러니까 한 번에 10GB가 불어났다가 프로세스가 끝나자 다시 돌아왔습니다. 여유가 17GB뿐인 디스크에서 스왑이 10GB를 요구했다는 뜻입니다.

부수적으로 하나 더 관찰한 게 있습니다. 스왑에 눌리기 시작하자 같은 프로세스인데도 생성 속도가 17 it/s(카드 한 장에 15초)에서 0.5 it/s(9분)까지 무너졌습니다. 머신이 느려진 게 아니라 스왑이 느린 것이었습니다. 디스크를 왕복하는 순간 연산 속도는 의미를 잃습니다.

임계는 사용률 %가 아니라 절대 여유 GB였습니다

이 사건에서 제가 가장 오래 붙들고 생각한 부분이 여기입니다. 우리는 흔히 "디스크 90%를 넘기면 위험하다" 같은 퍼센트 기준으로 이야기합니다. 그런데 스왑 관점에서 보면 그 % 자체는 큰 의미가 없다는 결론에 이르렀습니다.

스왑이 실패하는 조건은 의외로 단순합니다. 그 순간 밀어내야 할 페이지의 양이, 디스크에서 스왑파일로 확보할 수 있는 여유보다 클 때입니다. 그렇다면 중요한 건 전체 용량 대비 몇 %가 찼느냐가 아니라, 지금 이 순간 몇 GB가 비어 있느냐입니다. 512GB 디스크가 96% 찼든 2TB 디스크가 99% 찼든, 남은 여유 GB가 같다면 위험도는 같습니다. 반대로 여유가 100GB라면 사용률이 90%여도 이 워크로드에는 안전합니다.

이 관점으로 이번 사고를 다시 계산해 보면 그림이 선명해집니다. chatterbox 생성 중 스왑 수요가 +10GB였고 당시 여유는 17GB였으니, 스왑 확장분만으로 이미 남은 공간의 60%를 먹은 셈입니다. 여기에 ffmpeg가 동시에 상주하고, 백업용 zip 임시파일이 얹히고, APFS 컨테이너가 거의 찬 상태에서 필요한 작업 공간까지 겹치면서 한도를 넘겼습니다. 고정된 % 임계값이 있었던 게 아니라, 그 순간의 수요와 여유가 만나는 지점에서 무너진 것입니다.

%가 아닌 절대값으로 가드를 세웠습니다

그래서 파이프라인 앞단에 넣은 preflight 가드도 %가 아니라 절대값을 기준으로 잡았습니다. 실측한 스왑 수요(약 13GB)에 마진을 더한 값입니다.

# preflight 가드 (절대값 기준)
부트 볼륨 여유 20GB 미만  → ABORT
부트 볼륨 여유 30GB 미만  → WARN
메모리 free 30% 미만       → ABORT

한 가지 더 주의할 점이 있습니다. APFS에는 purgeable(퍼지 가능) 공간이 있어서, Finder가 보여주는 여유 공간과 df가 보여주는 여유가 서로 다를 수 있습니다. 스왑 확장은 지금 당장 실재하는 공간을 필요로 하기 때문에, 가드는 낙관적인 Finder 값이 아니라 보수적인 df 값을 기준으로 삼아야 한다는 생각이 들었습니다.

다만 가드는 방어일 뿐입니다

이후 밀린 백업을 정리하면서 디스크를 15GB에서 82GB까지 비웠습니다. 이 워크로드 기준으로 6배쯤 되는 헤드룸이 생긴 셈입니다. 그런데 그 백업을 하려면 NTFS 외장하드에 써야 했고, 거기서 또 다른 삽질이 기다리고 있었습니다. 그 이야기는 세 번째 글로 따로 남기려 합니다.

정작 이번 일에서 제일 크게 남은 생각은 조금 다른 것이었습니다. 가드는 어디까지나 방어입니다. 위험한 순간에 멈춰 세워 줄 뿐, 근본적으로 안전해지는 것은 아닙니다. 진짜 해결은 프로세스가 요구하는 메모리 자체를 줄이는 것이라는 결론에 이르렀고, 그래서 TTS 엔진을 통째로 바꾸기로 했습니다. peak RSS 5GB를 0.8GB로 줄인 그 과정은 바로 다음 글에서 이어가겠습니다.

macOS스왑JetsamSIGKILLOOM메모리 관리Apple Silicon

관련 글

맥북에서 jest가 OOM 났는데, 알고 보니 macOS 메모리 지표가 거짓말이었다 — ts-jest에서 @swc/jest로

jest가 OOM 날 것 같아 중단했지만, 알고 보니 macOS의 Pages free 지표가 거짓말이었습니다. memory_pressure로 본 실제 여유는 87%. 진짜 원인은 Jest 기본 워커 수와 ts-jest의 V8 heap 부담이었고, @swc/jest로 이관해 heap 50%·속도 5배 개선한 기록입니다.

관련도 92%

내 목소리를 무료로 AI에 클로닝하기 — Mac 오픈소스 TTS 실전기

무료 플랜이 있다고 내 목소리가 무료로 클론되는 건 아니었습니다. Mac에서 오픈소스 TTS(XTTS·Chatterbox)로 AI 음성 클로닝을 직접 시도하며, 라이선스가 가른 선택과 레퍼런스 정제·파라미터 튜닝·GPU 신화까지 더듬어본 실전기입니다.

관련도 89%

Claude Code, 메모리 관점에서 mcp 바라보기

맥북 32GB의 상당량을 평상시에 잡아먹고 있던 원인 중 하나는 모든 MCP를 켜둔 Claude Code 세션이었습니다. 세션별 alias로 필요한 MCP만 활성화하는 방식으로 메모리 압박을 줄여본 기록입니다.

관련도 88%