필요한 블록만 불러오는 Lambda 컨테이너 로딩 (On-demand Container Loading, USENIX ATC 2023)
AWS Lambda 가 최대 10GiB 컨테이너 이미지를 쓰면서도 함수를 수십 밀리초 만에 띄우기 위해, 이미지를 블록 단위 조각으로 나누고 암호화한 채 중복을 제거하며 3단계 캐시로 필요한 부분만 불러오는 방법을 설명한 논문입니다.
출처
| 항목 | 내용 |
|---|---|
| 원제 | On-demand Container Loading in AWS Lambda |
| 저자 | Marc Brooker, Mike Danilov, Chris Greenwood, Phil Piwonka |
| 실린 곳 | Proceedings of the 2023 USENIX Annual Technical Conference (USENIX ATC '23), 315-328쪽 |
| 발표 | 2023년 7월 |
| DOI | 없음 (USENIX 는 DOI 를 부여하지 않음). arXiv 판: arXiv:2305.13162 |
| 원문 | USENIX 발표 페이지와 공개 PDF |
| 이용 조건 | USENIX 공개 (Open Access). 저작권은 저자에게 있음 |
참고 이 페이지는 원문 번역이 아니라 한국어 해설이에요. 정확한 표현과 수치는 원문을 확인하세요.
한눈에
- 문제: Lambda 는 2020년부터 최대 10GiB 의 컨테이너 이미지를 함수로 받습니다. 한 고객이 초당 15,000 개의 새 컨테이너를 띄우는데 매번 10GiB 를 내려받는다면 150Pb/s 의 대역폭이 필요합니다.
- 제안: 이미지를 결정적인 ext4 파일 시스템 하나로 펼친 뒤 512KiB 조각으로 자르고, 조각마다 내용 기반 암호화를 해 고객 사이에서도 안전하게 중복을 제거합니다. 함수가 시작할 때는 실제로 읽는 블록만 작업자 캐시, AZ 캐시, S3 의 3단계에서 불러옵니다.
- 결과: 일주일 관찰한 결과 조각 요청의 중앙값 기준 67% 가 작업자 캐시, 32% 가 AZ 캐시에서 처리되었고 S3 까지 간 것은 0.06% 였습니다.
- 지금의 의미: Lambda 가 수 GiB 짜리 컨테이너 이미지로도 함수를 빠르게 시작하는 이유를 설명하며, 이후 Lambda SnapStart 의 기반 기술과도 이어집니다.
왜 나왔나
예전 Lambda 는 함수 코드를 최대 250MiB 의 zip 파일로 받았고, MicroVM 을 시작하기 전에 전체를 내려받아 풀었습니다. 컨테이너 이미지를 받으면서 크기 한도가 40배로 늘어나자 이 방식은 불가능해졌습니다. 목표는 한 고객이 초당 최대 15,000 개의 새 컨테이너를 만들고, 시작 시간이 최소 50ms 수준이 되는 것이었습니다.
저자들은 세 가지 관찰 덕분에 문제가 풀릴 수 있다고 봅니다. 첫째, 같은 이미지가 반복해서 쓰이니 캐시가 잘 듣습니다. 둘째, 많은 이미지가 같은 베이스 레이어(운영체제, 언어 런타임) 를 공유합니다. 셋째, 컨테이너가 시작할 때 실제로 읽는 데이터는 이미지의 일부입니다. 논문은 선행 연구(Harter 등) 를 인용해 시작 시 필요한 데이터가 6.4% 에 불과하다고 소개합니다.
핵심 아이디어
파일이 아니라 블록 단위로 (2절)
Slacker 나 Starlight 같은 기존 연구는 파일 시스템 수준에서 지연 로딩을 했습니다. 하지만 파일 시스템 코드를 신뢰 경계 안에 넣으면 공격 면이 넓어집니다. Lambda 는 블록 수준을 택했습니다.
함수를 만들 때 컨테이너 레이어들을 하나의 ext4 파일 시스템으로 펼칩니다. 이 과정은 직렬로, 타임스탬프까지 고정해 결정적으로 수행하므로 같은 입력이면 언제나 같은 블록이 나옵니다. 그다음 이미지를 고정 크기 512KiB 조각으로 자르고, 내용으로 이름을 붙여 S3 에 저장합니다(그림 3, 4).
작업자에서는 로컬 에이전트가 이 조각들을 FUSE 를 통해 블록 장치처럼 보여주고, Firecracker 가 이를 virtio-blk 로 게스트에 넘깁니다. 게스트가 블록을 읽는 순간 해당 조각을 가져옵니다. 쓰기는 페이지별 암호화된 쓰기 시 복사(copy-on-write) 계층으로 가므로, 공유되는 조각은 절대 바뀌지 않습니다.
서로 믿지 않는 중복 제거 (3절)
실제 업로드를 보면 새 이미지의 약 80% 는 고유한 조각이 하나도 없었습니다. CI/CD 가 같은 이미지를 반복해서 올리기 때문입니다. 나머지 20% 도 고유 조각 비율이 평균 4.3%, 중앙값 2.5% 에 그쳤습니다(그림 5). 중복을 제거하면 저장 공간을 최대 23배 줄일 수 있습니다.
문제는 서로 다른 고객의 데이터를 안전하게 합치는 것입니다. 해답은 수렴 암호화(convergent encryption) 입니다. 각 조각의 SHA-256 해시에서 키를 만들어 그 조각을 암호화하므로, 같은 내용은 같은 암호문이 되어 중복 제거가 가능하고, 내용을 모르는 사람은 풀 수 없습니다. 어떤 조각들이 어떤 키로 이미지를 이루는지 적은 매니페스트는 고객별로 AWS KMS 키로 따로 암호화합니다. 16GiB 이미지의 매니페스트가 3MiB 미만이라 오버헤드는 0.02% 입니다. 압축은 부채널 위험과 작은 이득 때문에 쓰지 않았습니다.
중복 제거는 위험도 키웁니다. 인기 있는 조각 하나가 손상되면 수많은 함수가 영향을 받습니다. 그래서 키 유도에 "솔트" 를 섞어 조각이 공유되는 범위를 일부러 제한합니다. 솔트는 시간, 조각의 인기도, AZ 같은 배치에 따라 달라집니다. 삭제는 세대별 "루트" 를 두어 처리합니다. 오래된 루트를 은퇴시키면서 살아 있는 매니페스트와 조각을 새 루트로 옮기고, 은퇴한 루트에 접근이 생기면 경보를 울리고 삭제를 자동으로 멈춥니다(그림 6).
3단계 캐시 (4절)
캐시는 작업자 로컬 캐시(L1), 가용 영역 단위 분산 캐시(L2), 원본인 S3 로 이루어집니다. L2 는 HTTP/2 기반이며 메모리와 플래시 두 층에 LRU-k 교체 정책을 씁니다. L2 적중의 중앙값 지연은 550µs 로, S3 의 36ms 보다 훨씬 빠릅니다.
L2 에는 4-of-5 이레이저 코딩을 적용했습니다. 조각을 5개로 나눠 저장하고 그중 아무 4개만 받으면 복원합니다. 저장 공간과 요청 수는 25% 늘지만, 가장 느린 서버 하나를 기다리지 않아도 되어 꼬리 지연이 줄고, 캐시 노드 하나가 죽어도 적중률이 떨어지지 않습니다(그림 9).
캐시에는 위험도 있습니다. 캐시가 비면 갑자기 S3 로 요청이 몰려 시스템이 회복하지 못하는 준안정(metastable) 상태에 빠질 수 있습니다. 그래서 동시성 제한을 두고, 빈 캐시에서 시작하는 상황을 정기적으로 시험합니다. 캐시 크기는 "5분 규칙" 을 바탕으로 정하되 적중률 하한을 둡니다.
구현과 운영 경험 (5절)
로컬 에이전트와 두 캐시는 Rust(tokio, hyper 등) 로 작성했습니다. 이레이저 코딩의 패리티 계산은 Rust 컴파일러의 자동 벡터화가 불안정해, 코드를 다시 구성해 바이트 단위 처리보다 최대 약 10배 빠르게 만들었습니다. FUSE 의 문맥 전환 비용 때문에 userfaultfd 와 mmap 기반으로 옮기는 작업도 진행 중이라고 밝혔습니다.
실험과 결과
- 캐시 계층별 처리 비율 (4절): 큰 리전 하나를 일주일 관찰했을 때 중앙값 기준 67% 가 L1, 32% 가 L2, 0.06% 가 S3 에서 처리되었습니다. L2 의 적중률 중앙값은 99.9% 였습니다.
- L2 서버 지연 (5절): GET 의 중앙값은 50µs 미만, PUT 은 중앙값 125µs, p99 300µs 미만이었습니다.
- 로컬 에이전트 읽기 지연 (5절): 분포가 여러 봉우리로 나뉩니다. 로컬 적중은 100µs 미만, L2 적중은 약 2.75ms, 드물게 S3 까지 가는 경우가 있습니다.
- 중복 제거 (그림 5): 업로드의 약 80% 가 고유 조각 0개, 나머지도 고유 조각 비율 중앙값 2.5% 였습니다.
7절에서 저자들은 컨테이너를 "대규모 정적 링크" 에 비유하며, 개발자가 의존성을 더 가볍게 묶을 수 있도록 도와야 한다고 말합니다. 또한 캐시는 준안정성과 여러 봉우리의 지연 분포를 낳는다는 점, MicroVM 이 운영체제 연구의 좋은 도구라는 점을 교훈으로 꼽습니다.
지금 AWS 에서는
- AWS Lambda 컨테이너 이미지 지원(최대 10GB) 이 이 논문이 설명하는 시스템입니다.
- 논문은 향후 과제로 메모리 스냅숏을 쓰는 Lambda SnapStart 에 같은 기술을 쓰는 방향을 언급합니다. SnapStart 는 Java 로 시작해 이후 Python 과 .NET 으로 확대되었습니다.
- Lambda 의 격리 기반은 Firecracker 이고, 이 논문의 블록 장치도 Firecracker 의 virtio-blk 로 게스트에 연결됩니다.
함께 읽기
- AWS Lambda: 이 사이트의 기능 문서
- Firecracker 논문 해설
- Marc Brooker 의 블로그 글
- arXiv 판 원문