Создание быстрых файловых систем для ВМ

Источник: Modal•

Создание быстрых файловых систем для ВМ

Как мы ускорили файловые системы VM Sandbox, перейдя с файлов на блоки.

В Modal мы любим думать, что кое-что смыслим в выжимании наносекунд из систем. Мы обожаем доводить наши системы до высокой производительности и функциональности с самого нуля, и мы просто обязаны это делать! Мы строим инфраструктуру для тысяч GPU, ожидающих очередной запуск обучения с подкреплением (RL), для миллионов сессий агентов, ожидающих следующий вызов инструмента, и даже для человека, ждущего компиляции своего проекта. Для нас все это сводится к одному: заставить всё работать по-настоящему быстро.

Недавно мы выпустили Песочницы на базе виртуальных машин (VM Sandboxes), поддерживающие еще более сложные агентные рабочие нагрузки в масштабе и с теми же требованиями к производительности, которые были у нашей платформы на базе gVisor. Фундаментальным строительным блоком песочницы является ее файловая система: все в песочнице так или иначе зависит от скорости ее файловой системы. Сегодня мы расскажем о том, как мы развивали наш стек файловых систем, чтобы сделать его производительным фундаментом для VM Sandboxes.

Предыстория: ImageFS и быстрые холодные старты

Мы решили проблему «файловой системы контейнеров» в апреле 2022 года, выпустив ImageFS — нашу распределенную файловую систему с ленивой загрузкой для контейнеров. Если говорить в двух словах, ImageFS работает следующим образом: она предварительно подтягивает только минимальный объем метаданных, монтирует их через сервер FUSE (Filesystem in Userspace) на Rust, а затем предоставляет контейнеру. ImageFS лениво извлекает содержимое файлов из многоуровневого кэша с адресацией по содержимому — от локальной памяти и региональных CDN вплоть до глобального облачного хранилища. ImageFS также реализует интеллектуальную предварительную выборку в фоновом режиме, поэтому обращения почти всегда попадают на файлы, которые уже находятся на локальном диске или в памяти.

ImageFS — это главная причина, почему холодный старт контейнеров Modal занимает меньше секунды. В то время как традиционные запуски Docker тратят минуты на извлечение и распаковку слоев, ImageFS должна загрузить только свой индекс метаданных перед запуском, и она справляется с этим за миллисекунды. И несмотря на то, что ImageFS работает на базе FUSE, она быстра! Учитывая ее неизменяемый (read-only) характер, ImageFS может сигнализировать ядру о необходимости агрессивно кэшировать каждый обрабатываемый запрос FUSE.

Для поддержки записи мы используем OverlayFS — стандартную файловую систему Linux, которая объединяет нижний слой только для чтения (в нашем случае ImageFS) с верхним слоем для записи. Все изменения файлов в конечном итоге попадают в копии файлов на верхнем слое.

Опора на ImageFS

Когда мы начали проектировать виртуальные машины, мы знали, что снова будем опираться на ImageFS, чтобы сохранить тот же великолепный опыт работы в песочнице, что и у наших контейнеров. Контейнеры разделяют ядро Linux с нижележащей хост-системой и поэтому могут использовать ImageFS напрямую. С другой стороны, виртуальные машины имеют полноценное «гостевое» ядро, у которого нет доступа к файловым системам хоста, включая ImageFS.

В мире виртуальных машин именно virtio-fs соединяет этот разрыв. virtio-fs — это еще одна файловая система на базе FUSE, которая перенаправляет операции с файловой системой гостевой ВМ на «демон virtio-fs» (virtiofsd) на хосте. virtiofsd принимает запросы файловой системы от гостя и безопасно применяет их к каталогу, разделяемому с гостем. Если направить этот механизм на ImageFS, виртуальная машина унаследует все ее свойства: ленивую загрузку, холодные старты быстрее секунды и многоуровневое кэширование. В собранном виде стек выглядит следующим образом:

И действительно, это сработало! Виртуальные машины загружались из тех же modal.Images, что и наши существующие контейнеры, с той же производительностью холодного старта, которую мы и ожидали. Так что мы выпустили это... верно?

Быстро загружается, медленно работает

Быстрые холодные старты — не единственный критерий жизнеспособности стека файловых систем.

В случае с virtio-fs мы просто добавили дополнительный слой поверх нашего существующего стека ImageFS. Из-за принципа «делай больше» мы ожидали, что он будет работать медленнее. Однако мы увидели, что производительность рекурсивного chown упала с четырех секунд до более чем минуты! Реальные рабочие нагрузки, такие как pnpm install и bun install, стали выполняться в три раза медленнее, а производительность ripgrep упала до нуля.

