English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

게스트
1 / ?
수업 목록으로

더 많은 부하를 감당하는 두 가지 방법

환영합니다

서비스가 부하를 감당하지 못하고 무너지기 시작하면, 운영자는 선택에 직면합니다. 기존 머신을 더 크게 만드는 것(더 많은 CPU, 더 많은 RAM, 더 빠른 디스크) 또는 동일한 작업을 수행하는 더 많은 머신을 추가하는 것입니다.

첫 번째 경로는 수직 확장(scale up)이라고 불립니다. 두 번째 경로는 수평 확장(scale out)이라고 불립니다.

이 레슨에서는 왜 거의 모든 현대 웹 아키텍처가 수평 확장을 선택하는지, 그리고 워크로드의 어떤 특성이 그 선택을 가능하게 하는지를 가르칩니다. 그 답은 한 단어에 숨어 있습니다: 상태(state).

수업이 끝날 때쯤 다음 내용을 이해하게 됩니다:

- 세로 확장(Vertical Scaling)과 가로 확장(Horizontal Scaling)의 비용 곡선 및 각각이 적합한 시점

- '상태 유지(Stateful)'와 '상태 없음(Stateless)'의 실제 의미, 그리고 왜 그중 하나가 저렴하게 복제되는지

- 예상 부하와 급증 부하 하에서 리플리카 플릿(Replica Fleet) 규모를 결정하는 수학적 원리

- 특정 계층이 큐잉 무릎(Queuing Knee)을 넘어 붕괴하지 않도록 하는 헤드룸(Headroom) 규칙

- 상태(State)가 반드시 존재해야 하는 위치(상태는 사라지지 않음) 및 확장성이 필요한 계층에서 상태를 어떻게 분리하는지

왜 특정 임계점을 넘으면 가로 확장이 유리한가

세로 확장(Vertical Scaling): 더 큰 하나의 상자

장점: 단순함. 코드 변경 없음. 조율 없음. 동일한 프로세스가 이제 더 많은 CPU를 갖게 됨.

단점: 한계(ceiling). 시중에서 구매할 수 있는 가장 큰 VM에는 유한한 RAM과 코어가 있음. 그 이상으로는 아무리 돈을 써도 더 많은 여유 공간을 살 수 없음. 벤더의 제공 범위의 최적점을 지나면 비용이 비선형적으로 증가함. 그 한 대의 머신에 장애가 발생하면 서비스 전체가 중단됨.

수평적 확장: 더 작은 박스 여러 대

장점: 한계 없음(머신을 구매하고 조율할 의지가 허용하는 한). 용량은 레플리카 수에 따라 선형적으로, 예측 가능하게 증가함. 단일 레플리카 장애 시 100%가 아닌 1/N의 용량만 손실됨.

단점: 워크로드가 이를 지원해야 함. 일부 워크로드(단일 대형 데이터베이스, 라이브 세션을 유지하는 상태형 게임 서버)는 수평적 확장에 저항함. 조율 및 부하 분산이 운영상의 고려 사항이 됨.

교차점: 단일 머신 장애를 견뎌야 하는 모든 프로덕션 서비스는 최소 두 대의 머신에서 실행되어야 함. 두 대를 수용하는 순간 이미 수평적 확장을 선택한 셈임. 그 이후의 질문은 '해야 하는가?'가 아니라 '다음 레플리카를 얼마나 저렴하게 추가할 수 있는가?'임.

핵심 활성화 요인: 머신 자체에 요청별 상태를 유지하지 않는 워크로드. 그러면 어떤 레플리카든 어떤 요청이든 응답할 수 있으며, 레플리카를 추가하면 조율 없이 용량이 증가함.

수직적 대 수평적 확장: 비용 곡선 및 한계

8개 CPU를 가진 단일 VM에서 서비스를 운영하며, 현재 60%의 CPU 사용률로 초당 800개의 요청을 처리하고 있습니다. 향후 1년 내 트래픽이 4배로 증가할 것으로 예상됩니다. 32개 CPU의 VM을 임대하는 것(수직 확장)과 로드 밸런서 뒤에서 동일한 8개 CPU VM 4대를 실행하는 것(수평 확장) 중 어떤 옵션을 추천하시며, 순수한 처리 용량 외에 두 가지 이유를 들어 설명해 주세요.

실무에서의 상태 유지(Stateful)와 상태 없음(Stateless)

상태는 사라지지 않고, 단지 이동할 뿐입니다

상태를 가지는 컴포넌트(Stateful component): 손실 시 동작이 변경되는 정보를 보유합니다. 사용자 계정을 저장하는 데이터베이스, 세션 토큰을 저장하는 캐시, 특정 사용자의 장기간 스트리밍 연결을 고정하는 워커 등이 해당됩니다.

