사전 콜드 스타트
문제

콜드 스타트

gabury1

콜드 스타트는 준비가 안 된 상태로 일을 시작하느라 처음 한 번만 오래 걸리는 현상입니다. 두 번째 요청부터는 앞 요청이 해 둔 준비를 물려받아 제 속도가 납니다. 같은 코드에 같은 입력을 줘도 첫 요청만 다릅니다. 무엇을 준비해야 하는지는 이 이름을 쓰는 분야마다 다릅니다.

쉽고 빠른 이해

처음 부를 때만 오래 걸리는 현상입니다. 한동안 아무도 안 부른 함수를 호출하면 그 코드를 올려 둘 실행 환경부터 새로 마련하느라 첫 응답이 늦습니다.

이 현상에 이름이 붙은 까닭은 평균으로는 안 보이기 때문입니다. 전체 요청 중 적은 몫만 겪어서 평균은 거의 안 움직입니다. 그 몫에 걸린 사용자는 눈에 띄게 기다립니다.

이렇게 일어납니다:

  1. 요청이 도착합니다. 받아 줄 준비가 아직 안 되어 있습니다
  2. 준비부터 합니다. 그동안 요청은 기다립니다
  3. 준비가 끝난 뒤 처리합니다. 다음 요청은 이 준비를 건너뜁니다

무엇이 나빠지나 — 가장 오래 기다린 사용자의 체감이 나빠집니다. 부르는 일이 뜸한 서비스일수록 준비가 자주 풀려서 겪는 몫이 커집니다.

상세

콜드 스타트는 일을 받을 준비가 안 된 상태에서 요청이 도착해, 그 준비까지 요청이 떠안는 현상입니다. 오랜만에 불린 서버리스 함수가 그런 경우입니다. 서버리스는 서버를 직접 띄우지 않고 함수만 올려 두면 부를 때마다 대신 실행해 주는 방식입니다.

부르는 쪽은 함수 하나를 호출했을 뿐입니다. 받는 쪽은 그 함수를 담을 컨테이너를 새로 띄우는 일부터 시작합니다. 컨테이너는 프로그램과 그것이 쓰는 파일을 한 묶음으로 싸서 따로 띄우는 단위입니다.

이 편에서 「준비」는 요청을 처리하기 전에 한 번만 해 두면 되는 일을 말합니다. 실행 환경을 마련하고, 코드와 설정을 읽어 들이고, 바깥과 연결을 맺어 두는 것이 그런 일입니다.

준비가 이미 끝난 상태에서 받은 요청은 웜 스타트라고 부릅니다. 두 이름은 요청을 가른 것이 아니라 요청이 도착했을 때의 상태를 가른 것입니다. 같은 요청이 언제 오느냐에 따라 한쪽이 되기도 하고 다른 쪽이 되기도 합니다.

준비를 미리 안 해 두는 까닭

준비를 미리 해 두지 않는 데에는 이유가 있습니다. 미리 해 두려면 아무도 안 부르는 동안에도 실행 환경을 띄워 둬야 합니다. 그동안 메모리도 계속 붙들고 있습니다. 쓰지도 않을 것에 자원을 매어 두는 셈입니다.

그래서 많은 시스템이 반대쪽을 고릅니다. 부를 때까지 아무것도 띄우지 않다가 첫 요청이 오면 그때 준비합니다. 콜드 스타트는 이 선택의 대가입니다.

아래 그림은 요청 하나가 도착했을 때 갈리는 두 길입니다.

flowchart TD
    A["요청이 도착한다"] --> B{"준비된 실행 환경이 있나"}
    B -->|있다| W["바로 처리한다 · 웜 스타트"]
    B -->|없다| C["실행 환경을 띄운다"]
    C --> D["코드와 설정을 읽어 들인다"]
    D --> E["초기화 코드를 돌린다"]
    E --> F["바깥과 연결을 맺는다"]
    F --> G["처리한다 · 콜드 스타트"]
    G --> H["준비된 실행 환경으로 남는다"]
    H --> B

바로 처리하는 길과 준비부터 하는 길은 마지막 한 칸만 같습니다. 준비부터 하는 쪽은 네 칸을 더 지납니다. 그 네 칸에 든 시간을 그 요청 하나가 떠안습니다.

맨 아래 화살표가 「처음 한 번만」의 뿌리입니다. 준비를 마친 요청이 그것을 남겨 두고 가므로 뒤따르는 요청은 바로 처리하는 쪽으로 빠집니다.

