용량 계획·변경
복원 여유를 계산하고 지원되는 자원 크기를 바꾼다
무엇을 하게 되나
동시 세션 수와 worker 메모리·노드 수를 계획하고 지원되는 값을 변경한다. transcript와 성과물의 고정 상한은 저장소 계약이 정본이다.
준비물
실제 노드 allocatable·다른 Pod requests·slot limit·workspace 사용량을 확인한다. transcript 크기와 compaction 여부에 따라 복원 peak가 다르므로 평균 사용량만으로 크기를 줄이지 않는다.
단계
1. 복원 비용과 안전 여유
기준: compaction이 없는 transcript를 resume할 때의 최악 peak이 worker 메모리 한도의 절반 이하여야 한다. 나머지 절반은 첫 turn의 작업, 측정하지 않은 transcript 모양, 엔진 버전 차이에 쓰는 여유다.
개발용 .env.example은 WORKER_MEMORY_MB=2048을 둔다. 그대로 쓰면 위의 절반 기준이 지켜지지 않는다. 큰 transcript를 resume하는 시험에서는 그 줄을 지우거나 WORKER_MEMORY_MB=4608로 바꾼다. 실제 배포 입력에서 적용되는 값도 함께 확인한다.
측정 방법
비교 조건은 fake model·cgroup v2 Compose 환경이다. source worker가 합성 transcript를 mirror에 넣고 checkpoint한 뒤 pause한다. 이어서 새 worker가 그 checkpoint를 복원하고 첫 resume turn을 돈다. 그동안 0.5초마다 worker 컨테이너의 cgroup(memory.peak, anon, file, shmem, swap, oom_kill)과 프로세스별 VmHWM을 기록했다. memory.peak에는 회수 가능한 page cache(file)도 들어간다. 그래도 기준에는 포함했다. worker 이미지는 claude-agent-sdk 0.3.270, Claude Code CLI 2.1.270이다.
4608MiB 한도 (현재 기본값)
| transcript | compaction | 첫 resume turn | memory.peak MiB (한도 대비) | anon | file | swap | OOM | worker(bun) VmHWM | CLI VmHWM |
|---|---|---|---|---|---|---|---|---|---|
| 511MiB | 없음 | 성공 | 1970 (43%) | 1054 | 710 | 0 | 0 | 546 | 1129 |
| 511MiB | 있음 | 성공 | 1338 (29%) | 533 | 716 | 0 | 0 | 588 | 237 |
| 64MiB | 없음 | 성공 | 726 (16%) | 558 | 75 | 0 | 0 | 504 | 329 |
6144MiB 한도 (비교 측정)
| transcript | compaction | 첫 resume turn | memory.peak MiB (한도 대비) | anon | file | swap | worker(bun) VmHWM | CLI VmHWM |
|---|---|---|---|---|---|---|---|---|
| 600.0MiB | 없음 | 성공 | 2178 (35%) | 1301 | 745 | 0 | 626 | 1308 |
| 600.1MiB | 있음 | 성공 | 1029 (17%) | 453 | 609 | 0 | 497 | 236 |
| 799.4MiB | 없음 | 성공 | 2785 (45%) | 1669 | 852 | 0 | 597 | 1727 |
| 799.1MiB | 있음 | 성공 | 1392 (23%) | 671 | 996 | 0 | 716 | 234 |
| 64.9MiB | 없음 | 성공 | 839 (14%) | 508 | 279 | 0 | 437 | 326 |
| 150.2MiB | 없음 | 성공 | 828 (13%) | 501 | 151 | 0 | 487 | 492 |
읽는 법
- worker(bun)는 transcript 크기와 상관없이 440–720MiB를 쓴다. restore가 part 단위로 스트리밍하기 때문이다.
- 크기에 비례하는 몫은 엔진(CLI)이다. compaction이 없으면 엔진은 대략 210 + 1.85 × T MiB를 쓴다(T는 transcript MiB). 511MiB에서 공식은 1155MiB이고, 실측은 1129MiB였다. compaction이 있으면 엔진 몫은 크기와 상관없이 약 235MiB다.
- 전체 peak는 page cache를 더해 150–800MiB 구간에서 transcript 1MiB당 약 3MiB씩 는다(150.2MiB에서 828MiB, 600MiB에서 2178MiB, 800MiB에서 2785MiB). 아래 표의 256·400MiB 값은 이 직선으로 보간한 추정이다.
2. transcript 상한과 메모리 선택
후보 상한마다 필요한 메모리와 노드당 세션 수를 비교했다. 메모리는 2 × peak를 512MiB 단위로 올린 값이다. 세션 수는 allocatable 30GiB(30720MiB) 노드 기준이다. 표본이 작아 이 표만으로 운영 환경의 상한을 결정하지 않는다.
| 상한 | compaction 없는 peak | 절반 기준 메모리 | 노드당 세션 | 분포 중 상한 안 | 80% 경고 |
|---|---|---|---|---|---|
| 256MiB | 1146MiB (추정) | 2560MiB | 12 | 10/12 | 204.8MiB |
| 400MiB | 1578MiB (추정) | 3584MiB | 8 | 10/12 | 320MiB |
| 512MiB | 1970MiB (실측) | 4096MiB → 4608MiB | 6 | 11/12 | 409.6MiB |
| 600MiB | 2178MiB (실측) | 4608MiB | 6 | 12/12 | 480MiB |
| 800MiB | 2785MiB (실측) | 5632MiB | 5 | 12/12 | 640MiB |
- 512MiB는 관측한 12개 중 11개를 담는다. 둘째로 큰 400.1MiB 세션도 경고에 걸리지 않는다. 넘는 것은 591.8MiB 하나다. 이런 세션은 409.6MiB에서 경고를 받고 새 세션으로 옮긴다.
- 400MiB면 400.1MiB 세션의 checkpoint가 바로 거절된다. 256MiB면 217MiB 세션도 경고 구간에 들어간다.
- 메모리는 4096MiB가 아니라 4608MiB다. 512MiB의 실측 peak 1970MiB는 4096MiB의 절반(2048)에 78MiB밖에 남지 않는다. 4608MiB면 여유가 334MiB다.
3. 노드당 동시 세션 수
sessions per node = floor((node allocatable memory - sum of other Pod requests on the node) / worker.memoryMb)worker 전용 노드 그룹(worker.nodeSelector, worker.tolerations)을 쓰면 다른 Pod 몫은 DaemonSet 정도다. 30GiB allocatable 노드에서 DaemonSet 몫을 0으로 보면 다음과 같다.
worker.memoryMb | 노드당 세션 |
|---|---|
| 2048 | 15 |
| 4608 (현재) | 6 |
| 6144 | 5 |
CPU도 같은 식으로 센다(worker.cpus, 기본 1). 보통은 메모리가 먼저 찬다. 세션마다 workspace PVC(worker.workspace.quotaMb, 기본 4096MiB)도 하나씩 붙는다. 복원된 transcript(최대 512MiB)는 작업 트리, git objects, restore spool과 함께 이 안에 든다.
local Docker 실행 기반에서는 한 머신의 메모리를 EXECUTION_SLOT_LIMIT(기본 10)개 worker가 나눠 쓴다. 한도는 cgroup 상한이지 예약이 아니라서 동시에 다 차지 않으면 넘쳐도 돈다. 그래도 slot 수 × 4608MiB가 머신 메모리를 크게 넘으면 OOM이 난다.
지원되는 변경 선택
| 방법 | 하는 일 | 얻는 것 | 대가 |
|---|---|---|---|
① worker.memoryMb 낮추기 | values 값 하나만 바꾼다(WORKER_MEMORY_MB, 다음 launch부터 적용) | 노드당 세션이 는다. 예를 들어 3584MiB면 30GiB 노드에 8개다 | compaction 없는 큰 transcript를 resume할 여유가 같이 준다. 절반 기준을 지키려면 상한도 같이 내려야 한다(3584MiB면 상한 약 400MiB, 추정). 상한은 코드 상수라 빌드가 필요하다 |
② (현재 미지원) requests를 limits보다 작게 하는 오버커밋 | chart의 worker Pod requests를 limits보다 작게 둔다 | 작은 세션이 많을 때 노드당 세션이 크게 는다. 실측에서 64–150MiB transcript의 resume peak는 726–839MiB였다 | 지금 chart와 scheduler는 requests = limits로만 Pod를 만든다. 바꾸려면 코드 변경이 필요하다. QoS가 Burstable로 내려가, 큰 세션 몇 개가 동시에 resume하면 노드 메모리 압박으로 OOM kill이나 eviction이 난다 |
| ③ 노드 그룹 키우기 | worker 노드 수나 노드 크기를 늘린다 | 세션당 보장은 그대로 두고 총 세션 수를 늘린다 | 비용이 는다. 코드 변경은 없다 |
| ④ (현재 미지원) 세션 크기별 Pod 크기 | 복원할 transcript 크기를 보고 scheduler가 Pod 메모리를 고른다 | 작은 세션은 작게, 큰 세션만 크게 띄운다 | 코드 변경이 필요하다. scheduler가 launch 전에 checkpoint 크기를 알아야 하고, 실행 중에 같은 Pod 안에서 transcript가 커지는 경우를 따로 다뤄야 한다 |
상한(MAX_TRANSCRIPT_BYTES)만 올리고 메모리를 그대로 두는 것은 권하지 않는다. compaction 없는 큰 transcript의 resume이 한도에 닿으면 worker가 재기동을 반복하다 세션이 recovery_required가 된다. 상한을 바꿀 때는 이 문서의 방법으로 다시 잰다.
메모리 값은 다음 launch부터 적용되고 실행 중 worker는 교체되지 않는다. 상한을 바꾸는 직접 빌드는 API와 worker의 같은 릴리스가 필요하므로 업그레이드 게이트를 따른다.
API만 더 작은 transcript 상한을 쓰면 worker에서 상한 안인 checkpoint도 finalize가 거절하고 transcript_too_large를 기록해 새 turn을 막을 수 있다. API와 worker의 상한을 함께 맞춘다. 새 transcript reason을 모르는 옛 API는 보고를 400으로 거절하므로 migration → API 모든 인스턴스 → worker 순서를 지키고, 그 reason을 아는 빌드까지만 rollback한다.
4. provider 비용과 상한 대조
사용량과 비용 계측의 추정·fallback 단가·재인가 경계를 보고 실제 provider 청구와 비교한다. 설치 상한을 바꾸고 적용할 때는 모델 연결과 backend별 업그레이드 절차를 따른다.
확인
변경 전후 동일한 checkpoint를 새 worker에서 복원해 turn 완료·memory.peak·OOM·디스크·inode·queue wait를 비교한다. 표의 숫자는 기재한 SDK·CLI·합성 transcript 조건에서만 관측됐으며 운영 환경·새 runtime의 실측을 대신하지 않는다.
자주 막히는 곳
transcript 상한만 올리고 메모리를 유지하면 재기동 후 recovery_required가 될 수 있다. requests를 limits보다 작게 하는 오버커밋과 세션 크기별 Pod 크기는 현재 지원하지 않는다.
되돌리기
새 launch에 적용할 메모리·slot·노드 수를 이전 값으로 되돌린다. 이미 만들어진 workspace의 byte quota를 임의로 바꾸거나 volume을 지우지 않는다. quota 계약 변경은 workspace 이전을 따른다.