Чтобы понять, что происходит, мы разбили системные вызовы наших рабочих нагрузок bun и pnpm для создания синтетических микробенчмарков. Мы сравнили ImageFS, используемую напрямую хостом, позади gVisor и поверх virtio-fs в виртуальной машине:

Ага! stat работала в 2 раза медленнее под управлением virtio-fs, в то время как chown — более чем в 6 раз хуже. Хотя мы списали более низкую производительность stat на дополнительные накладные расходы, создаваемые virtio-fs, chown все равно нас удивил. Повторный анализ системных вызовов, на этот раз в virtiofsd, показал нам, что chown распадается на гораздо большее количество операций через virtio-fs:

  • Операция chown покидает гостевой процесс как fchownat и попадает в гостевое ядро.
  • Гостевое ядро проверяет и сбрасывает возможности файлов (file capabilities), отправляя FUSE_GETXATTR из ВМ в virtiofsd, который проверяет файл и отвечает, что стоит 3 системных вызова хоста.
  • Гостевое ядро также проверяет и сбрасывает биты setuid, отправляя FUSE_GETATTR на хост.
  • Наконец, изменение владельца уходит как FUSE_SETATTR. virtiofsd изменяет владельца и возвращает обновленные атрибуты файла.

В общей сложности один вызов chown требовал трех круговых рейсов (round-trips) из гостевой ВМ на хост, а virtiofsd совершала шесть системных вызовов хоста, каждый из которых приходился на ImageFS.

И это при том, что virtiofsd уже была настроена на максимальное кэширование и объединение операций. Файлы несут в себе «смысл», или метаданные: имена, владельцев, права доступа и т.д. Таким образом, каждая операция с метаданными должна обращаться к тому, кто является авторитетом для этих метаданных. virtio-fs размещает этот авторитет по ту сторону границы виртуальной машины, по отношению к которой гость всегда является лишь клиентом.

Другими словами, virtio-fs — это общая файловая система, даже если на стороне хоста никогда не возникает необходимости читать корневую файловую систему ВМ. Пришло время вернуться к чертежной доске.

Смена языка: разговор на языке блоков

Что произойдет, если позволить файловой системе существовать исключительно внутри гостя? Когда все метаданные интерпретируются внутри гостя, у хоста не остается иной задачи, кроме как хранить байты и возвращать их гостю при необходимости.

У такого устройства уже есть название: блочное устройство! Блоки — это язык, на котором говорят физические диски, такие как жесткие диски и SSD. Диски понятия не имеют, что такое файл; они просто обрабатывают фрагменты данных для нас. С высоты птичьего полета такие файловые системы, как ext4, являются лишь переводчиками с языка файлов на язык блоков.

Что еще более удачно для нас, ядро Linux потратило десятилетия на то, чтобы научиться отлично работать с блочными устройствами. Блочные устройства являются золотым стандартом для физических машин (через HDD и SSD) и для виртуальных машин (через блочный аналог virtio-fs: virtio-blk).

И, пожалуй, самое главное: в отличие от virtio-fs, гостевое ядро изначально рассматривает каждое доступное ему блочное устройство как находящееся в исключительном владении. Ни одна операция с файловой системой не должна обращаться к кому-либо еще по поводу состояния дел.

Благодаря этому отправка предыдущей операции chown через решение на базе virtio-blk показывает нам следующее:

  • Операция chown покидает гостевой процесс как fchownat и попадает в гостевое ядро, как и раньше.
  • Гостевое ядро проверяет и сбрасывает возможности файлов, так как драйвер ext4 находится внутри гостевого ядра.
  • Гостевое ядро проверяет и сбрасывает биты setuid, опять же без каких-либо внешних вызовов.
  • ext4 выполняет изменение владельца и записывает его в свой журнал упреждающей записи (write-ahead journal).

Хост узнает об этом только позже, когда ext4 сбрасывает пакет записей, содержащий все изменения с момента последнего коммита. Это распределяет затраты на сотни или тысячи файловых операций на одну блочную команду и происходит асинхронно, оставляя файловую систему активной в процессе.

Поскольку хост работает только с блоками, он теряет способность взаимодействовать с файловой системой виртуальной машины. Взамен мы оставляем все операции, включая назойливые обращения к метаданным, внутри гостевой системы и избегаем всех дополнительных обращений через FUSE, которые возникали бы с virtiofsd.

ImageFS-blk

Блочные устройства сделали файловую систему виртуальной машины быстрой, но как связать ее обратно с ImageFS, чтобы поддержать предоставляемые ею свойства, такие как совместимость с modal.Image и ленивая загрузка?

