원문 캡처 · modal.com
원문 캡처 · modal.com

Modal은 시스템에서 나노초를 줄이는 일을 중요하게 여긴다. 수천 개의 GPU가 강화학습 롤아웃을 기다리고, 수백만 개의 에이전트 세션이 다음 도구 호출을 기다리며, 프로젝트를 컴파일하려는 사람이 기다리는 상황에서 시스템을 처음부터 빠르고 유능하게 만들어야 하기 때문이다. Modal은 이 모든 경우에 필요한 목표가 같다고 설명한다. 시스템을 가능한 한 빠르게 만드는 것이다.

Modal은 최근 VM Sandboxes를 출시해 더 복잡한 에이전트 작업을 대규모로 지원하기 시작했다. 이 기능에도 gVisor 기반 플랫폼에서 기대하던 성능 수준을 적용했다. Sandbox의 기본 구성 요소 가운데 하나는 파일시스템이다. Sandbox에서 하는 일은 어떤 식으로든 파일시스템 속도에 영향을 받는다. Modal은 VM Sandboxes의 기반이 되는 파일시스템을 어떻게 발전시켰는지 설명한다.

ImageFS와 빠른 콜드 스타트

Modal은 2022년 4월 컨테이너 파일시스템 문제를 해결하기 위해 분산형 지연 로딩 파일시스템인 ImageFS를 출시했다. ImageFS는 우선 최소한의 메타데이터만 가져와 Rust 기반 FUSE(Filesystem in Userspace) 서버로 마운트한 다음 컨테이너에 제공한다. 파일 내용은 로컬 메모리부터 지역 CDN, 글로벌 블롭 저장소에 이르는 계층형 콘텐츠 주소 지정 캐시에서 필요할 때 가져온다. 백그라운드에서 영리한 프리페칭도 수행하므로 파일 접근은 거의 항상 로컬 디스크나 메모리에 이미 있는 파일을 이용한다.

ImageFS는 Modal 컨테이너가 1초 이내에 콜드 스타트하는 데 큰 역할을 한다. 기존 Docker 실행은 레이어를 가져와 압축을 푸는 데 몇 분이 걸리지만, ImageFS는 시작 전에 메타데이터 색인만 밀리초 단위로 불러오면 된다. FUSE 기반인데도 빠르다. 읽기 전용이기 때문에 ImageFS는 커널에 자신이 처리한 모든 FUSE 요청을 적극적으로 캐시하도록 알릴 수 있다.

쓰기를 지원하기 위해 Modal은 표준 리눅스 파일시스템인 OverlayFS를 사용한다. OverlayFS는 읽기 전용 하위 계층(이 경우 ImageFS)과 쓰기 가능한 상위 계층을 합친다. 파일을 변경하면 해당 파일의 복사본이 상위 계층에 놓인다.

읽기는 OverlayFS를 거쳐 ImageFS로 전달된다. 파일에 처음 쓰면 쓰기 가능한 상위 계층으로 파일이 복사된다.

ImageFS를 VM에서 사용하기

Modal은 VM을 설계하기 시작했을 때 컨테이너와 같은 Sandbox 경험을 제공하기 위해 ImageFS를 다시 활용할 계획이었다. 컨테이너는 기반이 되는 호스트 시스템과 리눅스 커널을 공유하므로 ImageFS를 직접 사용할 수 있다. 반면 VM에는 완전한 게스트 커널이 있어 ImageFS를 비롯한 호스트 파일시스템에 접근할 수 없다.

VM에서는 virtio-fs가 이 간극을 연결한다. virtio-fs는 FUSE 기반 파일시스템으로, 게스트 VM의 파일시스템 작업을 호스트의 virtiofsd 데몬에 전달한다. virtiofsd는 게스트의 파일시스템 요청을 받아 게스트와 공유된 디렉터리에서 안전하게 처리한다. 이를 ImageFS에 연결하면 VM도 지연 로딩, 1초 미만의 콜드 스타트, 계층형 캐시를 이용할 수 있다.

실제로 이 방식은 작동했다. VM은 기존 컨테이너와 같은 modal.Image에서 부팅했고, 기대했던 콜드 스타트 성능도 나왔다. 하지만 부팅이 빠르다고 파일시스템을 사용하기에도 빠른 것은 아니었다.

빠르게 부팅하지만 사용은 느린 문제

virtio-fs를 추가하면서 기존 ImageFS 스택 위에 계층 하나가 더해졌다. 따라서 느려질 것이라고 예상했지만, 재귀 chown 작업은 4초에서 1분 이상으로 늘어났다. 실제 작업인 pnpm install과 bun install은 세 배가량 느려졌고, ripgrep 성능도 크게 떨어졌다.

원인을 파악하기 위해 bun과 pnpm 작업의 시스템 호출을 분석하고 합성 마이크로벤치마크를 만들었다. 호스트에서 직접 사용하는 ImageFS, gVisor 뒤에서 사용하는 ImageFS, VM의 virtio-fs를 거치는 ImageFS를 비교했다.

