[번역] 콜드 스타트(Cold Starts)

N
negu63

Back-End2026.01.01

Modal - 2024년 10월 24일


Modal 함수(Functions)는 컨테이너(containers)에서 실행됩니다.
함수를 실행할 준비가 된 컨테이너가 이미 있다면, 해당 컨테이너가 재사용됩니다.
그렇지 않은 경우, Modal은 새로운 컨테이너를 가동합니다. 이를 *콜드 스타트(cold start)*라고 하며, 종종 더 높은 지연 시간(latency)을 유발합니다.
콜드 스타트 중에 지연 시간이 증가하는 원인은 두 가지입니다:
  1. 입력값이 컨테이너가 준비되거나 "웜(warm)" 상태가 될 때까지 큐(queue)에서 대기하는 시간이 더 길어질 수 있습니다.
  1. 이제 막 시작된 컨테이너가 입력값을 처리할 때, 첫 번째 호출 시에만 수행되어야 하는 추가 작업("초기화(initialization)")이 있을 수 있습니다.
이 가이드에서는 큐 대기 시간과 초기화가 체감 지연 시간에 미치는 영향을 줄이기 위한 기술과 Modal의 기능들을 소개합니다.
웜 컨테이너가 없는 상태에서 함수를 호출하거나, 입력값이 "대기(pending)" 상태에서 너무 많은 시간을 소비하는 것을 확인했다면, 큐 대기 시간 최적화를 목표로 해야 합니다.
일부 함수 호출이 다른 호출보다 훨씬 오래 걸리고, 해당 호출이 새 컨테이너에서 처음 처리되는 경우라면, 초기화 최적화를 목표로 해야 합니다.

웜 컨테이너를 위한 큐 대기 시간 단축

현재 입력값의 수를 처리할 수 있는 웜 컨테이너가 충분하지 않을 때 새로운 컨테이너가 부팅됩니다.
예를 들어, 함수에 처음으로 입력값을 보낼 때 웜 컨테이너는 0개이고 입력값은 1개이므로, 단일 컨테이너가 부팅되어야 합니다. 입력값에 대한 총 지연 시간은 컨테이너를 부팅하는 데 걸리는 시간을 포함하게 됩니다.
첫 번째 입력 처리가 끝난 직후에 다른 입력값을 보내면, 웜 컨테이너 1개와 대기 중인 입력값 1개가 있게 되며, 새로운 컨테이너는 부팅되지 않습니다.
일반화하자면, 입력값이 큐에서 대기하는 시간에 영향을 미치는 요소는 두 가지입니다: 컨테이너가 부팅되어 웜 상태가 되는 데 걸리는 시간(더 빠르게 부팅하여 해결)과 입력값을 처리할 수 있는 웜 컨테이너가 생길 때까지의 시간(더 많은 웜 컨테이너를 보유하여 해결)입니다.

컨테이너를 더 빠르게 웜업하기

컨테이너가 웜 상태가 되어 입력값을 받을 준비가 되기까지 걸리는 시간은 수 초에서 수 분까지 걸릴 수 있습니다.
Modal의 커스텀 컨테이너 스택은 이 시간을 줄이기 위해 고도로 최적화되어 있습니다. 컨테이너는 약 1초 만에 부팅됩니다.
하지만 컨테이너가 웜 상태로 간주되어 입력값을 처리할 준비가 되기 전에, 코드의 전역 범위(global scope)에 있는 로직(예: import)이나 modal.enter 메서드를 실행해야 합니다. 따라서 부팅이 느리다면 이곳이 최적화를 시작해야 할 첫 번째 지점입니다.
예를 들어, 부팅 과정 중에 모델 서버에서 대용량 모델을 다운로드하고 있을 수 있습니다. 대신 모델을 미리 다운로드해 두어 한 번만 다운로드하면 되도록 할 수 있습니다.
수십 기가바이트에 달하는 모델의 경우, 이를 통해 부팅 시간을 수 분에서 수 초로 단축할 수 있습니다.

더 많은 웜 컨테이너 실행하기

