# task-3028 — 유튜브 파이프라인 1회 처리량 제한 (OOM 재발 방지)

**레벨**: Lv.2 · **팀**: dev2-team (오딘) · **회장 승인 완료** (2026-08-26)
**저장소**: `Jeon-Jonghyuk/InsuWiki` (`/home/jay/projects/insuwiki`, 기본 브랜치 **master**)

## ★ 배경 — 오늘 서버가 실제로 죽었다 (실측)
```
09:25  파이프라인 시작 (채널 2, 영상 21건 처리 시도)
10:17  whisper-gpu.service OOM 사망 — 희생 프로세스 anon-rss 6.2 GB
10:34  global oom-killer 재발동 → cokacdir.service 사망 = 봇 4팀 전원 사망
11:23  파이프라인 종료 (처리 11 / 스킵 4 / 실패 6)
12:06  서버 재부팅
```
서버 RAM **15 GB**(swap 4GB). whisper 는 **유휴 시에도 4.7 GB 점유**하고
**10분 미사용 시에만** 모델을 언로드한다. 연속 처리 중에는 언로드 기회가 없어 계속 상주한다.

**회장 지시**: *"처리할 영상이 많을 경우 다운이 되는 것 같다.
한 번에 진행할 영상 처리 갯수 제한을 하는 방안이 필요하다."*

## 현재 설정
```python
# youtube_pipeline/config.py:51
MAX_VIDEOS_PER_CHANNEL = 20     # 채널 2개 → 1회 최대 40건
```

## ★★ 이 태스크가 고칠 것은 "동시 처리량"이지 "총 처리량"이 아니다
OOM 은 누적 처리량이 아니라 **특정 시점의 동시 메모리 사용량** 문제다.
1회 배치를 작게 하고, 남은 영상은 **다음 회차에 이어서** 처리되게 하라.
**영상을 버리지 마라.** 커서 가드(t3021)가 실패분을 보존하고 있으니 그 설계를 깨지 마라.

### ① 1회 처리 상한을 낮춘다
- `MAX_VIDEOS_PER_CHANNEL` 을 낮춘다 (**초기값 5 권장**, 채널 2개 기준 1회 10건)
- ★ 채널이 늘면 총량도 비례해 늘어난다. **채널 수와 무관한 전체 상한**을 함께 두어라
  (예: `MAX_VIDEOS_PER_RUN`). 채널별 상한만으로는 채널이 3개 되면 다시 위험해진다
- 두 값 모두 **환경변수로 override 가능**하게 하라. 서버 사양이 바뀌면 코드 수정 없이 조정해야 한다

### ② 남은 영상이 다음 회차에 처리되는지 보장
- 상한에 걸려 처리하지 못한 영상이 있으면 **커서를 전진시키지 마라**
- 이미 t3021 가드가 "실패 건이 있으면 보류"를 한다. **상한 미처리도 같은 취급**이 되어야 한다
- 로그에 **"상한으로 N건 이월"** 을 남겨라. 조용히 넘어가면 아무도 모른다

### ③ ★ 여유가 되면: 배치 사이 whisper 유휴 확보
whisper 는 10분 idle 이면 스스로 언로드한다. 연속 처리가 이 기회를 막는다.
- 영상 사이에 **짧은 간격**을 두는 것이 메모리 회복에 도움이 되는지 **실측으로 판단**하라
- ★ 근거 없이 sleep 을 넣지 마라. 도움이 되는지 측정하고, 안 되면 넣지 마라

## ★ 하지 말 것
- **총 처리량을 줄이는 방향 금지** — 배치만 작게, 회차로 나눠 처리
- **커서 가드 완화·제거 금지** (t3021 결과물, 라이브에서 발화 확인됨)
- **fail-closed 완화 금지** (전사 없으면 카드 생성 안 함 — 옳은 동작)
- **타이머 주기 변경 금지** — Codex 검토 결과 주기 단축은 whisper 언로드 기회를 줄여
  역효과 가능. 이 태스크 범위 밖이다
- **systemd 유닛 파일 수정 금지** (`MemoryMax` 등은 회장 승인 대상, 별건)
- `summarizer.py` · `firestore_writer.py` · `drive_uploader.py` 로직 변경
- 요약 규격 변경 (내용 기준 4단 구성 · 1,500~2,000자 — 회장 확정)

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "scripts/youtube-pipeline/youtube_pipeline/config.py"
    - "scripts/youtube-pipeline/youtube_pipeline/main.py"
    - "scripts/youtube-pipeline/tests/**"
    - "memory/reports/task-3028.md"
  forbidden_paths:
    - "scripts/youtube-pipeline/youtube_pipeline/summarizer.py"
    - "scripts/youtube-pipeline/youtube_pipeline/firestore_writer.py"
    - "scripts/youtube-pipeline/youtube_pipeline/drive_uploader.py"
    - "scripts/youtube-pipeline/youtube_pipeline/transcriber.py"
    - ".github/workflows/**"
    - "functions/**"
    - "nextapp/**"
  commands: ["bash", "python3", "pytest", "gh"]
  merge_policy: "tiered"
  ttl_hours: 24
