Mac mini / Cloud Mac에서 Ollama, Claude Code, GitHub Actions를 동시에 돌리면 흔한 문제는 「성능 부족」이 아니라 Swap, CLI 지연, CI 빌드 저하입니다.
이 글은 도구 설치 튜토리얼이 아닙니다. 세 종류 AI workload가 같이 있을 때 메모리가 흔들리는 이유와 scheduling으로 피하는 방법을 설명합니다. 반례, 메모리 예산, 임계 scheduling, 30초 Runbook, 전체 스크립트를 제시하며 16GB vs 24GB benchmark는 반복하지 않습니다.
깊이 읽으면 AI workload scheduling 모델이 보입니다. 관측한 Cloud Mac에서 대부분 Swap/OOM은 절대 메모리 부족보다 scheduling 문제에 가깝습니다.
진짜 문제: 대부분 메모리 부족이 아니라 우선순위 부재
표면 증상은 「로컬 모델 유휴 + CI 피크 + 코딩 Agent 지연」——근본 원인을 「스펙업」으로 오해하기 쉽습니다.
관측한 Cloud Mac에서는 대부분 Swap/OOM이 절대 메모리 부족보다 workload scheduling 문제에 가깝습니다. 같은 Mac mini에 다음 3종 workload가 공존합니다(다음 절):
- Burst——GitHub Runner /
xcodebuild, push 시 +4–8 GB - Interactive——Claude Code, IDE + 터미널 + 대형 저장소 index
- Background——Ollama 등 로컬 Inference, 8B loaded 후 5–7 GB 조용히 점유
기본값은 통합 메모리를 평등하게 경쟁하고 아무도 양보하지 않습니다. 수정 방향: workload 우선순위와 트리거 조건 정의——background Inference를 「설치하면 상주」에서 unload 가능하고 CI 이벤트에 preempt되는 schedulable 리소스로.
잘못된 스케줄 반례(M4에서 매우 흔함)
소규모 팀 Cloud Mac에서 실제로 밟은 조합——onboarding 「금지 패턴」:
Bad schedule · three workloads default "all on" qwen3:8b always loaded → 5–7 GB (background, idle) push triggers xcodebuild → +4–8 GB peak (burst) Claude Code indexing large repo → +2–3 GB (interactive) Observed (24GB M4 Mac mini) Swap 0 → 2.1 GB xcodebuild link phase latency +~40% Claude Code terminal noticeably slower
M4 통합 메모리에서 8B 상주 + CI burst + 대화형 코딩이 동시에 일어나면 Swap은 매우 흔합니다. 반례의 가치는 workload scheduling이 장식이 아니라 다중 workload 공존의 전제 조건임을 보여 주는 것입니다.
3형태: burst / interactive / background
시계만으로는 부족합니다. Cloud Mac에서 3 workload 메모리 곡선은 완전히 다릅니다:
| 형태 | 대표 | 레이어 | 메모리 특성 | scheduling 우선순위 |
|---|---|---|---|---|
| Burst | xcodebuild, 링크, 시뮬레이터 |
L1 Runner | 첨예·예측 어려움 | 최고——Fact는 실패할 수 없음 |
| Interactive | Claude Code、IDE、SSH | L3 | 중간·사용자 상주 | 높음——Inference는 API, 대형 모델 미사용 |
| Background | Ollama embedding, 로그 요약 | L2 | 지연 가능·unload 가능 | 최저——burst에 양보 |
한 줄로: L2는 Stack에서 유일하게 「kick 가능」한 레이어——덜 중요해서가 아니라 작업이 대부분 비동기·재시도 가능하기 때문입니다.
메모리 예산: 3 workload 점유
Ollama / Claude Code / GitHub Runner를 예시 컴포넌트로, 수치는 M4 Mac mini 실측(steady, 컴파일 피크 제외):
| 컴포넌트 | 레이어 | 전형 점유 | 피크 설명 |
|---|---|---|---|
| macOS + 시스템 캐시 | L0 | 3–4 GB | 비교적 안정 |
| Claude Code 워크스페이스 | L3 | 1–3 GB | Inference는 API, 모델 가중치 없음 |
| GitHub Runner job | L1 | 2–6 GB(steady) | 링크 단계 +4–8 GB |
| Ollama · qwen3:8b | L2 | 5–7 GB | ollama stop 로 해제 가능 |
| Ollama · qwen3:14b | L2 | 9–13 GB | burst 병행 시 Swap 2GB+ 쉬움 |
| Ollama · nomic-embed-text | L2 | 0.3–0.8 GB | 낮 상주 가능한 경량 background |
24GB에서 낮 「코딩 + CI + 8B 상주」≈ 17 GB——아직 여유; 14B 상주를 겹치면 22 GB를 쉽게 넘깁니다. 예산표는 「가능한가」; scheduling은 「언제 누가 점유하는가」.
scheduling 모델: 시간대에서 memory_pressure 임계값으로
입문은 「낮/밤 시간표」(아래 모드 ②). 운영 upgrade는 메모리 압력 임계 scheduling——「22시 배치」를 사람이 기억하지 않게:
AI Workload Scheduler · L2-Q03 recommended rules When memory_pressure enters warn / critical (or equivalent > ~70%): → auto ollama stop qwen3:8b / qwen3:14b When memory_pressure normal (< ~50%) and idle > 10 min: → auto preload nomic-embed-text (keepalive 10m) When CI event trigger (Runner job start): → force CI mode: stop all large Ollama models, priority L1 > L3 > L2 When CI job succeeds + memory recovers: → async L2 batch (log summary / embedding rebuild)
시간대 스케줄은 baseline; 임계 scheduling은 upgrade. 소팀은 Runbook + CI 전 stop부터; 안정 후 memory_pressure guard(§ Runbook).
3가지 기본 스케줄 모드
임계 scheduling과 함께 쓰는 기본 전략:
| 모드 | 방법 | 적합 |
|---|---|---|
| ① 경량 병행 | 낮에는 nomic-embed-text만 상주; 8B/14B는 on-demand |
16GB Mac mini, 코딩 중심 |
| ② 시간대 분리 | 09–18 코딩+CI / 22–06 야간 배치 | 24GB, 정기 embedding/로그 |
| ③ CI 트리거 양보 | job 전 ollama stop, 종료 후 비동기 L2 |
push 잦음, xcodebuild 피크 높음 |
권장: ①+③ 전원 기본; ②는 야간 배치 보조; 임계 guard로 안전망.
Pipeline 분류: L2 vs L3
스케줄 전에 pipeline 고정. 아니면 Ollama 유휴(L2-Q01 · 전형적 오해):
| 작업 | 레이어 | scheduling 메모 |
|---|---|---|
| 저장소 수정, patch 생성 | L3 Claude Code(API) | 로컬 대형 모델 불필요 |
| 빌드·테스트·아카이브 | L1 Runner | burst 최우선; 피크 전 L2 stop |
| CI 실패 로그 요약 | L2 qwen3:8b |
job 후 비동기 또는 야간 배치 |
| CodeGraph / RAG embedding | L2 nomic-embed-text |
낮 상주 가능(<1GB) |
Runner 피크와 CI 시간 분리
L1 Runner는 Fact를 산출——Inference는 빌드 결과를 대체하지 않습니다. CI 측 최소 변경:
# .github/workflows/ios.yml · self-hosted macOS runner - name: Enter CI mode — free memory for xcodebuild run: | ollama ps ollama stop qwen3:8b 2>/dev/null || true ollama stop qwen3:14b 2>/dev/null || true sleep 30 # wait for memory reclaim; do not start xcodebuild same second - name: Build run: xcodebuild ...
실측: 24GB M4에서 ollama stop qwen3:8b 후 5–15초에 5–7 GB 해제; Swap 발생 후 완전 회수는 수 분——CI 전 stop은 최소 30초 전에.
Runbook: 30초판과 전체 스크립트
30초판(대부분 이 3단이면 충분)
전체 스크립트 생략? 아래 3단 복사로 80% 커버:
① CI 전(가장 중요)——GitHub Actions 또는 Runner hook에:
ollama stop qwen3:8b ollama stop qwen3:14b sleep 30
② 낮——경량 embedding만, 대형 모델 가중치 없음:
ollama run nomic-embed-text --keepalive 30m
③ 밤——배치 창에서 8B load:
ollama run qwen3:8b # then run your log summary / embedding rebuild script
전체판(프로덕션 Runbook)
LaunchAgent/cron/다중 환경용 ~/bin/cloud-mac-stack-runbook.sh 저장:
전체 Runbook · 서브커맨드
day-start · ci-pre · ci-post · night-batch
#!/usr/bin/env bash # cloud-mac-stack-runbook.sh — L2-Q03 standard Runbook set -euo pipefail OLLAMA_HOST="${OLLAMA_HOST:-127.0.0.1:11434}" export OLLAMA_MAX_LOADED_MODELS=1 ensure_ollama() { curl -sf "http://${OLLAMA_HOST}/api/tags" >/dev/null || ollama serve & sleep 2 } ci_pre() { # Before CI: force CI mode, L1 priority ollama ps || true ollama stop qwen3:8b 2>/dev/null || true ollama stop qwen3:14b 2>/dev/null || true sleep 30 } ci_post() { # After CI: restore light embedding (lowest background tier) ensure_ollama ollama run nomic-embed-text --keepalive 10m } day_start() { # Day login / boot: embedding only or full stop ensure_ollama ollama stop qwen3:8b 2>/dev/null || true ollama stop qwen3:14b 2>/dev/null || true ollama run nomic-embed-text --keepalive 30m } night_batch() { # Night batch (cron: 0 22 * * *) ensure_ollama ollama run qwen3:8b --keepalive 6h # ./your-log-summary-or-embed-rebuild.sh } case "${1:-}" in day-start) day_start ;; ci-pre) ci_pre ;; ci-post) ci_post ;; night-batch) night_batch ;; *) echo "Usage: $0 {day-start|ci-pre|ci-post|night-batch}"; exit 1 ;; esac
Memory guard(cron 5분마다 또는 LaunchAgent 권장): 메모리 압력 상승 시 대형 모델 자동 unload——「임계 scheduling」의 최소 구현.
#!/usr/bin/env bash # memory-guard.sh — memory_pressure threshold guard PRESSURE=$(memory_pressure 2>/dev/null | head -1 || true) if echo "$PRESSURE" | grep -qiE 'warn|critical|urgent'; then logger -t cloud-mac-stack "memory guard: stopping Ollama 8B/14B ($PRESSURE)" ollama stop qwen3:8b 2>/dev/null || true ollama stop qwen3:14b 2>/dev/null || true fi # optional: when pressure normal + no Runner job, restore embedding # if echo "$PRESSURE" | grep -qi 'normal'; then ... fi
연결 예:
- GitHub Actions——workflow 첫 step
ci-pre, 마지막ci-post - LaunchAgent——로그인 시
day-start; 22:00 cronnight-batch - cron——
*/5 * * * * /path/memory-guard.sh
16GB Mac mini 스케줄
- 14B 상주 금지; 8B는
night-batch만 - 낮 Runbook은
day-start만(embedding 또는 전체 stop) - CI마다
ci-pre, 예외 없음 - 「데스크톱+8B+Claude Code 동시」가 기본이면 24GB——스케줄로 하드 한계는 못 살림
16GB 요诀: 낮 대형 모델 없음, 밤 단일 모델 단일 작업, CI 전 필수 ci-pre. 24GB 요诀: 낮 embedding 가능, 8B는 CI와 시간 분리, 14B는 밤만.
결정표: 어떤 전략을 쓸지
| 상황 | 권장 |
|---|---|
| 24GB · push 적음 · 주로 Claude Code | day-start + embedding; 야간 배치 없음 |
| 24GB · 매일 CI · 로그 요약 필요 | ci-pre/post + night-batch + memory guard |
| 16GB · 로컬 8B 필수 | night-batch만; 낮은 Claude API |
| Ollama가 일주일 제로 호출 | L2-Q01 로 돌아가 pipeline 정의 |
시리즈 · Cloud Mac AI Stack
L2-Q03 · 메모리 Scheduling 레이어——대외 「Mac mini Swap 회피」; 대내 L2-Q01 프라이빗 Inference 레이어 scheduling 후속:
- L2-Q01 — Inference란(위치)
- L2-Q03 · 본문 — 메모리 Scheduling 레이어(동일 머신 scheduling)
- 예정 — 모델 pin, 11434 헬스체크, CI에서 Ollama 호출
- 하류 — L4 Context + MCP scheduling(다음 L4-Q03)
Ollama 튜토리얼도 순수 CI 최적화 글도 아니라 Apple Silicon AI Workload Scheduler 1층 설계입니다.
기존 글과의 관계
- L2-Q01 · 프라이빗 Inference 레이어——L2 위치; 본문은 scheduling 후속.
- L2-Q02 · 16GB vs 24GB——Swap 수치 출처; benchmark 미반복.
- L1-Q01 · Runner——burst 최우선.
- L3 · Claude Code——interactive 주 경로.
자주 묻는 질문
Mac mini에서 Ollama, Claude Code, GitHub Actions를 동시에 돌리면 Swap이 나나요?
나옵니다. burst/interactive/background 우선순위 없으면 동시 풀가동.§ 진짜 문제·반례.
Ollama를 항상 켜 둬야 하나요?
아닙니다. 로컬 Inference는 schedulable 리소스: 낮 nomic-embed-text만, 대형 모델은 on-demand 또는 야간 배치.
CI 빌드 때 Ollama를 멈춰야 하나요?
권장. 30초 Runbook · CI 전 또는 ci-pre.
Claude Code와 Ollama를 동시에 돌릴 수 있나요?
가능. 코딩 주 경로는 API. 로컬 경합은 모델 가중치·CI 피크. CI 전 stop 또는 시간 분리.
Cloud Mac OOM은 메모리 부족인가요?
관측상 대부분 OOM은 scheduling 문제. 16GB 한계는 L2-Q02 실측.
시간대 스케줄과 memory_pressure 임계값은?
먼저 30초 Runbook; 안정 후 memory-guard.sh(전체 Runbook).
이 글과 L2-Q01 차이는?
Q01은 Inference Service 위치; Q03(Memory Scheduling 레이어)은 동일 머신 AI workload scheduling.
30초 안에 쓸 명령이 있나요?
있습니다. CI 전: ollama stop qwen3:8b && ollama stop qwen3:14b && sleep 30. 낮: ollama run nomic-embed-text --keepalive 30m. 밤: ollama run qwen3:8b.
Cloud Mac AI Stack · 내일 예고
Claude Code + MCP: GitHub / 로컬 파일 / API를 한 체인으로
L2 스케줄이 고정되면 다음은 L4 Context: MCP가 GitHub·저장소·API를 Claude Code 워크플로에 연결.
블로그로 · Cloud Mac AI Stack 전 시리즈