같은 이름으로 부르는 네 가지

이 이름은 한 분야의 말이 아닙니다. 무엇이 준비 안 되어 있는지가 분야마다 다릅니다.

아래 표에 낯선 이름 둘이 나옵니다. JIT(Just In Time)는 프로그램이 도는 중에 코드를 기계어로 옮기는 방식이고, 오리진은 캐시가 비었을 때 값을 가지러 가는 원본 저장소입니다.

어디서 무엇이 준비 안 되어 있나 첫 요청이 더 하는 일
서버리스 함수 코드를 올릴 실행 환경 환경을 띄우고 코드를 올린다
JIT 컴파일로 도는 프로그램 기계어로 옮겨 둔 코드 해석해 가며 돌고 옮길 구간을 고른다
비어 있는 캐시 캐시에 담긴 값 오리진까지 가서 값을 가져온다
추천 시스템 그 사용자가 남긴 기록 추천할 근거를 못 만든다

앞의 셋은 뜻이 같습니다. 준비가 안 된 채로 시작해서 처음 한 번만 오래 걸립니다. 그 준비가 끝나면 없어집니다.

넷째는 이름만 같습니다. 기다린다고 풀리지 않고 데이터가 쌓여야 풀립니다. 이 편은 앞의 셋을 다룹니다.

재현되는 조건

넷이 다 서면 콜드 스타트가 됩니다.

  1. 요청을 처리하기 전에 한 번만 해 두면 되는 준비가 있습니다
  2. 그 준비를 미리 안 해 두고 첫 요청이 올 때 합니다
  3. 한 번 해 둔 준비를 뒤따르는 요청들이 물려받습니다
  4. 준비에 드는 시간이 처리 한 번에 드는 시간과 견줄 만큼 큽니다

재현은 두 번 부르는 것으로 끝납니다. 오랫동안 안 부른 함수를 한 번 부르고, 응답을 받자마자 같은 함수를 한 번 더 부릅니다. 두 응답 시간이 갈리면 그 차이가 콜드 스타트입니다.

셋째가 빠지면 콜드 스타트가 아닙니다. 요청마다 준비를 새로 하면 모든 요청이 똑같이 오래 걸립니다. 그건 처리 자체에 시간이 드는 것입니다.

넷째가 빠지면 있어도 안 보입니다. 준비에 드는 시간이 처리에 견줘 작으면 첫 요청과 그다음 요청의 차이가 묻힙니다.

준비가 굳었다 풀리는 흐름

준비는 한 번 해 두면 끝나는 것이 아닙니다. 쓰지 않는 동안 거둬 가기 때문입니다. 안 쓰는 실행 환경을 붙들고 있으면 그만큼 다른 일에 못 내줍니다.

stateDiagram-v2
    [*] --> 차가움
    차가움: 차가움 · 준비된 실행 환경이 없다
    준비중: 준비 중 · 첫 요청이 기다린다
    따뜻함: 따뜻함 · 다음 요청은 준비를 건너뛴다
    차가움 --> 준비중: 요청이 도착한다
    준비중 --> 따뜻함: 준비가 끝난다
    따뜻함 --> 따뜻함: 요청이 이어서 들어온다
    따뜻함 --> 차가움: 한동안 요청이 없어 거둬 간다
    따뜻함 --> 차가움: 새 코드를 올리거나 다시 띄운다

따뜻함에서 차가움으로 돌아가는 두 화살표가 「처음 한 번만」을 깹니다. 부르는 일이 뜸한 서비스는 따뜻해질 틈 없이 식어서 거의 모든 요청이 첫 요청이 됩니다.

배포도 그 화살표를 탑니다. 새 코드를 올리면 지금까지 데워 둔 것이 전부 버려지고 처음으로 돌아갑니다.

늘어날 때도 마찬가지입니다. 요청이 갑자기 몰리면 오토스케일링이 같은 프로그램을 여러 벌 더 띄웁니다. 새로 뜬 쪽은 전부 차갑습니다. 하필 붐빌 때 콜드 스타트가 몰리는 까닭이 이것입니다.

평균으로는 안 보이는 까닭

콜드 스타트를 겪는 요청은 전체에서 적은 몫입니다. 그래서 평균 응답 시간은 거의 안 움직입니다. 평균만 보는 팀은 이 현상이 있는 줄도 모릅니다.

