Vercel 샌드박스(Sandbox) 스냅샷(snapshot)을 활용하면 전체 파일시스템을 저장하고 원하는 시점으로 복원할 수 있습니다. 병렬 다운로드, 스트리밍 압축 해제, 로컬 NVMe 캐싱을 도입해 스냅샷 복원 속도를 어떻게 끌어올렸는지 소개합니다.
최근 Vercel 샌드박스에 파일시스템 스냅샷 기능을 출시했습니다. 이 기능으로 샌드박스의 전체 파일시스템 상태를 저장하고 원하는 시점으로 복원할 수 있습니다. 스냅샷은 안정적이고, 사용하기 쉽고, 빨라야 합니다. 초기에는 안정성에 집중해 스냅샷 저장, 복원, 데이터 손실이 절대 발생하지 않도록 하는 데 주력했습니다. 하지만 아무리 안정적이어도 속도가 너무 느리면 아무도 쓰지 않습니다. 당시 p75 스냅샷 복원 시간은 40초를 넘었습니다. 병렬화와 로컬 캐싱을 도입한 지금은 1초 미만으로 줄였습니다.
Vercel 샌드박스는 내부 빌드 제품인 Hive와 동일한 인프라 위에서 실행됩니다. 각 샌드박스는 Firecracker microVM 내부의 격리된 컨테이너입니다.
스냅샷은 샌드박스 디스크의 압축 복사본입니다. 두 가지 파일을 다룹니다:
수 GB에 달할 수 있는 원시 디스크 이미지(.img)
S3에 업로드·다운로드되는 자체 VHS 포맷(Vercel Hive Snapshot)의 압축 파일
sandbox.snapshot()를 호출하면 .img를 .vhs로 압축해 S3에 업로드합니다. 스냅샷과 함께 Sandbox.create()를 호출하면 .vhs를 다운로드해 압축을 해제합니다. 압축을 통해 파일 크기를 줄일 수 있는데, 수백 MB에서 수 GB 규모의 데이터를 네트워크로 전송할 때 이 차이가 체감됩니다.
안정성 문제를 해결하고 나서 성능 개선으로 눈을 돌렸습니다. 복원 과정은 답답할 정도로 순차적이었습니다. S3에서 .vhs 파일 전체를 단일 요청으로 다운로드하고, 완료될 때까지 기다린 뒤, 단일 스레드로 압축을 해제했습니다.
스냅샷 크기는 200MB에서 수 GB에 달하기 때문에, 단일 S3 다운로드만으로도 수 초에서 수십 초가 걸릴 수 있었습니다. Range HTTP 헤더를 활용해 청크를 병렬로 다운로드하는 방식으로 전환했습니다. AWS Go SDK에는 이를 위한 transfermanager API가 내장되어 있습니다. 여러 동시성 및 청크 크기를 벤치마킹한 결과, 다운로드 속도를 2~5배 향상시킬 수 있었습니다.
다음은 압축 해제 차례였습니다. .vhs 포맷은 헤더와 디스크 이미지의 각 할당 영역에 해당하는 프레임으로 구성됩니다. 프레임을 하나씩 순차적으로 디코딩·압축 해제하는 대신, 하나의 디코더와 N개의 압축 해제 고루틴(goroutine)을 함께 사용하는 방식으로 바꿨습니다. 그 결과 스냅샷 크기에 따라 .vhs에서 .img 복원 속도가 2~4배 빨라졌습니다.
다운로드와 압축 해제를 모두 병렬화했지만, 아직 한 가지 최적화가 남아 있었습니다. 중간 파일을 디스크에 기록하거나 전체 다운로드가 완료될 때까지 기다리지 않고, S3 범위 요청(range request) 스트림을 압축 해제 파이프라인에 바로 연결했습니다. 이를 통해 엔드투엔드 복원 시간을 추가로 2배 단축했습니다.
눈치채셨겠지만, 지금까지의 이야기는 모두 캐시 미스(cache miss) 상황, 즉 S3에서 스냅샷을 가져와야 하는 느린 경로를 개선하는 내용이었습니다. 사실 빠른 경로 자체가 없었습니다. 전부 캐시 미스였던 거죠. 처음에는 정말 성능에 신경을 쓰지 않았습니다.
샌드박스는 NVMe 디스크가 장착된 베어메탈 인스턴스에서 실행됩니다. 수 테라바이트에 달하는 빠른 로컬 스토리지가 대부분 활용되지 않은 채로 있었던 셈입니다.
항목 수가 아닌 전체 디스크 공간을 기준으로 크기를 정하고, LRU(least recently used, 가장 오래 미사용) 방식의 로컬 디스크 캐시를 추가했습니다. 압축된 .vhs가 아닌 압축 해제된 .img를 직접 캐싱하기 때문에, 캐시 히트(cache hit) 시 다운로드와 압축 해제 과정을 모두 건너뜁니다. 캐시가 가득 차면 가장 오래 사용되지 않은 스냅샷부터 제거해 공간을 확보합니다.
대부분의 고객이 여러 샌드박스에서 재사용하는 "베이스" 스냅샷을 보유하고 있어 캐시 히트율이 95%에 달합니다. 캐시 히트 시 부팅 시간은 microVM과 컨테이너를 시작하는 시간에만 좌우됩니다.
p75는 40초에서 1초 미만으로, p95는 50초에서 5초로 줄었습니다. 높은 캐시 히트율 덕분에 대부분의 샌드박스 부팅은 다운로드와 압축 해제 파이프라인을 완전히 건너뜁니다.
더 많은 아이디어를 검토 중입니다. 캐시 어피니티(cache affinity)를 적용하면 샌드박스를 요청한 스냅샷이 이미 캐싱된 베어메탈 인스턴스로 라우팅해, 인기 있는 스냅샷의 콜드 패스(cold path)를 사실상 없앨 수 있습니다. 다만 썬더링 허드(thundering herd) 문제와 특정 머신의 핫스팟(hotspot) 현상이 발생할 수 있어 신중하게 접근하고 있습니다.
장기적으로는 캐싱이 필수가 아닌 보너스로 느껴질 만큼 콜드 패스 자체를 충분히 빠르게 만드는 것이 목표입니다.
이러한 최적화는 현재 베타로 제공 중인 자동 퍼시스턴스(Automatic Persistence)에 이미 적용되어 있습니다. 이름이 지정된 샌드박스를 중지하면 파일시스템이 자동으로 스냅샷으로 저장되고, 재개 시 복원됩니다. 1초 미만의 복원 속도 덕분에 이 과정이 즉각적으로 느껴집니다.
파일시스템 스냅샷은 모든 Vercel 샌드박스에서 지금 바로 사용할 수 있습니다. 시작하려면 샌드박스 문서를 확인하세요.