virtio-fs에서 stat은 두 배 느렸고 chown은 여섯 배 넘게 느렸다. stat 저하는 virtio-fs가 추가하는 오버헤드로 설명할 수 있었지만, chown 결과는 예상보다 나빴다. virtiofsd에서 시스템 호출을 다시 추적하자 virtio-fs를 통과하는 chown이 훨씬 많은 작업으로 나뉘는 것을 확인했다.

  • chown은 게스트 프로세스에서 fchownat으로 전달된 뒤 게스트 커널로 들어간다.
  • 게스트 커널은 파일 기능(capability)을 확인하고 지우기 위해 FUSE_GETXATTR 요청을 VM 밖의 virtiofsd로 보낸다. virtiofsd는 파일을 확인해 응답하며, 이 과정에서 호스트 시스템 호출 세 번이 발생한다.
  • 게스트 커널은 setuid 비트도 확인하고 지우기 위해 FUSE_GETATTR를 호스트에 보낸다.
  • 마지막으로 소유권 변경 요청이 FUSE_SETATTR로 전달된다. virtiofsd는 소유자를 변경한 뒤 갱신된 파일 속성을 반환한다.

결과적으로 chown 한 번에 게스트 VM과 호스트 사이를 세 차례 왕복해야 했고, virtiofsd는 ImageFS에 도달하는 호스트 시스템 호출을 여섯 번 수행했다. 이는 virtiofsd가 작업을 최대한 캐시하고 합치도록 이미 조정된 상태에서 나온 수치다.

파일에는 이름, 소유자, 권한 같은 메타데이터가 있다. 메타데이터 작업은 그 상태를 관리하는 주체에게 확인해야 한다. virtio-fs에서는 그 주체가 VM 경계 너머에 있고, 게스트는 언제나 요청을 보내는 쪽이다.

즉, 호스트가 VM의 루트 파일시스템을 읽을 필요가 전혀 없어도 virtio-fs는 공유 파일시스템처럼 동작한다. Modal은 설계를 다시 검토하기로 했다.

파일 대신 블록으로 통신하기

파일시스템을 게스트 안에서만 작동하게 하면 어떨까? 메타데이터 해석을 모두 게스트에서 처리하면 호스트는 바이트를 저장하고 필요할 때 게스트에 돌려주는 일만 하면 된다.

이 방식에는 이미 이름이 있다. 바로 블록 장치다. 하드 드라이브와 SSD 같은 물리 드라이브는 블록 단위로 데이터를 다룬다. 드라이브는 파일이 무엇인지 알지 못하고 데이터 덩어리만 처리한다. ext4 같은 파일시스템은 파일의 언어를 블록의 언어로 바꾸는 번역기라고 볼 수 있다.

리눅스 커널은 블록 장치를 다루는 데 수십 년 동안 발전해 왔다. 블록 장치는 HDD와 SSD를 쓰는 물리 머신의 표준이며, VM에서는 virtio-fs의 블록 기반 대응 기술인 virtio-blk를 사용한다.

무엇보다 virtio-fs와 달리 게스트 커널은 자신이 접근할 수 있는 블록 장치를 전용으로 소유한다고 본다. 따라서 파일시스템 작업을 위해 다른 주체에게 상태를 물어볼 필요가 없다.

virtio-blk 방식에서 앞서 살펴본 chown은 다음과 같이 처리된다.

  • chown은 이전과 마찬가지로 게스트 프로세스에서 fchownat으로 전달된 뒤 게스트 커널로 들어간다.
  • ext4 드라이버가 게스트 커널 안에 있으므로 게스트 커널이 파일 기능을 확인하고 지운다.
  • 게스트 커널은 setuid 비트도 확인하고 지우며, 이때도 외부에 요청하지 않는다.
  • ext4는 소유권을 바꾸고 변경 내용을 선행 기록 저널에 기록한다.

호스트는 ext4가 마지막 커밋 이후의 변경 사항을 담은 쓰기 묶음을 플러시할 때 나중에 이를 확인한다. 이렇게 하면 파일 작업 수백 개에서 수천 개의 비용을 블록 명령 하나에 나눠 담을 수 있다. 이 작업은 비동기적으로 이뤄지므로 파일시스템은 계속 동작한다.

virtio-blk에서 chown 한 번은 ext4 저널까지 호출당 약 2.1마이크로초가 걸리며, 저널 내용은 묶음으로 비동기 커밋된다.

호스트는 블록만 다루기 때문에 VM 파일시스템을 직접 처리할 수 없다. 대신 메타데이터 왕복을 비롯한 모든 작업을 게스트 안에 두고 virtiofsd에서 발생하는 추가 FUSE 왕복을 피할 수 있다.

ImageFS-blk

블록 장치로 VM 파일시스템을 빠르게 만들 수는 있지만, modal.Image 호환성과 지연 로딩처럼 ImageFS가 제공하던 기능은 어떻게 유지할 수 있을까?