드러나는 곳은 분포의 꼬리입니다. 백분위수는 응답 시간을 짧은 쪽부터 줄 세웠을 때 어느 지점의 값인지를 보는 잣대입니다. 이 지점을 끝쪽으로 잡고 보면 콜드 스타트를 겪은 요청만 따로 솟아 있습니다.

가장 오래 기다린 쪽의 응답 시간을 꼬리 지연이라고 부릅니다. 평균을 목표로 걸어 둔 팀은 이 값이 얼마나 벌어졌는지 모른 채 지나갑니다.

한 화면을 그리려고 여러 곳을 부르는 구조에서는 몫이 더 커집니다. 부르는 곳 중 하나만 차가워도 화면 전체가 그만큼 늦습니다.

부하 테스트에서는 오히려 잘 안 잡힙니다. 요청을 쉬지 않고 밀어 넣으면 띄워 둔 것이 모두 곧 따뜻해집니다. 그 테스트가 재는 것은 데워진 뒤의 성능입니다.

콜드 스타트가 아닌 것

늦는 원인은 많습니다. 원인마다 고치는 곳이 달라서 처음에 잘못 짚으면 엉뚱한 데를 팝니다.

이런 경우 왜 콜드 스타트가 아닌가
모든 요청이 똑같이 오래 걸린다 물려받을 준비가 없다. 처리 자체에 시간이 드는 것이다
요청이 몰려 줄을 서느라 늦는다 줄은 준비와 상관없다. 따뜻한 상태에서도 한꺼번에 끝낼 수 있는 양(처리량)을 넘기면 생긴다
담아 둔 값이 만료돼 다시 가져온다 준비가 없던 것이 아니라 버린 것이다. 처음 한 번으로 안 끝나고 되풀이된다

가르는 물음은 하나입니다. 같은 요청을 곧바로 한 번 더 보내면 응답이 짧아지나. 짧아지면 콜드 스타트입니다. 안 짧아지면 다른 원인입니다.

겪는 몫을 줄이는 세 갈래

줄이는 방법은 준비를 언제 치를지 옮기는 일입니다. 크게 셋으로 갈립니다.

무엇을 하나 무엇을 내주나
요청이 오기 전에 미리 띄워 두고 기다리게 한다 아무도 안 부르는 동안에도 자원을 붙든다
읽어 들일 코드와 초기화를 덜어낸다 미룬 초기화가 나중 요청으로 옮겨 간다
일정 간격으로 불러 식지 않게 한다 쉴 틈이 없어져 늘 켜 둔 것과 비슷해진다

첫째 갈래를 예열이라고 부릅니다. 미리 기계어로 옮겨 두는 AOT(Ahead Of Time) 컴파일도 같은 갈래입니다. 번역을 실행 전으로 당겨 첫 실행이 떠안을 몫을 없앱니다.

셋 모두 콜드 스타트를 없애지는 않습니다. 누가 언제 치를지를 옮길 뿐입니다.

관련 항목

콜드 스타트를 겪는 실행 단위

서버리스 · AWS Lambda · 컨테이너 · 가상 머신 · 프로세스 · JVM

콜드 스타트 동안 치르는 준비 단계

컨테이너 이미지 · 프로비저닝 · 클래스 로딩 · 초기화 · 지연 로딩 · 커넥션 풀

콜드 스타트를 만드는 코드 번역 방식

JIT 컴파일 · AOT 컴파일 · 인터프리터 · 계층형 컴파일 · 코드 캐시 · 워밍업

빈 캐시에서 콜드 스타트와 나란히 터지는 고장

캐시 미스 · 캐싱 · 캐시 스탬피드 · 썬더링 허드 · 오리진 · CDN

콜드 스타트가 드러나는 지표

응답 시간 · 꼬리 지연 · 백분위수 · 지연 · 처리량 · 서비스 수준 목표

콜드 스타트를 다시 일으키는 운영 동작

배포 · 오토스케일링 · 수평 확장 · 롤링 재시작 · 헬스 체크 · 무상태

콜드 스타트를 줄이려고 미리 채워 두는 준비

웜 스타트 · 예열 · 프리페치 · 캐시 워밍

콜드 스타트와 헷갈리는 다른 지연 원인

큐잉 이론 · 병목 · 가비지 컬렉션 · 락 경합 · 타임아웃 · 만료

다른 이름: Cold Start · cold start · 콜드스타트