Наивный подход с вызовом mkfs.ext4 -d для создания отформатированного в ext4 диска и совместного использования его через virtio-blk не работает с ленивой архитектурой ImageFS. Чтобы mkfs.ext4 работала, все содержимое образа должно быть предварительно доступно.

Вместо этого мы задались вопросом: как заставить драйвер ext4 гостевой системы думать, что у него есть полностью материализованный образ, фактически обслуживая содержимое файлов по запросу? Или, говоря иначе, как применить идеи ImageFS к virtio-blk?

ext4 хранит небольшую запись для каждого файла, известную как инод (inode), которая содержит его права доступа, размер и список «экстентов» (extents) или диапазонов блоков, представляющих данные файла. Для запрошенного modal.Image мы снова загружаем заранее лишь минимальное количество метаданных файла и используем их для самостоятельного сопоставления и записи записей инодов ext4.

Мы оставляем остальную часть образа ext4 разреженной (sparse), предоставляя фактические блоки только тогда, когда гостевая система читает их. Поскольку мы сами записали все метаданные ext4, у нас уже есть все необходимое для сопоставления запросов блоков с байтами файлов ImageFS и наоборот.

Хост-демон с креативным названием ImageFS-blk обрабатывает результирующие запросы блоков через virtio-blk. Его задача — лениво передавать содержимое, используя наши сопоставления метаданных, обслуживая неизменяемый образ и сохраняя записи отдельно.

При записи: мы сохраняем блоки в разреженный файл и помечаем их как измененные в битовой карте. Разреженный файл аккумулирует только записи из поддерживаемой им виртуальной машины, и такие операции, как снимки (snapshots), просто должны обращаться к нему, чтобы выяснить, что изменилось.

При чтении: мы обращаемся к нашим сопоставлениям, чтобы преобразовать запрошенный диапазон блоков в диапазон файлов и байтов, и получаем его из ImageFS. Измененные блоки и метаданные ext4 обслуживаются напрямую из разреженного файла, аналогично OverlayFS, но на уровне блоков.

На этапе проектирования ImageFS-blk мы также поняли, что можем превзойти точку монтирования OverlayFS в Linux поверх ImageFS. Перед изменением файла в неизменяемом нижнем слое OverlayFS должна скопировать его в записываемый верхний слой. В обычной настройке это может быть дешево: когда верхний и нижний слои находятся на одной файловой системе XFS, reflink-ссылки позволяют двум копиям совместно использовать базовые данные без их физического копирования.

В нашей настройке ImageFS нижний слой представляет собой файловую систему FUSE, а верхний — XFS, что означает, что reflink-ссылки не работали. В худших случаях это приводит к гигабайтам накладных расходов на запись отдельных байтов! ImageFS-blk может выполнять «копирование при записи» (copy on write) на уровне блоков, а не на уровне файлов, ограничивая худший случай накладных расходов копированием одного блока.

С внедрением предвыборки мы вернулись к холодному запуску за доли секунды и загрузке без необходимости скачивать весь образ. И все это — получая преимущества от предоставления гостевой системе возможности управлять собственной файловой системой.

Бенчмаркинг

ImageFS-blk полностью превзошел решение virtio-fs. Глядя на тот же микробенчмаркинг системных вызовов:

Наш бенчмаркинг установки bun показал 12-кратное ускорение: с 12,5 секунд до 1,07 секунды. Он даже превзошел классическую файловую систему ImageFS, поскольку операции с метаданными в ImageFS-blk никогда не покидают гостевую систему и не проходят через путь FUSE в ImageFS. Навигация по коду и пропускная способность чтения также значительно улучшились.

Эти улучшения распространились и на рабочие нагрузки баз данных. pgbench и speedtest1 продемонстрировали масштабное увеличение скорости с ImageFS-blk, показав эффективность блочных устройств.

И мы постоянно работаем над тем, чтобы сделать наш стек файловых систем еще быстрее!

Попробуйте!

Если вы хотите протестировать все, что мы здесь создали, песочницы виртуальных машин (VM Sandboxes) теперь общедоступны и работают на базе бэкенда ImageFS-blk. Если вам интересна подобная работа, присоединяйтесь к нам!

Благодарности

Этот проект стал результатом командной работы и стал возможен только благодаря помощи Даниэля Норберга (Daniel Norberg), Стаффана Гимокера (Staffan Gimåker), Апурвы Бхагвата (Apoorva Bhagwat), Коннора Адамса (Connor Adams) и Джонатона Белотти (Jonathon Belotti).

О чём эта статья

Ещё в разделе «AI и машинное обучение»

Все →

Ещё от Modal