간단한 방법은 mkfs.ext4 -d를 실행해 ext4 형식의 디스크를 만들고 virtio-blk로 공유하는 것이다. 하지만 ImageFS의 지연 로딩 구조에는 맞지 않는다. mkfs.ext4를 실행하려면 이미지 내용이 처음부터 모두 준비돼 있어야 하기 때문이다.

Modal은 이미지가 실제로 모두 준비되지 않은 상태에서도 게스트의 ext4 드라이버가 완성된 이미지라고 여기게 하면서 파일 내용을 요청 시 제공할 방법을 찾았다. 다시 말해 ImageFS의 아이디어를 virtio-blk에 적용하려 했다.

ext4는 각 파일에 inode라는 작은 기록을 유지한다. inode에는 파일 권한과 크기, 파일 데이터가 있는 블록 범위인 익스텐트 목록이 들어 있다. 요청된 modal.Image에 대해 Modal은 필요한 최소한의 파일 메타데이터만 먼저 가져온 뒤, 이를 바탕으로 ext4의 inode 기록을 직접 구성해 쓴다.

나머지 ext4 이미지는 희소 상태로 두고 게스트가 읽을 때 실제 블록을 제공한다. ext4 메타데이터를 직접 작성했으므로 블록 요청을 ImageFS의 파일 바이트로, 또는 그 반대로 변환하는 데 필요한 정보도 이미 갖고 있다.

ImageFS-blk라는 호스트 데몬이 virtio-blk를 통해 들어오는 블록 요청을 처리한다. 메타데이터 매핑을 이용해 내용을 지연 로딩하고, 변경할 수 없는 이미지의 내용을 제공하며, 쓰기는 별도로 저장한다.

  • 쓰기 요청이 오면 블록을 희소 파일에 저장하고 비트맵에서 변경된 블록으로 표시한다. 희소 파일에는 해당 VM이 쓴 내용만 쌓인다. 스냅샷 같은 작업은 이 파일을 확인해 무엇이 달라졌는지 파악한다.
  • 읽기 요청이 오면 매핑을 확인해 요청된 블록 범위를 파일과 바이트 범위로 변환한 뒤 ImageFS에서 가져온다. 변경된 블록과 ext4 메타데이터는 OverlayFS와 비슷하게 희소 파일에서 직접 제공한다. 다만 파일이 아니라 블록 단위로 처리한다.

ImageFS-blk는 각 블록을 분기한다. 쓰인 블록은 희소 파일에서, 바뀌지 않은 이미지 블록은 캐시 계층에서 제공한다.

ImageFS-blk를 설계하면서 Modal은 ImageFS 위에 마운트한 리눅스 OverlayFS보다 더 나은 성능을 낼 방법도 찾았다. OverlayFS는 변경할 수 없는 하위 계층의 파일을 수정하기 전에 쓰기 가능한 상위 계층으로 복사해야 한다. 일반적인 환경에서는 상위 계층과 하위 계층이 같은 XFS 파일시스템에 있을 때 reflink를 이용해 데이터를 실제로 복사하지 않고 두 복사본이 공유할 수 있어 비용이 적다.

하지만 ImageFS 환경에서 하위 계층은 FUSE 파일시스템이고 상위 계층은 XFS이므로 reflink를 사용할 수 없었다. 극단적인 경우에는 바이트 하나를 쓰기 위해 기가바이트 단위의 복사가 발생할 수 있다. ImageFS-blk는 파일 전체가 아니라 블록 단위로 기록 중 복사(copy on write)를 수행하므로 최악의 경우에도 블록 하나를 복사하는 수준으로 오버헤드를 제한한다.

프리페칭을 적용한 뒤에는 전체 이미지를 내려받지 않고도 1초 미만의 콜드 스타트를 다시 달성했다. 동시에 게스트가 자체 파일시스템을 관리하는 이점도 얻었다.

벤치마크

Modal의 비교에서 ImageFS-blk는 virtio-fs 방식보다 훨씬 나은 결과를 냈다. 같은 시스템 호출 마이크로벤치마크에서 bun install 시간은 12.5초에서 1.07초로 12배 줄었다. 메타데이터 작업이 게스트를 벗어나지 않고 ImageFS의 FUSE 경로도 통과하지 않기 때문에 기존 ImageFS보다도 빨랐다. 코드 탐색과 읽기 처리량도 크게 개선됐다.

데이터베이스 작업에서도 개선이 나타났다. pgbench와 speedtest1에서 큰 폭의 성능 향상을 보여 블록 장치의 효율을 확인했다고 Modal은 설명한다. Modal은 파일시스템 스택을 더 빠르게 만드는 작업을 계속하고 있다고 밝혔다.

VM Sandboxes는 ImageFS-blk를 기반으로 일반 공개됐다.

프로젝트에는 다니엘 노버그(Daniel Norberg), 스태판 기마케르(Staffan Gimåker), 아푸르바 바그왓(Apoorva Bhagwat), 코너 애덤스(Connor Adams), 조너선 벨로티(Jonathon Belotti)가 참여했다.