```

## 검증 (실측 강제)
1. **상한 동작 실증** — 상한을 넘는 영상이 있는 상황에서 **정확히 상한만큼만** 처리되는지 확인
2. **이월 보장** — 미처리분이 있을 때 **커서가 전진하지 않는지** 실측.
   그리고 다음 회차에 그 영상이 다시 잡히는지 확인(양방향)
3. **채널 증가 시나리오** — 채널이 3개일 때도 전체 상한이 지켜지는지 (테스트로)
4. ★ **메모리 실측** — 수정 전후로 파이프라인 1회 실행 중 **최대 RSS 를 실제로 측정**해 비교하라.
   "줄었을 것이다"가 아니라 **숫자**를 제시하라.
   측정법 예: 실행 중 `ps -o rss= -p <whisper pid>` 를 주기 샘플링하거나 `systemd-cgtop`
   ★ **GPU 메모리만 보지 마라. 오늘 사고는 시스템 RAM 이었다**
5. **환경변수 override 동작** 확인
6. **봉인** — 상한을 무력화하는 변이를 만들어 테스트가 FAIL 하는지 확인·복원.
   변이가 no-op 이 아님을 `assert` 로 먼저 증명하라
7. `pytest` 회귀 유지 (**base 재측정 기준선**. 명세 숫자를 믿지 마라)

## ★ 실행 시 주의 — 서버를 또 죽이지 마라
- 검증용 실행은 **짧은 영상·소수 건**으로 하라. GPU·RAM 을 장시간 점유하지 마라
- 파이프라인 타이머가 **6시간 주기로 살아 있다**(다음 18:16 KST). 타이머 실행과 겹치지 않게 하라
- 실행 전 `free -h` 로 여유를 확인하고, 4GB 미만이면 대기하라

## 완료 조건 (DoD)
**1회 실행에서 처리되는 영상 수가 상한 이내로 제한되고, 미처리분은 커서 보류로 다음 회차에
이어서 처리되며, 수정 전후 최대 RSS 차이가 실측 숫자로 제시된다.**

## ★ 세션 조기 종료 대책
시작 즉시 브랜치 + 드래프트 PR 을 먼저 열어라. 단계마다 PR 본문에 진행을 갱신하라.
★ ANU 명세 전제가 틀렸다고 판단되면 구현하지 말고 **반증을 먼저 기록한 뒤 보고하라.**
(t3018·t3019 에서 봇이 ANU 전제를 반증한 것이 옳았다)

## 운영 계약
- `origin/master`(=`5da890c`) 기준 `git pull --ff-only` 후 시작
- ★ **worktree 를 써라.** 메인 저장소(`/home/jay/projects/insuwiki`)를 체크아웃 점유하지 마라.
  그 경로는 **systemd 러너가 직접 실행**한다. 오늘 InsuRo 에서 실제로 물렸다
- ★ `gh` 호출 시 `GH_TOKEN="$BOT_GITHUB_TOKEN"` 주입 필수 (회장 개인 PAT 금지 — 감사기록 오염)
- 워크플로우 `/home/jay/workspace/prompts/DIRECT-WORKFLOW.md` · QC `/home/jay/workspace/teams/shared/QC-RULES.md`
- `WORKSPACE_ROOT=/home/jay/workspace` · `CHAT_ID=6937032012` · 수집자 key `ANU_KEY=c119085addb0f8b7`
- 완료 경로는 `finish-task.sh` 실행이 유일하다. 수동 `.done` 금지.
- ★ PR Check `validate` 는 이 저장소에서 **상시 적색**이다(`scripts/*.ts` tsc, firebase-admin 미설치).
  변경 파일과 교집합이 없으면 선재로 판정하고 근거를 보고서에 적어라

## 보고
**PR 생성까지가 범위다. 머지는 ANU 가 한다. 자동 머지 금지.**
`memory/reports/task-3028.md` 작성 후 표준 완료 콜백 등록. 콜백 프롬프트 **UTF-8 3900 bytes 이하**.
★ 봉투 첫 줄에 **"상한 동작 실증 여부 + 수정 전후 최대 RSS 숫자"** 를 담아라.