상태를 가지지 않는 컴포넌트(Stateless component): 손실 시 영향이 없는 정보를 보유하지 않습니다. 요청을 읽고, 데이터베이스를 쿼리하며, 응답을 작성하는 웹 티어가 해당됩니다. 각 요청은 독립적이며, 이 티어는 요청 사이에 아무것도 기억하지 않습니다.

핵심 통찰: 상태는 시스템에서 사라지지 않습니다. 상태를 보유하도록 설계된 레이어(데이터베이스, Redis 클러스터, 객체 저장소 등)로 이동합니다. 이를 통해 트래픽을 직접 처리하는 레이어는 상태를 가지지 않게 되며, 상태 없는 레이어는 모든 리플리카가 모든 요청에 응답할 수 있으므로 수평적으로 확장됩니다.

실용적 테스트: 이 티어의 프로세스 하나를 무작위로 종료하고 다시 시작했을 때, 사용자에게 잘못된 답변이나 세션 손실이 발생합니까? 그렇다면 상태가 있는 것입니다. 그렇지 않다면 상태가 없는 것입니다.

예시

- 요청을 읽고, Postgres를 쿼리하며, JSON을 반환하는 Python 웹 프로세스: 상태를 가지지 않음(Stateless). 상태는 Postgres에 있습니다.

- 로컬 메모리에 사용자 장바구니를 유지하는 Python 웹 프로세스: 상태를 가짐(Stateful). 프로세스를 종료하면 장바구니가 손실됩니다.

- 채팅 사용자와의 연결을 유지하는 WebSocket 서버: 연결의 의미에서 상태가 있는(stateful) 상태. 프로세스가 종료되면 연결이 끊어지며, 클라이언트는 다시 연결해야 합니다. 이러한 시스템은 주의가 필요하지만(스티키 세션, 일관된 해싱 등) 수평 확장(horizontally)이 가능한 경우가 많습니다.

- Postgres 앞에 배치된 Redis 캐시: 캐시 콘텐츠에 대해서는 상태가 있는(stateful) 상태이지만, 캐시 미스(cache miss)가 허용 가능하다면 수용 가능합니다. 리플리카가 실패해도 데이터 손실이 아닌 캐시 미스가 발생합니다.

수평 확장(horizontally scaling)을 위한 설계 = 확장해야 하는 레이어에서 상태를 제거하는 것입니다.

의심스러운 티어(Tier) 감사

한 팀은 리버스 프록시 뒤의 6개 백엔드 VM에서 추천 API를 실행하고 있습니다. 해당 애플리케이션은: 요청에서 사용자 ID를 읽고, Postgres에서 사용자의 최근 활동을 가져오며, 스코어링 알고리즘을 실행한 후, 추천 항목 목록을 반환합니다. 두 가지 비표준 동작이 있습니다:

- 애플리케이션은 프로세스 메모리에 '최근 사용자 활동' 캐시를 유지하며, 사용자의 첫 번째 요청 시 채워지고 이후 요청에서 재사용됩니다.

- 애플리케이션은 스티키 세션(sticky sessions)을 사용합니다: 사용자가 VM #3에 한 번 접속하면, 이후 모든 요청이 VM #3으로 전송됩니다(프록시는 쿠키 기반의 스티키 라우팅으로 설정되어 있음).

이 두 가지 동작 중 어떤 것이 해당 티어를 상태가 있는(stateful) 상태로 만드는지 식별하고, 팀이 6개 VM에서 12개 VM으로 확장하려고 할 때 무엇이 깨지는지 설명하십시오. 그런 다음, 사용자 캐시 이점을 잃지 않으면서도 해당 티어가 수평적으로 확장되도록 하는 재설계를 제안하십시오.

레플리카 공식

가장 단순한 용량 공식

티어가 무상태(stateless)가 되면, 크기 조정은 산술 문제가 됩니다. 정상 상태의 부하가 동일한 속도로 유입되고 유출되도록 충분한 리플리카 수가 필요하며, 급증(surge)을 위한 여유 공간도 확보해야 합니다.

공식:

replicas = ⌈ (peak_load × surge_factor) / per_replica_capacity ⌉ + headroom

여기서:

- peak_load: 정상 운영 시 예상되는 최대 지속 요청/초

- surge_factor: 피크를 초과하는 짧은 시간의 급증을 커버하기 위한 배율 (예측 가능한 트래픽의 경우 일반적으로 1.5배~2배, 바이럴/예측 불가능한 트래픽의 경우 3배 이상)

- per_replica_capacity: 허용 가능한 지연 시간 및 사용률에서 하나의 리플리카가 처리할 수 있는 요청/초 (일반적으로 포화 상태가 아닌 70% CPU 사용률에서 측정)

- headroom: 일부 리플리카 장애 시 티어가 붕괴하지 않도록 하는 추가 리플리카 수 (작은 플릿의 경우 일반적으로 1~2개, 큰 플릿의 경우 10~20%)

