dev-ops/aws

FaaS 기반 Lambda 실행 흐름과 Cold Start 최적화 기법

heemang_e 2025. 4. 20. 16:26

FaaS 기반 Lambda 실행 흐름과 Cold Start 최적화 기법

온디맨드 방식의 EC2 인스턴스의 경우 애플리케이션 자체를 VM에 올리기 때문에 API 호출이 없더라도 인스턴스가 실행되는 시간 단위로 비용이 발생한다.

 

반면에 FaaS(Function as a Service)는 애플리케이션을 함수 단위로 분리한다. FaaS는 이벤트 기반 아키텍처로써, 함수는 이벤트가 발생할 때만 실행된다. 여기서 이벤트란 클라이언트의 API 호출을 예시로 들 수 있다. 서버나 인프라를 직접 관리하지 않고, 이벤트를 처리할 수 있는 함수 코드만 작성하여 배포하면 된다. 함수는 API 호출, 파일 업로드, 스케줄링 등의 이벤트가 발생할 때만 실행되며, 호출 및 실행 시간 동안만 비용이 발생한다. FaaS의 대표적인 예시로 AWS Lambda가 있다.

graph TD
  A[사용자 요청] -->|지속적인 실행 필요| B[EC2 ]
  A -->|이벤트 발생 시| C[Lambda]

  B --> D[항상 실행 중인 서버]
  D --> E[요청 처리]

  C --> F[함수 컨테이너 실행]
  F --> G[요청 처리 후 종료]

 

Faas 환경의 실행 구조

Lambda 함수는 Firecracker MicroVM 위에서 실행되는 컨테이너 안에서 실행되고, 이 컨테이너 안에 FaaS 런타임과 함수 코드가 존재한다. 여기서 컨테이너는 Docker 컨테이너가 아닌, AWS에서 관리하는 경량 환경으로 Firecracker MicroVM 내에서 격리된 방식으로 동작한다.

graph TD
  A[물리 하드웨어 - H/W] --> B[하이퍼바이저 또는 호스트 OS]
  B --> C[Firecracker 기반 MicroVM]
  C --> D[컨테이너 환경 - Amazon Linux 기반]
  D --> E[Lambda 런타임 + 사용자 함수 코드]

 

이벤트 발생 및 처리되는 과정을 다음과 같이 도식화할 수 있다:

flowchart TD
  A[이벤트 발생] --> B[Lambda 트리거 확인]
  B --> C[Firecracker MicroVM 생성 or 재사용]
  C --> D[컨테이너 내 Amazon Linux 환경 준비]
  D --> E[Lambda 런타임 실행]
  E --> F[사용자 함수 코드 실행]

 

Warm Start를 통해 Lambda 함수는 MicroVM과 컨테이너를 재사용할 수 있다.

Lambda 함수는 Warm Start를 통해 같은 함수 코드가 짧은 시간 내에 반복적으로 호출된다면 이전에 사용했던 MicroVM과 컨테이너 실행 환경을 재사용할 수 있다. 단, AWS Lambda는 내부적으로 MicroVM과 컨테이너를 일정 시간 동안 유지해 재사용할 수 있으나, 사용자가 이를 명시적으로 제어하거나 보장할 수는 없다.

 

재사용이 발생하는 조건:

  • 같은 Lambda 함수: 함수 이름, 버전, 구성 등이 같아야 한다.
  • 짧은 시간 내의 재호출: 일정 시간 동안 트래픽이 지속되면 재사용이 가능하다.
  • 동일한 리전/기용 영역에서의 호출
flowchart TD
  A[이벤트 발생] --> B{기존 환경 존재?}

  B -- 예 --> C[MicroVM 재사용]
  C --> D[컨테이너 재사용]
  D --> E[Lambda 함수 코드 실행]

  B -- 아니오 --> F[새 MicroVM 생성]
  F --> G[컨테이너 생성]
  G --> E

 

Lambda 함수는 Cold Start 한계가 존재한다.

Warm Start는 동일한 이벤트가 트리거 되었을 때 이전에 사용한 컨테이너 환경을 그대로 사용하여 빠르게 처리할 수 있었다. 그러나 Warm Start와는 반대로 최초 호출 또는 장시간 미사용된 이벤트가 발생하면 Cold Start가 발생한다.

 

Cold Start는 이벤트의 최초 호출 또는 오랜 기간 동안 호출되지 않다가 호출된 경우 발생한다. 이벤트를 처리하기 위한 MicroVM과 컨테이너를 새로 생성해야 하고, ENI 설정, 환경변수 설정, 외부라이브러리 로딩 등 다양한 초기화 단계가 포함되므로 지연 시간이 발생한다.

 

함수가 오랜만에 호출되었을 때 cold start가 발생하는 이유는 다음과 같다:

  • 함수를 실행하기 위해 자원을 프로비저닝 하고 시작하는 데 걸리는 시간 (가상머신이나 컨테이너)
  • 함수의 런타임 환경을 구동 및 초기화하는 데 걸리는 시간 (JVM이나 인터프리터)
  • 함수 내 애플리케이션의 부트스트랩 시간 (라이브러리 컴파일과 로딩)

  • Code downloaded: 이벤트가 트리거 되었을 때 AWS에 업로드되어 있는 Lambda 함수를 다운로드한다.
  • Container starts: Lambda 함수가 실행될 환경을 구축한다.
  • Runtime bootstrapped: Lambda Runtime API가 초기화되고, 핸들러 로직을 실행할 준비를 마친 상태
  • Code starts: 이벤트를 처리하는 Lambda 함수 코드 실행

 