부팅 속도를 충분히 높이는 것이 항상 가능한 것은 아닙니다. 예를 들어, 대화형 환경에서는 모델을 로드하기 위해 추가되는 수 초의 지연 시간도 허용되지 않을 수 있습니다.
이 경우 유일한 옵션은 더 많은 웜 컨테이너를 실행 상태로 유지하는 것입니다. 이렇게 하면 입력값이 웜 컨테이너에 의해 처리될 확률이 높아집니다. 예를 들어, 다른 컨테이너가 부팅되는 동안 한 컨테이너가 입력을 마치는 식입니다.
Modal은 현재 얼마나 많은 컨테이너를 웜 상태로 유지할지 제어하는 세 가지 매개변수scaledown_window, min_containers, buffer_containers를 제공합니다.
이러한 모든 전략은 함수가 소비하는 리소스를 증가시킬 수 있으므로, 콜드 스타트 지연 시간과 비용 사이의 트레이드오프(trade-off)가 발생합니다.

scaledown_window로 컨테이너를 더 오래 웜 상태로 유지하기

Modal 컨테이너는 종료되기 전 짧은 시간 동안 유휴(idle) 상태를 유지합니다. 기본적으로 최대 유휴 시간은 60초입니다. 이는 @function 데코레이터에서 scaledown_window를 설정하여 구성할 수 있습니다. 값은 초 단위로 측정되며, 2초에서 20분 사이로 설정할 수 있습니다.
scaledown_window를 늘리면 후속 요청이 콜드 스타트를 필요로 할 가능성이 줄어들지만, 컨테이너가 유휴 상태인 동안 사용된 리소스(예: GPU 예약 또는 잔류 메모리 점유)에 대해 비용이 청구됩니다. 오토스케일러(autoscaler)는 함수에 리소스가 과도하게 할당된 경우 더 공격적으로 스케일 다운을 수행하므로, 컨테이너가 반드시 전체 윈도우 시간 동안 살아있는 것은 아니라는 점에 유의하십시오.

min_containersbuffer_containers로 리소스 과다 할당(Overprovisioning)하기

함수가 0에서부터 스케일링되는 경우처럼 처음에 웜 컨테이너가 아예 없다면, 이미 웜 상태인 컨테이너를 더 오래 유지하는 것은 도움이 되지 않습니다.
일부 컨테이너를 항상 웜 상태로 실행 유지하려면 @function 데코레이터에 min_containers 값을 설정하십시오. 이는 컨테이너 수의 하한선을 설정하여 함수가 0으로 스케일 다운되지 않도록 합니다. Modal은 평소와 같이 함수의 수요가 min_containers 값을 초과하여 변동함에 따라 더 많은 컨테이너를 스케일 업하거나 스케일 다운합니다.
min_containers가 함수가 유휴 상태일 때 컨테이너를 과다 할당하는 반면, buffer_containers는 함수가 활성 상태일 때 추가 컨테이너를 할당합니다. 이 "버퍼"용 추가 컨테이너는 유휴 상태로 대기하다가 요청 속도가 증가할 때 입력값을 처리할 준비를 합니다. 이 매개변수는 새로운 사용자나 클라이언트가 함수를 호출하기 시작할 때처럼, 하나의 입력이 도착하면 더 많은 입력이 도착할 것으로 예측되는 급증(bursty)하는 요청 패턴에 특히 유용합니다.

초기화로 인한 지연 시간 단축

일부 작업은 함수가 처음 호출될 때 수행되지만, 이후의 모든 호출에서 재사용될 수 있습니다. 이것이 초기화 시 수행되는 분할 상환 작업(amortized work)입니다.
예를 들어, 처음 사용할 때 디스크에서 메모리로 가중치를 로드해야 하는 대규모 사전 학습된 모델을 사용하고 있을 수 있습니다.
이로 인해 웜 컨테이너의 첫 번째 호출에 대한 지연 시간이 길어지며, 이는 애플리케이션에서 간헐적인 느린 호출(높은 꼬리 지연 시간(tail latency) 또는 상승된 p9X 지표)로 나타납니다.