예제: 백엔드가 리플리카당 CPU 70% 사용률로 초당 100건의 요청(req/s)을 처리합니다. 피크 부하가 초당 600건(req/s)이고, 가끔 2배의 급격한 증가가 발생할 것으로 예상됩니다. 2개의 리플리카 장애에도 버텨내야 합니다.

replicas = ⌈ (600 × 2) / 100 ⌉ + 2 = 12 + 2 = 14개 리플리카

80% 규칙

리플리카당 처리 용량은 포화점(saturation point)이 아닙니다. 용량은 CPU 100%가 아닌 70~80% 사용률에서 측정해야 합니다.

약 80% 사용률을 넘으면 대기열(queueing) 곡선이 급격히 상승합니다: 60% 사용률에서 10ms였던 대기열이 90% 사용률에서는 80ms가 됩니다. 처리량(throughput)보다 지연 시간(latency)이 먼저 무너집니다. (동반 레슨 geometry_of_stateless_horizontal_scaling에서 이 곡선을 수학적으로 유도합니다.)

오토스케일링 vs 정적 프로비저닝

정적: 피크 × 서지(surge) 여유분을 프로비저닝하고, 오프피크 시간대의 낮은 사용률로 인한 비용을 감수합니다.

오토스케일링: 컨트롤러가 관찰된 사용률, 목표 지연 시간, 또는 대기열 깊이에 따라 리플리카를 추가하고 제거합니다.

오토스케일링 주의사항: 콜드 스타트 시간이 중요합니다. 새 리플리카가 부팅하는 데 2분이 걸린다면, 오토스케일링은 30초짜리 트래픽 급증에 대응할 수 없습니다. 성숙한 오토스케일링은 스케일업 임계값 바로 아래에서 미리 프로비저닝된 리플리카의 웜 풀(warm pool)을 유지합니다.

리플라이 크기 산정 공식 및 실습 예제

새 서비스용 플릿(Fleet) 크기 산정

팀은 비디오 메타데이터 API를 출시할 계획입니다. 벤치마크에 따르면 단일 리플리카는 CPU 사용률 70% 및 p99 레이턴시 50ms에서 초당 250개의 요청(req/s)을 처리합니다. 마케팅은 프라임 타임 시간대의 피크 부하를 초당 4,000개 요청으로 예측합니다. 예정된 프로모션 이벤트로 인해 일시적으로 3배까지 급증할 수 있습니다. 서비스는 3개의 리플리카가 동시에 장애가 발생해도 생존한 리플리카들의 사용률이 80%를 초과하지 않도록 해야 합니다.

리플리카 공식을 적용하여 출시 플릿의 크기를 산정하세요. 단계를 따라 계산 과정을 보여준 후, 왜 이 숫자가 여전히 틀릴 수 있는지에 대한 이유를 하나 설명하세요 (콜드 스타트, 캐시 웜업, 의존성 레이턴시 등 방어할 수 있는 어떤 이유든 가능합니다).

콜드 스타트, 느린 드레인 & 기타 실제 엣지 케이스

실제 플릿에는 실제 엣지 케이스가 있습니다

이 공식은 리플리카가 즉시 생성되고, 즉시 트래픽을 수용하며, 즉시 트래픽을 중단한다고 가정합니다. 프로덕션 환경에서는 이 중 어느 것도 성립하지 않습니다.

콜드 스타트: 새로운 리플리카는 OS 부팅, 프로세스 시작, 설정 로드, 캐시 워밍, 헬스 체크 통과에 시간이 필요합니다. 컨테이너 재시작의 경우 5초, 전체 VM 부팅 + 이미지 풀링의 경우 5분까지 걸릴 수 있습니다. 오토스케일링은 이 지연 시간보다 짧은 버스트에는 대응할 수 없습니다.

느린 드레인: 풀에서 제거되는 리플리카는 종료 전에 진행 중인 요청을 완료하는 데 시간이 필요합니다. 그렇지 않으면 사용자는 잘린 응답을 보게 됩니다. 리버스 프록시는 드레인(새 요청 수락 중단, 활성 요청 완료)을 지원하지만, 이는 몇 초에서 몇 분까지 걸립니다.

워밍 풀(Warm pool): 프로덕션 플릿은 신호가 오면 즉시 트래픽을 처리할 수 있도록 사전에 프로비저닝되어 있지만 유휴 상태인 리플리카 풀을 유지합니다. 빠른 서지(surge) 응답을 위해 소량의 고정 비용을 지불하는 트레이드오프입니다.

커넥션 드레인(Connection draining) vs 즉시 킬(Immediate kill): 그레시풀(Graceful) 종료는 중요합니다. 드레인을 트리거하는 SIGTERM은 SIGKILL보다 시간이 더 걸리지만, 사용자 요청을 깨뜨리지 않습니다.