x축의 경우 Lambda 함수가 실행되는 컨테이너의 메모리 크기를 의미한다. 우측으로 갈수록 메모리 크기가 증가하면서 cold start 시간이 줄어드는 것을 확인할 수 있다. 메모리가 커질수록 시간이 줄어드는 이유는 AWS Lambda는 메모리 크기를 늘리면 CPU 및 네트워크 리소스도 함께 증가하도록 설계되어 있기 때문이다. 이는 더 많은 CPU 자원을 할당받아 런타임 환경 초기화 및 코드 실행 속도가 빨라지고, 이뿐만 아니라 GC, JVM 초기화 그리고 컨테이너 부팅 시간이 단축되며 전반적으로 실행 속도가 빨라지기 때문에 cold start 시간이 줄어드는 것이다.

  • 메모리를 늘리면 CPU 성능이 향상되므로 런타임 초기화 속도가 빨리진다.
  • 런타임 속도가 빨리지므로 Java 같은 무거운 언어의 VM 로딩 속도가 빨라진다.
  • 네트워크 대역폭이 향상되므로 외부 리로스 접근이 빨라진다.
AWS 공식 문서: Adding more memory proportionally increases the amount of CPU, increasing the overall computational power available. If a function is CPU, network or memory-bound, then increasing the memory setting can dramatically improve its performance.

 

그러나 자바의 경우 다른 언어들에 비해 cold start 시간이 길다. 그 이유는 JVM이라는 무거운 런타임 초기화가 필요하고, 소스 코드를 실행하기 위해 런타임 시점에 바이트코드를 해석하거나 JIT 컴파일을 수행해야 하기 때문이다.

 

자바는 소스 코드를 실행하기 위해 javac(컴파일러)가 소스 코드(.java)를 바이트 코드(.class)로 변환하는 과정이 필요하다. 이 바이트코드는 JVM의 인터프리터가 한 줄씩 읽고 기계어로 변환하여 실행한다. JIT(Just-In-TIme) 컴파일러 덕분에 매번 바이트코드를 기계어로 해석하지 않고, 자주 사용되는 바이트코드는 미리 기계어로 컴파일한 후 캐싱해 둔다. (JIT 컴파일러가 없다면 JVM이 바이트코드를 실행할 때마다 매번 기계어로 컴파일해야 한다.)

언어 Cold Start 속도 이유
Python 인터프리터 기반, 가볍고 빠르게 시작 가능
Node.js 이벤트 기반 비동기 처리, 빠른 시작
Go 정적 컴파일 언어지만, 실행 바이너리가 작고 빠름
Java / .NET 무거운 런타임, 초기화 시간이 오래 걸림

 

Cold Start 한계를 극복하기 위한 Provisioned Concurrency

Provisioned Concurrency는 Lambda 함수가 호출되기 전에 미리 지정한 수만큼 인스턴스를 준비해 두는 방법이다. 기존에는 이벤트가 트리거 될 때 함수를 실행하기 위해 컨테이너를 생성했다면, 이번에는 미리 인스턴스를 생성해 두어 cold start 없이 빠르게 실행될 수 있도록 한다. 참고로 미리 준비한 인스턴스 수를 초과하는 요청이 들어오면 여전히 cold start 문제가 발생할 수 있다.

 

기존 Lambda의 문제점:

  • cold start 발생: 함수가 처음 호출되거나 오랜 시간 동안 호출되지 않으면, 실행 환경을 새로 만들어야 해서 지연 발생
  • 일관성 없는 응답 시간: 사용자 요청 수가 급증하면 일부 요청은 cold start로 인해 느려짐
sequenceDiagram
  participant 사용자
  participant Lambda
  participant Firecracker
  participant 함수 컨테이너

  사용자->>Lambda: 함수 호출
  Lambda->>Firecracker: 이미 실행 환경 준비됨
  Firecracker->>함수 컨테이너: 준비된 상태 유지 중
  함수 컨테이너-->>Lambda: 바로 함수 실행
  Lambda-->>사용자: 빠른 응답

 

Provisioned Concurrency는 미리 인스턴스를 띄어두기 때문에, 함수가 준비 상태로 유지된 시간인 프로비저닝 시간 동안 요금이 추가로 발생한다. 또한 기존 Lambda 함수와 동일하게 Lambda 함수가 실행되는 시간에 대해 비용도 발생한다. 따라서 Provisoned Concurrency는 사용자 요청이 예측 가능하며 지연 시간에 민감한 서비스에서 사용하는 것이 적절하다.

flowchart TD
  A[기본 Lambda 호출] -->|콜드 스타트 발생 가능| B[컨테이너 생성]
  A2[Provisioned Concurrency] -->|항상 준비됨| C[즉시 실행]

  B -. 느림 .-> D[응답]
  C --> D

 


참고