2.5 Autoscaling과 Scale to Zero
Compute가 데이터를 보유하지 않는다는 특성은 branching뿐 아니라 요금 구조에도 영향을 줍니다. 상태를 보유하지 않는 프로세스는 언제든 크기를 바꾸거나 종료해도 됩니다. 이 장에서는 Neon이 compute 크기를 어떻게 정하는지 다룹니다. 또한 언제 compute를 종료해 0으로 만들고, 다시 시작할 때 무엇을 하는지 살펴봅니다. Branch에는 compute가 없을 수도 있습니다. Primary 하나와 read replica 여러 개가 연결될 수도 있습니다. Compute의 크기와 가동 시간이 branch 비용을 결정합니다. 따라서 비용을 예측하려면 이 동작을 이해해야 합니다.
요약
- Compute 크기 단위는 CU(Compute Unit)이며 플랜에 따라 0.25 CU부터 최대 56 CU까지 선택합니다.
- Autoscaling은 CPU, 메모리, 캐시 working set 세 지표에서 각각 필요한 CU를 계산한 뒤 최댓값을 선택합니다.
- Scale to zero는 유휴 상태가 일정 시간 이어지면 compute를 종료합니다. 기본값은 5분이며 Free 플랜에서는 해제할 수 없습니다.
- 재기동 시 로컬 상태를 재구성하지 않습니다. 기존 storage 이력에 다시 연결하기만 합니다.
- Read replica는 같은 Pageserver를 읽는 read-only compute입니다. 데이터 복사가 필요하지 않으며 생성에는 수 초가 걸립니다.
CU와 플랜별 범위
공개 문서에 명시된 플랜별 compute 범위입니다.
| 플랜 | CU 범위 | Autoscaling 최대 | Scale to zero |
|---|---|---|---|
| Free | 0.25~2 CU | 2 CU | 5분, 해제 불가 |
| Launch | 0.25~16 CU | 16 CU | 5분, 해제 가능 |
| Scale | 0.25~56 CU (고정 크기) | 16 CU | 1분부터 상시 가동까지 설정 가능. 16 CU 초과 compute는 항상 가동(2025-12-05 changelog 기준) |
Autoscaling 문서는 예시로 "최소 2 CU(8 GB RAM), 최대 8 CU(32 GB RAM)"을 제시합니다. 이 예시가 1 CU와 4 GB RAM의 대응 비율을 알려 줍니다. 다만 vCPU 대응은 이 문서에 명시되어 있지 않습니다. 따라서 여기서는 RAM 비율만 인용합니다. 요금은 CU-hour 단위로 부과됩니다. Launch는 시간당 0.106달러, Scale은 0.222달러입니다(문서 확인 시점 기준). 비용 계산은 6.1에서 다룹니다.
Autoscaling이 보는 세 지표
Autoscaling 알고리즘 문서에는 세 지표와 목표치가 명시되어 있습니다.
| 지표 | 측정 주기 | 목표 |
|---|---|---|
| CPU load average | 5초 (autoscaler-agent) | 1분 평균 load를 가용 CPU의 90% 이하로 유지 |
| 메모리 사용량 | 5초 (autoscaler-agent), 100 ms (vm-monitor) | 전체 할당 메모리의 75% 이하로 유지 |
| 캐시 working set 크기 | 20초, 1~60분 창 여러 개 | Working set이 compute 캐시(RAM의 최대 75%)에 들어가도록 |
각 지표에 필요한 CU를 따로 계산한 뒤 최댓값을 선택합니다.
goalCU := max(cpuGoalCU, memGoalCU, lfcGoalCU)
세 번째 지표는 Neon 고유의 지표입니다. 2.1에서 살펴본 LFC(Local File Cache)는 compute의 로컬 NVMe에 두는 disk-backed page 캐시입니다. 자주 읽는 page는 OS page cache를 통해 RAM에도 상주합니다. 캐시 크기를 산정하는 기준은 RAM 비율(최대 75%)입니다. 다만 저장 매체는 로컬 디스크입니다. Autoscaling은 최근 1분부터 60분까지 여러 시간 창을 사용합니다. 각 시간 창에서 "얼마나 많은 서로 다른 page를 읽었는가"를 추정합니다. Working set이 LFC에 들어가지 않으면 page 요청이 Pageserver로 전달됩니다. 그 결과 지연이 늘어나므로 CPU에 여유가 있어도 CU를 높입니다. 즉, 캐시 적중률을 높이기 위해 compute 크기를 키웁니다. 로컬 디스크 PostgreSQL에서는 필요하지 않았던 판단입니다.
구성 요소
| 구성 요소 | 역할 |
|---|---|
| NeonVM | Compute PostgreSQL이 실행되는 VM. 실행 중 CPU와 메모리를 바꿀 수 있는 형태 |
| autoscaler-agent | 5초와 20초 주기로 지표를 수집해 목표 CU를 계산하고 VM 크기를 조정 |
| vm-monitor | VM 안에서 100 ms 주기로 PostgreSQL 메모리를 감시. 급격한 메모리 증가를 빠르게 감지 |
메모리 지표를 두 주기로 확인하는 이유는 대응 속도입니다. 5초 주기만으로는 큰 정렬이나 hash join이 메모리를 급격히 늘릴 때 OOM을 방지하기 어렵습니다. vm-monitor는 100 ms 단위로 감시해 먼저 대응합니다.
Scale to Zero
유휴 상태가 설정 시간 이상 이어지면 compute를 종료합니다. 유휴 상태란 연결과 활동이 모두 없는 상태를 뜻합니다. 기본값은 5분입니다. Free 플랜에서는 이 값을 변경할 수 없습니다. Launch에서는 해제가 가능합니다. Scale에서는 1분부터 상시 가동까지 조절할 수 있습니다.
종료된 compute에는 상태가 남지 않습니다. 다음 연결이 들어오면 새 compute가 시작되어 같은 timeline에 연결됩니다. 이때 수행하는 작업은 2.1에서 살펴본 기동 절차와 같습니다. Pageserver가 만든 basebackup tarball을 받습니다. 이후 neon.signal의 LSN에서 recovery 없이 시작합니다. 로컬 데이터 파일을 다시 만들거나 WAL을 재생하는 단계가 없습니다. 따라서 기동 시간은 데이터 크기와 무관합니다.
다만 캐시는 비어 있습니다. shared_buffers와 LFC가 모두 빈 상태로 시작합니다. 따라서 첫 질의들은 Pageserver와의 왕복을 거칩니다. 콜드스타트에 걸리는 시간은 이 노트가 참고한 문서에 명시되어 있지 않아 적지 않습니다. 첫 연결 지연과 캐시 워밍 지연을 구분해 측정하는 방법은 6.2에서 다룹니다.
Branch와 compute의 관계
Branch(timeline)와 compute(endpoint)는 별개의 자원입니다.
- Branch를 만들 때 compute를 함께 연결하거나 연결하지 않을 수 있습니다(CLI의
--no-compute). - Compute가 없는 branch는 storage만 차지하며 compute 요금이 없습니다.
- Compute가 연결된 branch도 유휴 시 scale to zero로 종료되면 그동안 compute 요금이 없습니다.
- 한 branch에는 read-write compute 하나와 read-only compute 여러 개가 붙습니다.
따라서 CI에서 PR마다 branch를 만들더라도 compute 비용은 "테스트가 실제로 돌아간 시간 x CU"에 가깝습니다. 여기에 플랜의 branch 포함량을 초과한 branch의 요금이 추가됩니다. Storage와 history 보존 비용도 더해집니다. 전체 계산은 6.1에서 다룹니다. 대신 테스트를 시작할 때마다 콜드스타트가 발생합니다. 이 trade-off는 4.2에서 다룹니다.
Read Replica
Read replica 문서는 이를 "primary compute와 같은 storage에서 요청을 처리하는 독립적인 read-only compute"로 정의합니다. 일반 PostgreSQL의 streaming replica처럼 데이터를 복사해 두지 않습니다. 같은 Pageserver에서 page를 읽습니다. 따라서 생성할 때 데이터 전송이 없으며 수 초면 완료됩니다.
최신 변경 사항은 Safekeeper를 통해 전달됩니다. Safekeeper는 Primary가 쓴 WAL을 durable하게 저장합니다. 동시에 WAL을 read replica에도 전달합니다. Replica는 이를 통해 어느 LSN까지 읽어도 되는지 파악합니다. 문서는 이를 비동기, eventual consistency로 설명합니다. Primary의 commit이 replica에 보이기까지 짧은 지연이 발생합니다.
| 항목 | 내용 |
|---|---|
| 데이터 복사 | 없음. 같은 Pageserver 읽기 |
| 지역 | 같은 region 안에서만. 다른 region은 별도 project와 logical replication |
| Free 플랜 제한 | project당 read replica compute 3개 |
| Autoscaling, scale to zero | 지원 |
| 요금 | 별도 compute로 과금. Storage 추가 비용 없음 |
Read replica도 compute이므로 autoscaling과 scale to zero가 적용됩니다. 분석 질의를 replica로 보내면 primary의 캐시 영향이 줄어듭니다. 이때 replica의 LFC working set은 primary와 별도로 계산됩니다.
실패 사례
Scale to zero가 활성화된 branch에 연결 pool을 두고 keepalive를 짧게 설정하면 compute가 종료되지 않습니다. 반대로 pool이 없고 요청이 드물면 요청마다 콜드스타트가 발생합니다. 서비스 특성에 따라 둘 중 하나를 선택해야 합니다. "가끔 쓰는데 첫 응답은 빨라야 한다"는 요구는 scale to zero를 해제하는 유료 플랜 구성으로 해결합니다. Launch 이상에서 suspend timeout을 비활성화하면 됩니다. 이 경우 compute 요금이 계속 발생합니다.
Autoscaling 상한을 낮게 둔 상태에서 working set이 커지는 경우가 있습니다. 이 경우 CPU 사용량이 낮아도 LFC miss가 늘어나 질의가 느려집니다. CPU 그래프만으로는 원인을 찾기 어렵습니다. LFC 적중률과 Pageserver 요청 수를 함께 확인해야 합니다.
연습 문제
- 다음 측정값에서 goalCU를 구합니다. CPU 기준 3 CU, 메모리 기준 2 CU, working set 기준 5 CU.
- 하루 중 8시간만 사용하는 개발용 branch가 1 CU로 동작한다고 가정하고, scale to zero가 있을 때와 없을 때의 월간 CU-hour를 비교합니다.
- Read replica와 일반 PostgreSQL streaming replica의 생성 절차를 각각 세 단계로 적고, 어느 단계가 사라졌는지 표시합니다.
심화 체크
- Working set 지표가 CPU와 메모리 지표와 별도로 필요한 이유를 설명할 수 있는가?
- Scale to zero 후 재기동이 데이터 크기와 무관한 이유를 storage 구조로 설명할 수 있는가?
- Read replica의 지연은 어느 구성 요소 사이에서 발생하는가?