헬스 체크 윈도우: 방금 시작된 리플리카는 데이터베이스 연결 풀이 워밍(warm) 상태가 되기 전에 첫 번째 헬스 체크를 통과할 수 있습니다. 이 경우 프록시는 실제 트래픽을 보내고, 처음 몇십 개의 요청이 느려집니다. 헬스 체크를 프로세스의 생존 여부만 확인하는 것이 아니라 실제 처리 경로를 테스트하도록 조정해야 합니다.

스티키니스 크리프(Stickiness creep): 명목상 무상태(stateless)인 계층도 시간이 지나면 스티키니스(상태 유지성)를 획득하게 됩니다(CDN 캐시, DNS 리졸버 캐시, 연결 풀 등). '동일한 리플리카'임에도 불구하고 다르게 동작하는 현상에 대해 의심의 눈초리를 가져야 합니다.

워밍 풀(Warm Pool) 또는 반응형 오토스케일링(Reactive Autoscaling)?

비디오 메타데이터 API(이전 문제에서 다루었던 것으로, 정상 피크 + 서지를 위해 51개의 리플리카로 설정되어 있음)는 새로운 바이럴 비디오가 업로드될 때마다 30초 동안 정상 부하의 5배로 증가하는 서지를 경험합니다. 현재 오토스케일링은 콜드(이미지 풀 + 워밍업) 상태에서 새 리플리카를 추가하는 데 90초가 걸립니다. 이 90초의 공백 기간 동안 레이턴시가 급격히 상승하고 일부 요청이 타임아웃됩니다.

해결책을 제안하세요. 다음 중 하나를 선택하세요: (a) 반응형 오토스케일링을 유지하되 다른 방식으로 조정하기, (b) 유휴 리플리카의 워밍 풀을 프로비저닝하기, (c) 5배 피크를 위해 영구적으로 정적 프로비저닝하기. 선택한 이유를 논리적으로 설명하고, 나머지 두 옵션이 부과할 비용 하나를 명시하세요.

제약 조건 하에서 무상태 티어 설계하기

종합

수평적 확장(Horizontal Scaling)이 소규모 임계점을 넘어서면 왜 유리한지, 실무에서 '상태(State)'가 무엇을 의미하는지, 예상 부하와 급증 부하에 따라 플릿(Fleet) 크기를 어떻게 결정하는지, 그리고 수평적 확장이 어떤 한계에서 깨지는지를 학습했습니다.

네 가지 모두 적용하세요.

feed.example.com이라는 소셜 피드 API의 백엔드 티어를 설계하세요. 제약 조건: 레플리카당 용량 70% CPU에서 200 req/s; 예상 피크 부하 1500 req/s; 서지 계수 2.5배 (가끔 발생하는 트렌딩 스토리); 동시 2개 레플리카 장애를 견뎌야 함; 콜드 스타트 시간 60초; 버스트는 45초까지 지속 가능; 예산은 일부 유휴 용량은 허용하지만 2.5배의 영구 프로비저닝은 허용하지 않음.

정상 상태의 플릿 규모를 결정하세요(계산 과정 포함), 서지 처리 전략(워밍 풀 / 반응형 오토스케일링 / 둘 다)을 선택하고, 애플리케이션이 아마도 보유할 것으로 보이는 사용자별 상태 한 가지를 식별하여 이 티어가 무상태(stateless)를 유지하려면 해당 상태가 어디로 이동해야 하는지 명시하세요.

이 과정의 다음 단계

이 코스의 다음 단계

이제 상태 없는(stateless) 계층에 대한 작동하는 정신 모델을 갖추셨습니다: 왜 확장 가능한지, 어떻게 용량을 설정해야 하는지, 그 경계에서 무엇이 깨지는지, 그리고 확장해야 하는 계층을 벗어나면 상태가 어디로 이동해야 하는지.

이 코스의 다음 레슨(cs_distsys_ingress_egress_separation)은 더 미묘한 문제를 다룹니다: 완벽하게 용량이 설정된 상태 없는 계층조차도 들어오는 트래픽과 나가는 트래픽이 동일한 네트워크 경로를 공유할 때 예상치 못한 방식으로 실패할 수 있습니다. 대표적인 예시는 프록시가 자신에게 연결을 시도하는 경우이며, 해결책은 하나의 계층을 서로 다른 책임이 있는 두 개의 계층으로 분리하는 것입니다.

동반 레슨: geometry_of_stateless_horizontal_scaling은 대기열 곡선, 리플릿 플릿에 적용된 리틀의 법칙(Little's Law), 그리고 80% 사용률 무릎(knee)의 기하학적 의미를 유도합니다.

수고하셨습니다. 다음으로 갑시다.