초기화 작업을 첫 번째 호출 밖으로 이동하기

첫 번째 호출 시 수행되는 일부 작업은 앞당겨서 미리 완료할 수 있습니다.
모델 가중치 다운로드와 같이 디스크에 저장할 수 있는 모든 작업은 가능한 한 일찍 수행해야 합니다. 결과는 컨테이너의 이미지(Image)에 포함하거나 Modal 볼륨(Volume)에 저장할 수 있습니다.
네트워크 연결이나 추론 서버를 가동하는 것과 같은 일부 작업은 직렬화하기 까다롭습니다. 이러한 초기화 로직을 함수 본문에서 분리하여 전역 범위나 컨테이너 enter 메서드로 옮기면, 이 작업을 웜업 기간으로 이동시킬 수 있습니다. 모든 enter 메서드가 완료될 때까지 컨테이너는 웜 상태로 간주되지 않으므로, 이 초기화를 아직 완료하지 않은 컨테이너로는 입력값이 라우팅되지 않습니다.
머신러닝 모델 가중치와 함께 enter를 사용하는 방법에 대한 자세한 내용은 이 가이드를 참조하십시오.
enter가 지연 시간을 없애주는 것은 아니라는 점에 유의하십시오. 단지 지연 시간을 웜업 기간으로 옮기는 것뿐이며, 이는 더 많은 웜 컨테이너를 실행함으로써 처리할 수 있습니다.
메모리 스냅샷(memory snapshots)을 사용하여 콜드 스타트를 더 빠르게 만들 수도 있습니다.
첫 번째 이후의 함수 호출이 더 빠른 이유는 부분적으로 메모리가 이미 임포트된 라이브러리의 내용과 같이 계산하거나 디스크에서 읽어야 하는 값들로 채워져 있기 때문입니다.
메모리 스냅샷은 컨테이너가 웜업된 후 사용자가 제어하는 시점에서 컨테이너의 메모리 상태를 캡처하고, 향후 부팅 시 해당 상태를 재사용합니다. 이는 콜드 스타트 지연 시간 페널티와 웜업 기간의 지속 시간을 대폭 줄일 수 있습니다.
자세한 내용은 메모리 스냅샷 가이드를 참조하십시오.

초기화 코드 최적화

때로는 작업을 더 빠르게 만드는 것 외에는 방법이 없을 때도 있습니다.
여기서는 Modal 함수에서 초기화를 최적화할 때 나타나는 특정 패턴을 공유합니다.

여러 대용량 파일을 동시에 로드하기

Modal 애플리케이션은 입력값을 처리하기 전에 대용량 파일(예: 모델 가중치)을 메모리로 읽어 들여야 하는 경우가 많습니다. 가능한 경우 이러한 대용량 파일 읽기는 순차적이 아니라 동시에(concurrently) 발생해야 합니다. 동시 IO는 플랫폼의 높은 디스크 및 네트워크 대역폭을 최대한 활용하여 지연 시간을 줄입니다.
느린 순차 IO의 흔한 예는 여러 개의 독립적인 Huggingface transformers 모델을 직렬로 로드하는 것입니다.
위의 코드 조각은 네 개의 .from_pretrained 로드를 순차적으로 수행합니다. 어떤 구성 요소도 다른 구성 요소가 이미 메모리에 로드되어 있어야 할 필요가 없으므로, 대신 동시에 로드할 수 있습니다.
대신 다음과 같은 함수를 사용하여 동시에 로드할 수 있습니다:
대용량 파일 읽기에 대해 동시 IO를 수행해도 콜드 스타트 속도가 빨라지지 않는다면, 함수 코드의 일부가 Python의 GIL(Global Interpreter Lock)을 점유하여 멀티스레드 실행기의 효율성을 떨어뜨리고 있을 가능성이 있습니다.
0
7

댓글

?

아직 댓글이 없습니다.

첫 번째 댓글을 작성해보세요!

유사한 내용의 글