스케일링 (Scaling out)

스케일링 (Scaling out)

Modal에서는 컴퓨트를 수천 개의 컨테이너로 확장하는 게 아주 쉽다는 점이 큰 매력이에요. 앱이 갑자기 유명해져서 트래픽이 몰려도 App이 죽을 걱정을 하지 않아도 되고, 배치 잡이 끝나기를 오래 기다릴 필요도 없죠. 대부분 확장은 자동으로 일어나서 생각하지 않아도 되지만, 더 세밀한 제어가 필요할 때 Modal의 오토스케일러가 어떻게 동작하는지 이해하면 상황에 맞게 조절할 수 있어요.

출처: https://modal.com/docs/guide/scale

오토스케일러는 어떻게 움직일까

모든 Modal Function은 자동 스케일링되는 컨테이너 풀에 대응돼요. 풀의 크기는 Modal의 오토스케일러가 관리하는데, 새 입력을 받을 용량이 없으면 컨테이너를 새로 띄우고 리소스가 놀고 있으면 내리죠. 기본적으로 입력이 없으면 함수는 0개로 스케일 다운돼요. 오토스케일링 결정은 빠르고 자주 이뤄져서 배치 잡은 빨리 램프업하고 배포된 App은 갑작스러운 트래픽 변화에도 대응할 수 있어요.

자동 스케일링 동작 설정하기

오토스케일러 동작을 조절하는 설정은 @app.function이나 @app.cls 데코레이터로 넘겨요.

  • max_containers: 해당 Function의 컨테이너 상한
  • min_containers: 비활성일 때도 웜 상태로 유지할 최소 컨테이너 수
  • buffer_containers: 활성 상태에서 유지할 버퍼 크기 — 새 컨테이너 때문에 입력이 큐에 쌓이지 않게 함
  • scaledown_window: 스케일 다운 시 개별 컨테이너가 대기 상태로 남을 수 있는 최대 시간(초)

이 설정들은 기본적으로 비용과 지연을 맞바꾸는 선택이에요. 웜 풀이나 유휴 버퍼를 크게 유지하면 비용은 올라가지만, 입력이 새 컨테이너 시작을 기다릴 확률은 줄어들죠. scaledown_window를 길게 두면 컨테이너가 더 오래 대기할 수 있게 되어, 규칙적이지만 드물게 들어오는 입력을 받는 App에서 불필요한 컨테이너 변동을 피하는 데 도움이 돼요. 다만 App이 크게 과잉 프로비저닝된 경우 컨테이너가 전체 윈도우를 기다리지 않고 꺼질 수도 있어요.

동적 오토스케일러 업데이트

오토스케일러 설정은 Function.update_autoscaler() 메서드로 App을 재배포하지 않고도 동적으로 바꿀 수 있어요. 단, 다음 App 배포 때는 다시 데코레이터의 설정으로 되돌아가고, 그 전까지는 추가 동적 업데이트로 덮어쓸 수 있죠.

흔한 패턴은 이 메서드를 스케줄된 함수(크론)에서 실행해서 시간대에 따라 웜 풀 크기(또는 컨테이너 버퍼)를 조절하는 거예요.

update_autoscalermodal.Cls에서는 인스턴스의 메서드라서, 특정 파라미터 집합으로 Function을 서빙하는 컨테이너의 자동 스케일링을 제어해요. 이 줄에서는 객체가 정의한 클래스의 인스턴스로 보일 수 있으므로 타입 체크를 꺼둘 필요가 있다는 점만 기억하면 돼요.

입력 병렬 실행

같은 함수를 서로 다른 독립 입력으로 반복 실행하는 경우(예: 그리드 서치)에 성능을 올리는 가장 쉬운 방법은 Function.map()으로 함수 호출을 병렬 실행하는 거예요. evaluate_model이라는 단일 인자를 받는 함수가 있다면, 100개의 입력(이 경우 0~99) 각각을 거의 병렬로 호출하고 결과는 입력 순서와 동일하게 정렬된 이터러블로 돌려받아요.

  • 예외: 기본적으로 어느 호출이 예외를 던지면 그 예외가 전파돼요. 예외를 성공 결과처럼 다뤄 결과 리스트에 모으려면 return_exceptions=True를 넘기면 돼요.
  • Starmap: 함수가 여러 가변 인자를 받는다면 인자마다 하나씩의 입력 이터레이터를 쓰는 Function.map()이나, 튜플 같은 시퀀스를 담은 단일 이터레이터를 인자에 펼쳐 넣는 Function.starmap()을 쓸 수 있어요. Python 내장 map·itertools.starmap과 동작이 비슷하죠.
  • 주의: .map()은 modal 함수 객체 자체의 메서드라서 함수를 명시적으로 '호출'하지 않아요. 또 Python 내장 map()과는 달라서, 내장 map을 쓰면 병렬이 아니라 순차로 실행되니 주의하세요.

비동기 사용

모든 Modal API는 블로킹과 비동기 변형을 모두 제공해요. 비동기 프로그래밍에 익숙하다면 임의의 병렬 실행 패턴을 만들 수 있고, Modal 함수는 원격으로 실행된다는 이점도 있어요.

스케일링 한도 (Scaling Limits)

Modal은 함수마다 다음 한도를 강제해요.

  • 대기 입력(아직 컨테이너에 할당되지 않은 입력) 2,000개
  • 총 입력(실행 중 + 대기) 25,000개

비동기 잡으로 .spawn()으로 만든 입력은 대기 입력 한도가 2,000이 아니라 최대 100만 개까지 허용돼요. 한도를 넘으면 Resource Exhausted 오류가 나고 잠시 후 재시도해야 해요. 더 높은 한도가 필요하면 지원에 문의하면 돼요. 또한 각 .map() 호출은 최대 1,000개 입력을 동시에 처리할 수 있어요.

더 알아보기