Stream Autoscaling
“지표 선택이 알고리즘 정교함보다 중요하다. 알고리즘을 튜닝하기 전에 지표를 이해하라.”
핵심 개념
배치 잡은 끝나면 리소스를 반납한다. 스트리밍 잡은 영원히 돌기 때문에 프로비저닝 오류가 영원히 청구된다.
- 피크에 맞춰 프로비저닝 → 야간·주말 내내 낭비
- 평균에 맞춰 프로비저닝 → 급증 시 consumer lag 폭증
그래서 오토스케일링이 필수인데, 스트리밍에서 스케일링 액션은 공짜가 아니다. Flink 기준 기본 절차는 savepoint를 뜨고 잡을 우아하게 멈추고 새 parallelism으로 재시작하는 것이라 테라바이트 상태를 가진 잡은 몇 분이 걸린다. 즉 오토스케일러는 “자주 조금씩”이 아니라 “가끔 정확하게” 를 목표해야 한다.
두 가지 접근 — 밖에서 보기 vs 안에서 추론하기
1세대: 밖에서 보기 (external metrics)
컨테이너·인프라 지표를 텔레메트리 플랫폼에서 소비해 스케일링을 결정한다. Netflix의 1세대 오토스케일러(2019)는 CPU·네트워크·Kafka lag·input rate·consume rate를 Atlas에서 받아, lag 유래 catch-up 시간 + 활용률 임계 + 성능 이력 + input rate 회귀를 결합했다.
장점
- Flink 플랫폼과 독립적으로 동작 — Flink 자체 문제에 영향받지 않는다
- 스트리밍 잡 형태로 만들면 확장이 공짜다 — 각 노드가 잡 부분집합의 지표를 처리하고 커스텀 샤딩이나 조율 로직이 전혀 필요 없다
- 수천 개 파이프라인에서 리소스 25~45% 절감
천장
- 거친 컨테이너 지표로 클러스터 전체를 하나로 추론하고, 손잡이 하나(총 TaskManager 수)만 돌린다 → 잡의 모든 연산자가 함께 움직인다
- 단일 연산자 파이프라인에는 맞지만, 분기·조인·큰 상태를 가진 다중 연산자 stateful DAG에는 맞지 않는다. 지원하려면 일반적 능력이 아니라 커스텀 로직이 계속 붙는다
- 오토스케일러는 그 아래 외부 시스템이 주는 지표만큼만 좋다. 잡이 완전히 바쁜데 그것이 CPU 활용률로 전혀 나타나지 않으면 스케일러가 볼 방법 없이 저하된 상태에 갇힌다. 네트워킹 마이그레이션이 트래픽 보고 방식을 조용히 바꿔 지표가 정확하기를 멈춘 사례가 실제로 있었고, 격차가 훨씬 나중에 프로덕션에서 표면화될 때까지 보이지 않았다
2세대: 안에서 추론하기 (true processing rate)
Apache Flink Autoscaler의 접근. 컨테이너를 밖에서 보는 대신 잡 내부에서 각 연산자를 추론한다.
핵심은 TPR(true processing rate) — 이 연산자가 완전히 바쁘다면 지속할 수 있을 처리량.
Flink는 subtask별로 매 초 중 실제 작업에 쓴 비율(busy time) 을 backpressure·idle 시간과 분리해 보고한다. 따라서:
TPR = 관측된 처리량 / busy 비율
예) 70% 바쁘면서 700 rec/s 처리 → TPR = 700 / 0.7 = 1,000 rec/s
그다음 source에서 시작해 job graph를 걸으며 각 연산자의 TPR·입출력 비율·목표 활용률로 어떤 연산자도 병목이 되지 않도록 vertex별 parallelism을 계산한다.
결정적 차이 두 가지
- stateful 다중 연산자 잡을 정확히 스케일할 수 있다 (1세대가 못 하던 것)
- 잡마다 자기 설정(stabilization period, 임계, 스케일링 동작)을 워크로드에 맞게 지닐 수 있다
비교
| 축 | 밖에서 보기 | 안에서 추론하기 |
|---|---|---|
| 신호 | CPU·네트워크·lag (인프라) | busy time·처리량 (엔진 내부) |
| 스케일 단위 | 클러스터 전체 (TaskManager 수) | vertex별 parallelism |
| stateful 다중 연산자 DAG | ✗ | ✅ |
| 엔진 장애 격리 | ✅ 독립적 | ✗ JobManager 의존 |
| 지표 drift 위험 | 높음 (외부 시스템이 조용히 바뀜) | 낮음 (엔진이 직접 보고) |
| 잡별 튜닝 | 어려움 | 쉬움 |
둘은 배타적이지 않다. Netflix는 관리형 파이프라인 대량에는 1세대를, 커스텀 stateful 잡에는 2세대를 쓴다.
프로덕션에서 돌리기 — 알고리즘이 아니라 운영이 일
“알고리즘 채택은 쉬웠고, 안정적으로 돌리는 게 일이었다.”
라이브러리로 분리된 코어
OSS 오토스케일러는 원래 Flink Kubernetes Operator 안에 있도록 설계되었지만, 자체 control plane을 쓰는 조직은 그것을 못 쓴다. 커뮤니티가 핵심 로직을 독립 라이브러리로 유지하는 훌륭한 결정을 내려 4개 인터페이스로 리팩터링했다:
| 인터페이스 | 역할 |
|---|---|
| context | 잡 메타데이터·REST API 정보를 나른다 |
| state store | 지표 윈도우·이력 보관 |
| event handler | 결정 이벤트 발행 |
| realizer | 스케일링 결정을 실제로 적용 |
→ 어떤 사내 생태계에도 플러그인 가능. 오케스트레이션 프레임워크와 알고리즘의 분리가 채택을 가능하게 한 설계다.
workflow-per-job — blast radius 격리
Netflix는 orchestrator를 Temporal(durable 워크플로 엔진) 위 Spring Boot 앱으로 만들었다. 약 1분마다 control plane을 폴링해 오토스케일링이 켜진 잡을 찾고, 잡당 하나의 장기 워크플로를 시작한다.
이것은 고통에 대한 직접적 대응이었다. 처음에는 전체 잡 집합에 대한 단일 배치 루프로 평가했는데 취약했다 — 느리거나 오작동하는 잡 하나가 뒤의 모든 잡에 대한 지표 수집과 스케일링을 stall시켰다. 잡마다 durable 워크플로를 주니 문제 잡 하나가 혼자 실패하고 혼자 재시도하며, 온보딩할수록 런타임이 선형 확장된다. → Durable Execution
”커뮤니티에서 동작” vs “대규모에서 동작” — 세 가지 격차
| 격차 | 문제 | 해법 |
|---|---|---|
| 고 parallelism 지표 수집 | 큰 잡에서 JobManager 지표 fetch가 병목. 약 1,000 subtask 위에서 고전 | JobManager가 일시적 지표 이름을 캐시(매 fetch마다 재스캔하지 않음) + 서버 측 필터링으로 필요한 지표만 요청 → 최대 3,000 subtask 잡까지 동작 |
| forward chaining 보존 | forward 연결된 두 vertex는 레코드가 고정 로컬 채널로 메모리 전달되므로 같은 parallelism이어야 한다. 하나만 스케일하면 Flink가 실패하지 않고 조용히 그 엣지를 네트워크 셔플로 바꾼다 | forward 연결된 서브그래프를 탐지해 각각을 단위로 스케일 |
| sink 한계 | 일부 sink는 쓰기 용량이 유한 | async-sink backpressure 탐지 — 더 흡수 못 하는 sink로는 스케일 업하지 않는다 |
realizer의 안전 체크
실행 직전에 도는 가드:
- 전사 리전 failover 중 evacuate되는 리전의 잡은 스케일 다운 거부
- 새 클러스터가 잡의 체크포인트 상태를 담을 디스크가 충분한지 검증
- 더 큰 클러스터에는 작은 standby 버퍼 추가
스케일 다운의 비대칭성
스케일 업 실수는 돈을 쓰고, 스케일 다운 실수는 서비스를 죽인다.
너무 깊게 자르면 CPU가 포화되고 lag이 급증하는데, 지표 윈도우와 stabilization period가 재시작마다 다시 쌓여야 해서 시스템이 즉시 반응할 수 없다. 스스로 판 구덩이에서 빠져나오는 데 몇 사이클이 걸린다.
Netflix의 대응: 커뮤니티 기본값 0.7보다 낮은 목표 활용률 0.45 — 의도적으로 약간의 효율을 안정성과 교환한다. 큰 stateful 잡에는 적고 차분한 rescale이 marginal 비용만큼의 가치가 있다.
남은 근본 비용 — 상태 복구
가장 큰 비용은 스케일러 로직이 아니라 재시작과 상태 복구 과정 자체다.
스케일링 비용이 상태 크기에 비례하는 한, 오토스케일러는 언제나 보수적일 수밖에 없다. Flink 2의 disaggregated state는 상태를 로컬 디스크가 아니라 외부 스토리지에 두어 rescale·복구가 총 상태 크기에 얼마나 의존하는지를 크게 줄인다 — Disaggregated OLTP Architecture와 같은 방향의 움직임이다.
절감 효과와 그 출처
Netflix 클라이언트 텔레메트리·로깅 팀: 연환산 Flink 컴퓨트 지출 58% 감소, 연 약 $110만 절약. 세 요인:
- 동적 적응 — 정적 프로비저닝은 항상 피크를 감안해야 하지만 오토스케일링은 일일 주기를 따라가 야간·주말 하락을 포착
- 지속적 최적화 — 성능 개선이나 연휴 후 둔화 뒤에 팀이 수동으로 리소스를 최적화하기를 기대하는 대신 오토스케일러가 계속 조정
- 균일한 컨테이너 차원 — 더 나은 bin-packing과 더 세밀한 스케일링 증분
세 번째가 종종 간과되는데, 오토스케일링 도입이 컨테이너 형상 표준화를 강제해서 생기는 부수 효과다.
일반화되는 교훈
- 지표 선택 > 알고리즘 정교함. 가장 유용한 디버깅은 좀처럼 스케일링 수학에 대한 것이 아니라 어느 신호를 가장 신뢰할지에 대한 것이었다
- 합리적 기본값 + 잡별 오버라이드. 관리형 잡은 충분히 비슷해서 좋은 기본값 하나가 대부분을 손대지 않고 커버하고 그것이 플랫폼의 요점이다. 하지만 모든 잡에 단일 설정을 강요하면 맞지 않는 잡을 벌한다. 깊은 전문성이 필요한 손잡이는 의도적으로 숨긴다 — 대부분의 팀은 오토스케일러를 생각할 필요조차 없어야 한다
- 채택한 다음 확장하라(adopt, then extend). 사내에 만든 시스템이 있고 강한 커뮤니티 프로젝트가 나타났을 때 올바른 수는 투자를 영원히 방어하는 것도, 하룻밤에 뜯어내는 것도 아니라, 새 워크로드에 채택하고 수정을 기여하고 의도적 마이그레이션을 계획하는 것
연관 개념
- Real-Time Stream Processing — Flink 상태·체크포인팅의 기초
- Data Engineering FinOps — 오토스케일링이 만드는 절감의 구조
- Spot Instance Management — 다른 축의 컴퓨트 비용 최적화
- Durable Execution — workflow-per-job의 기반
- Distributed Systems Reliability — blast radius 격리, 안전 가드
- Open Source Data Stack — 사내 시스템 → OSS 채택 경로
- Disaggregated OLTP Architecture — disaggregated state의 방향성
- Self-Healing Pipelines
Source: A Tale of Two Flink Autoscalers