오토스케일링은 트래픽 급증을 못 막는다 — 몇 분을 버티는 법
방송에 소개된 날 트래픽이 1분 만에 열 배가 됐어요. 오토스케일은 제대로 움직였지만 서버가 늘기까지 몇 분 동안 서비스는 죽어 있었고요. 그 몇 분을 버티는 준비를 알 수 있어요.
오토스케일을 켜 뒀어요. CPU 가 70% 를 넘으면 인스턴스를 늘리도록요.
방송에 소개된 날, 트래픽이 1분 만에 열 배가 됐어요. 오토스케일은 정상적으로 동작했어요. 감지하고, 인스턴스를 늘리고, 몇 분 뒤 충분한 대수가 떴어요.
그런데 그 몇 분 동안 서비스는 완전히 죽어 있었어요. 그리고 새 인스턴스가 뜬 뒤에도 한동안 불안정했고요.
오토스케일은 분 단위로 반응하는 장치예요. 초 단위로 오는 급증에는 구조적으로 늦을 수밖에 없어요.
설정이 틀린 게 아니에요. 도구의 성격이 그렇고, 그걸 모르면 잘못된 안심을 하게 됩니다.
왜 그런가
오토스케일이 반응하는 데 걸리는 시간을 나눠 보면 이래요.
먼저 지표를 수집하고 판단해야 해요. 대개 1~2분 주기로 보고, 여러 주기 연속으로 조건을 만족해야 발동해요. 순간적인 튐에 반응하지 않으려는 안전장치인데, 그만큼 늦어져요.
그다음 인스턴스를 요청하고 받아요. 클라우드가 할당하는 데 수십 초가 걸려요.
그리고 부팅하고 애플리케이션이 뜨고 준비가 돼야 해요. 이게 대개 가장 길어요. 컨테이너 이미지를 받고, 런타임이 시작하고, 의존성에 연결하고, 캐시를 채우고요.
다 합치면 빨라야 2~3분이에요. 잘 튜닝해도 1분 아래로 내리기 어렵습니다.
그런데 급증은 초 단위로 와요. 방송, 알림 발송, 티켓 오픈, 그리고 앞 편에서 본 재시도 폭주까지요. 이런 상황에서 오토스케일은 도착했을 때 이미 상황이 끝나 있거나, 최악의 순간을 놓친 뒤예요.
그래서 오토스케일은 점진적인 부하 변화를 따라가는 장치로 봐야 해요. 급증 대응은 다른 수단이 필요합니다.
그리고 이 구분을 안 하면 "오토스케일을 켜 뒀으니 괜찮다"는 잘못된 안심을 하게 돼요. 실제로는 가장 위험한 순간에 아무 보호도 없는 상태입니다.
코드로 보면
급증은 다른 장치로 막는다:
· 오토스케일이 도착할 때까지 버티는 게 목적이다
· 1) 여유 용량을 미리 둔다
평시 사용률을 50~60% 로 유지
90% 로 꽉 채우면 급증에 즉사한다
· 2) 예측 가능한 급증은 미리 늘린다
방송·이벤트·알림 발송 시각을 알면 사전 확장
· 3) 큐로 첫 파도를 평탄화한다
동기 처리를 비동기로 바꿔 순간 부하를 시간에 펼친다
· 4) 넘치면 흘려보낸다
앞 편의 부하 흘리기가 마지막 방어선이다
· 5) 기동 시간을 줄인다
이미지 경량화 · 미리 당겨받기 · 준비 검사 최적화
기동이 3분이면 오토스케일은 3분 늦다✕ 문제 지점 · ✓ 해법 · 그대로 돌려 보기 전에 자기 환경에 맞게 고쳐 주세요.
실전에서는
대응은 "오토스케일이 도착할 때까지 버티는 것"으로 설계해야 해요.
여유 용량을 두는 게 가장 직접적이에요. 평시 사용률을 낮게 유지하면 급증의 초반을 흡수할 수 있어요. 비용이 들지만 그게 보험료예요. 90% 로 꽉 채워 운영하면 조금만 늘어도 즉사합니다.
예측 가능한 급증은 미리 늘리면 돼요. 방송 시각, 이벤트 시작, 푸시 알림 발송처럼 아는 일정이 있으면 사전에 확장해요. 사실 대부분의 큰 급증은 예측 가능해요.
큐로 평탄화하는 것도 강력해요. 동기로 처리하던 걸 비동기로 바꾸면, 순간에 몰린 부하를 시간에 펼칠 수 있어요. 사용자는 접수 확인만 즉시 받고 실제 처리는 나중에 되는 거죠. 모든 기능에 쓸 수 있는 건 아니지만 쓸 수 있는 곳에서는 효과가 큽니다.
그리고 마지막 방어선이 부하 흘리기예요. 어떤 준비를 해도 넘는 순간이 오는데, 그때 전부 받아서 다 같이 죽는 것보다 일부를 거절하는 게 낫습니다.
기동 시간을 줄이는 것도 직접적인 개선이에요. 기동이 3분이면 오토스케일은 구조적으로 3분 늦어요. 이미지를 가볍게 하고, 노드에 미리 받아 두고, 준비 검사를 최적화하면 이 시간이 줄어요.
축소 쪽도 생각해야 해요. 급증이 끝났다고 바로 줄이면, 다음 파도가 왔을 때 다시 없어요. 축소는 확장보다 훨씬 천천히 하는 게 원칙입니다.
그리고 오토스케일 기준을 CPU 로만 잡는 게 항상 맞지는 않아요. 대기 큐 길이나 동시 처리 수가 더 빨리 반응하는 지표일 때가 많습니다.
처방
- 평시 여유 용량을 두세요. 사용률을 50~60% 로 유지하는 게 급증에 대한 보험입니다.
- 예측 가능한 급증은 미리 확장하세요. 큰 급증의 대부분은 예측 가능합니다.
- 큐로 평탄화할 수 있는 기능을 찾으세요. 동기를 비동기로 바꾸면 부하가 시간에 펼쳐집니다.
- 부하 흘리기를 마지막 방어선으로 두세요. 어떤 준비를 해도 넘는 순간은 옵니다.
- 기동 시간을 재고 줄이세요. 기동이 오래 걸리면 오토스케일이 그만큼 늦습니다.
- 축소를 천천히 하세요. 확장보다 훨씬 보수적으로요.
- 오토스케일 지표를 다시 보세요. 대기 큐 길이나 동시 처리 수가 CPU 보다 빨리 반응합니다.
- 급증 상황을 실제로 시험하세요. 부하를 계단식이 아니라 순간적으로 올리는 시나리오가 필요합니다.
- 계정·네트워크 한도를 미리 확인하고 올려 두세요. 급증 한가운데서 인스턴스 한도에 걸리면 확장 자체가 안 됩니다.
지금 확인해 보세요
오토스케일이 얼마나 늦는가:
· 반응 시간을 단계별로 잰다
지표 감지 → 판단 → 인스턴스 할당 → 기동 → 준비 완료
각 단계의 시간을 알아야 어디를 줄일지 안다
· 평시 사용률
80% 를 넘게 운영하면 급증에 버틸 여유가 없다
· 급증 시험 — 계단이 아니라 순간적으로 올린다
부하 테스트가 서서히 올리는 방식이면 이 문제를 못 잡는다
· 확장했을 때 병목이 옮겨 가는지
앱을 늘렸는데 DB 커넥션만 늘고 처리량은 그대로면
병목은 다른 곳이다
· 계정·네트워크 한도 확인
인스턴스 한도 · 서브넷 IP · 이미지 저장소 요청 제한핵심 한 줄오토스케일은 분 단위다. 급증은 초 단위로 온다.
큐로 첫 파도를 평탄화하라
이벤트 시작 시각에 트래픽이 몰려요. 몇 초 만에 평소의 수십 배가 되고, 그 상태가 1~2분 지속돼요.
용량을 그 순간에 맞춰 늘리면 평소에는 대부분이 놀아요. 하루 1~2분을 위해 스물세 시간을 낭비하는 셈이라 비용이 감당이 안 돼요.
그렇다고 안 늘리면 그 순간에 무너지고요.
여기서 관점을 바꿀 수 있어요. 모든 요청을 즉시 처리해야 하는가?
접수만 즉시 하고 실제 처리는 조금 나중에 해도 되는 기능이라면, 순간의 봉우리를 시간에 펼칠 수 있어요.
그러면 필요한 용량이 최대 순간 부하가 아니라 평균 부하를 기준으로 정해져요. 비용 계산 자체가 완전히 달라집니다.
왜 그런가
용량 계획은 대개 최대 순간 부하를 기준으로 해요. 그런데 그 순간은 아주 짧고, 나머지 시간에는 그 용량이 놀아요.
큐를 두면 이 계산이 달라져요. 들어오는 요청을 일단 받아 큐에 넣고, 처리는 일정한 속도로 해요. 그러면 필요한 처리 용량이 최대 순간 부하가 아니라 평균 부하를 기준으로 정해져요. 봉우리를 시간에 펼치는 거죠.
이게 가능한 건 사용자가 결과를 즉시 필요로 하지 않는 경우예요. 신청 접수, 주문 처리, 이미지 변환, 알림 발송, 정산 같은 것들이요. 사용자는 "접수됐습니다"를 즉시 받고, 실제 처리는 몇 초에서 몇 분 뒤에 끝나요.
그리고 이건 앞 편의 백프레셔와 짝이에요. 큐가 무한정 길어지면 안 되니까요. 큐 길이에 상한을 두고 넘으면 거절하는 게 함께 있어야 해요.
중요한 건 큐가 용량을 늘려 주지 않는다는 점이에요. 처리 속도는 그대로예요. 큐가 하는 일은 시간을 벌어 주는 것이에요. 평균 유입이 처리 능력을 넘으면 큐는 계속 길어지기만 하고 결국 무너집니다.
그래서 큐는 순간적인 봉우리에 유효하고, 지속적인 초과에는 무력해요. 이 구분이 중요합니다.
코드로 보면
접수와 처리를 분리한다:
요청 → 처리(3초) → 응답
순간 부하 = 필요한 동시 처리 용량
요청 → 큐에 넣기(5ms) → '접수됨' 응답
워커가 큐에서 꺼내 일정한 속도로 처리
완료되면 알림 또는 상태 조회로 확인
· 큐에는 반드시 상한을 둔다
if queue.size() > MAX: 503 + Retry-After
무한 큐는 지연을 무한히 늘린다
· 처리 속도에 상한을 둔다
워커 수 × 처리 속도 = 하류(DB 등)가 견딜 만큼
큐에서 한꺼번에 쏟으면 하류가 대신 무너진다✕ 문제 지점 · ✓ 해법 · 그대로 돌려 보기 전에 자기 환경에 맞게 고쳐 주세요.
실전에서는
도입할 때 정해야 할 것들이 있어요.
먼저 어떤 기능을 비동기로 바꿀 수 있는지 골라야 해요. 사용자가 결과를 즉시 봐야 하는 기능은 안 돼요. 반대로 접수 확인만으로 충분한 기능이 후보예요. 그리고 실패했을 때 나중에 알려 줄 방법이 있어야 해요.
사용자 경험 설계가 함께 필요해요. "처리 중" 상태를 어떻게 보여 줄지, 완료를 어떻게 알릴지, 실패하면 어떻게 되는지요. 이걸 대충 하면 사용자가 불안해하고 새로고침을 반복하면서 오히려 부하가 늘어요.
처리 속도에 상한을 두는 것도 중요해요. 큐에 쌓인 걸 워커가 최대 속도로 처리하면, 그 부하가 그대로 DB 로 가요. 앞단에서 평탄화한 걸 뒷단에서 다시 몰아치는 셈이죠. 워커 수와 처리 속도를 하류가 견딜 만큼으로 맞춰야 해요.
큐 상한도 반드시 필요해요. 앞 편에서 본 대로 무한 큐는 지연을 무한히 늘리고 결국 전부 잃어요. 넘으면 거절하고, 거절할 때 언제 다시 오라고 알려 주세요.
그리고 순서와 공정성을 생각해야 해요. 먼저 온 요청이 먼저 처리되는 게 자연스럽지만, 항상 그래야 하는 건 아니에요. 우선순위가 필요할 수도 있고, 한 사용자가 큐를 독점하지 않게 막아야 할 수도 있어요.
마지막으로 이 방식의 한계를 분명히 해야 해요. 큐는 순간 봉우리를 펼칠 뿐 평균 처리 능력을 늘리지 않아요. 평균 유입이 처리 능력을 넘으면 큐가 계속 길어지고, 그건 용량을 늘려야 하는 신호입니다.
처방
- 즉시 처리가 필요 없는 기능을 골라내세요. 접수 확인으로 충분한 것들이 후보입니다.
- 접수와 처리를 분리하고, 사용자에게 상태를 보여 주세요. 불안하면 새로고침으로 부하가 늡니다.
- 큐에 상한을 두고 넘으면 거절하세요. 언제 다시 오라는 정보를 함께 주고요.
- 처리 속도에 상한을 두세요. 큐에서 한꺼번에 쏟으면 하류가 대신 무너집니다.
- 실패한 작업의 재시도와 최종 실패 처리를 설계하세요. 비동기는 실패가 사용자에게 즉시 안 보입니다.
- 큐 길이와 가장 오래된 항목의 나이를 지표로 보세요. 이 둘이 밀린 정도를 알려 줍니다.
- 한 사용자가 큐를 독점하지 않게 하세요. 공정성 장치가 필요합니다.
- 평균 유입이 처리 능력을 넘는지 확인하세요. 넘으면 큐로는 못 풉니다. 그건 용량을 늘려야 한다는 신호예요.
- 중복 신청을 막으세요. 사용자가 불안해서 다시 신청하면 처리할 일이 늘어납니다.
- 큐 자체가 새로운 의존성이자 장애 지점이라는 걸 인지하세요. 큐가 죽으면 접수도 안 됩니다.
지금 확인해 보세요
큐가 밀리고 있지 않은가:
· 큐 길이와 가장 오래된 항목의 나이
길이는 순간 상태, 나이는 밀린 정도를 알려 준다
나이가 계속 늘면 평균 유입이 처리 능력을 넘은 것이다
· 유입 속도와 처리 속도를 나란히
유입 > 처리 가 지속되면 큐로는 못 푼다
· 워커가 하류를 무너뜨리고 있지 않은지
워커를 늘렸을 때 DB 지표를 함께 본다
· 상태 조회 요청이 새로운 부하가 되고 있지 않은지
사용자가 불안하면 계속 확인한다
· 실패한 작업이 어디로 가는지
조용히 남아 있으면 아무도 모른다핵심 한 줄큐는 용량을 늘리지 않는다. 봉우리를 시간에 펼칠 뿐이다.