Репозиторий проекта весит почти 40 ГБ, а Pods, node_modules и DerivedData вместе тянут больше, чем сам исходный код. Открываете Xcode локально, проект смонтирован через SMB-шару на удалённом облачном Mac — и индексация крутится больше минуты, а переключение ветки подвешивает всё так, что начинаешь подозревать обрыв сети. Это первая стена, в которую упирается почти каждый, кто переносит окружение разработки на облачный Mac. Монтирование сетевой шары кажется самым простым решением, но на практике выясняется, что паттерны доступа к файлам в крупном iOS-проекте органически не сочетаются с сетевой файловой системой. В этой статье — как мы настроили двустороннюю синхронизацию между локальной машиной и удалённым нодом HireVPS через Mutagen, чтобы вернуть ощущение работы «как локально».
Почему монтирование шары не годится для крупных iOS-проектов
NFS и SMB спроектированы под сценарий «редкий доступ к большим файлам», а именно этим Xcode-индексация, SourceKit и сканирование CocoaPods-кеша заниматься не будут — наоборот, они делают stat, open, close по десяткам тысяч мелких файлов за короткий промежуток времени. Каждая такая операция — это сетевой round-trip, и даже при задержке 20 мс умножение на десятки тысяч операций даёт задержки на минуты. Хуже того — обработка обрывов связи: при малейшей нестабильности соединения точка монтирования просто зависает, терминал и редактор замирают одновременно, и остаётся только форсированно отмонтировать и смонтировать заново.
Наглядное сравнение трёх распространённых подходов:
| Подход | Актуальность данных | Работа с массой мелких файлов | Восстановление после разрыва | Область применения |
|---|---|---|---|---|
| Монтирование SMB/NFS | Высокая (видно сразу актуальное) | Плохо, каждый файл — сетевой round-trip | Плохо, легко зависает | Редкие операции с большими файлами |
| rsync по расписанию | Низкая, есть окно синхронизации | Средне, пакетная передача эффективна | Хорошо, задачу можно просто перезапустить | Периодические бэкапы, одностороннее пуш-обновление |
| Двусторонняя синхронизация Mutagen | Почти в реальном времени | Хорошо, локальный кеш + инкрементальная передача | Хорошо, автопереподключение и докачка | Непрерывная совместная разработка |
Один урок, который мы получили на своём опыте: ради простоты сначала просто смонтировали шару — и шаг «Building workspace» в Xcode стал занимать в среднем на 15–25 секунд дольше. В масштабе команды на одно только ожидание индексации ежедневно уходило заметное время. После перехода на Mutagen эта задержка практически исчезла.
Настройка двусторонней синхронизации через Mutagen
Установка и первое подключение
Mutagen — это единый бинарник: ни на локальной машине, ни на облачном Mac не нужны дополнительные постоянно работающие сервисы, агент автоматически разворачивается на удалённой стороне при установке соединения. Сначала устанавливаем локально:
brew install mutagen-io/mutagen/mutagen
mutagen version
Предполагается, что на облачном Mac уже настроен SSH, а учётные данные пришли в письме при получении доступа. Создаём сессию синхронизации:
mutagen sync create \
--name=ios-app \
--ignore-vcs \
--symlink-mode=posix-raw \
/Users/me/Projects/ios-app \
ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app
Флаг --ignore-vcs автоматически пропускает внутренние объекты .git (сам git синхронизируется по собственному протоколу эффективнее, дублировать эту работу через Mutagen не нужно), а --symlink-mode=posix-raw сохраняет символические ссылки, характерные для CocoaPods, без преобразования.
Правила исключений: какие каталоги нельзя синхронизировать никогда
В корне проекта обязательно нужен файл .mutagenignore, иначе при первой же синхронизации улетят и несколько гигабайт кеш-директорий:
DerivedData/
Pods/
.build/
node_modules/
*.xcuserstate
xcuserdata/
.DS_Store
Общая черта этих каталогов — их можно локально пересобрать на любой из сторон. Синхронизация таких директорий не только впустую тратит трафик, но и может привести к проблемам, описанным в следующем разделе.
Подводные камни, связанные с Xcode
- DerivedData синхронизировать категорически нельзя: кеш модулей (ModuleCache.noindex) и индекс сборки жёстко привязаны к абсолютным путям и архитектуре конкретной машины. Синхронизация на другую машину практически гарантированно вызывает полную повторную индексацию, а в тяжёлых случаях — необъяснимые ошибки компиляции. Пусть каждая сторона генерирует эту директорию независимо — она в принципе одноразовый артефакт.
- Pods и node_modules устанавливаются отдельно на каждой стороне: синхронизировать нужно только
Podfile.lockиpackage-lock.jsonв исходниках, а сами директории зависимостей на каждой стороне поднимаются отдельным запускомpod installиnpm install— это экономит трафик и избавляет от несовместимости бинарных артефактов между архитектурами. .gitignoreи.mutagenignoreдолжны быть согласованы: если два списка исключений расходятся, git будет показывать «нет изменений», а Mutagen тем временем в фоне гонит кучу бессмысленных файлов — разбираться с этим потом долго и муторно. Стоит завести небольшой скрипт, который после каждого изменения сравнивает эти два списка.- Разница в правах доступа: UID локального пользователя и пользователя на облачном Mac отличаются, и при ошибках прав доступа первым делом стоит проверить, не потерялся ли бит исполняемости при синхронизации. Флаг
--permissions-mode=portableснимает большинство таких проблем.
Реальная производительность и разрешение конфликтов
В повседневной работе инкрементальная синхронизация, запускаемая одним сохранением (изменение нескольких Swift-файлов), обычно укладывается в одну-две секунды — на глаз почти не отличается от локального сохранения. Первая полная синхронизация (десятки тысяч исходных файлов) заметно медленнее из-за сканирования дерева каталогов — рекомендуется прогнать её один раз до начала работы, а не ждать первичной синхронизации по ходу написания кода.
По умолчанию двусторонний режим two-way-safe не перезаписывает данные автоматически при конфликте — он останавливается и ждёт вашего решения. Это безопаснее, чем принудительная перезапись, но если обе стороны правят один и тот же файл, паузы будут случаться часто. Для командной работы больше подходит:
mutagen sync create \
--name=ios-app \
--sync-mode=two-way-resolved \
--default-file-mode-alpha=0644 \
/Users/me/Projects/ios-app \
ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app
Режим two-way-resolved при конфликте по умолчанию отдаёт приоритет локальной стороне (alpha), и в сочетании с правилом «один человек в один момент времени редактирует код только на одной стороне» изменения практически никогда не теряются. Если конфликт всё же возникает на бинарных ресурсах (изображения, шрифты), mutagen sync list покажет конфликтующие пути — дальше нужно вручную сравнить временные метки и размер файлов, чтобы решить, какую версию оставить.
Готовая конфигурация, которую можно просто скопировать
Mutagen поддерживает конфигурацию на уровне проекта — файл mutagen.yml в корне проекта. После клонирования репозитория участнику команды достаточно одной команды, чтобы поднять синхронизацию, не набирая все параметры вручную:
sync:
ios-app:
alpha: "."
beta: "ssh://devuser@your-cloud-mac-host/Users/devuser/Projects/ios-app"
mode: "two-way-resolved"
ignore:
vcs: true
paths:
- "DerivedData"
- "Pods"
- "node_modules"
- "xcuserdata"
- ".build"
symlink:
mode: "posix-raw"
Из повседневных команд достаточно запомнить эти:
mutagen sync list
mutagen sync monitor ios-app
mutagen sync pause ios-app
mutagen sync resume ios-app
mutagen sync terminate ios-app
Команда monitor особенно полезна при разборе ситуации «почему изменения не долетели» — в реальном времени видно, на каком из трёх этапов (сканирование, стейджинг, передача) произошла задержка.
Чек-лист перед началом работы
- Покрывают ли
.mutagenignoreи.gitignoreпересобираемые каталоги — DerivedData, Pods, node_modules, xcuserdata и прочие? - Какой режим синхронизации выбран —
two-way-safeилиtwo-way-resolved? Для командной работы предпочтительнее второй, при условии договорённости, чьи изменения имеют приоритет. - Проводилась ли первая полная синхронизация в спокойное время, без срочных задач? Начальное сканирование десятков тысяч файлов плохо сочетается с одновременным написанием кода.
- Подтверждена ли в консоли конкретная модель облачного Mac и нода с нужной текущей конфигурацией? Пропускная способность сети и характеристики диска напрямую влияют на верхнюю границу скорости синхронизации.
- Стало ли привычкой заглядывать в
mutagen sync monitorпри диагностике, а не вспоминать о логах только после того, как файлы уже потерялись?
Пройдитесь по этим пунктам — и работа с облачным Mac станет практически неотличима от локальной, а команда перестанет спорить о том, чьи изменения «не долетели».
Часто задаваемые вопросы
Почему не смонтировать облачный Mac по SMB или NFS?
Сетевое монтирование превращает каждое обращение к мелкому файлу (индексация Xcode, поиск Pods) в сетевой round-trip; открытие крупного проекта может занимать десятки секунд. Mutagen хранит локальный кэш и синхронизирует инкрементально, поэтому отклик редактора остаётся почти как при локальной работе, а обрыв связи не подвешивает файловую систему.
Нужно ли синхронизировать DerivedData?
Нет. Кэш модулей и индексы в DerivedData привязаны к архитектуре машины и абсолютным путям; их синхронизация почти всегда вызывает переиндексацию или ошибки сборки. Добавьте папку в .mutagenignore и дайте каждой машине генерировать её самостоятельно.
Что делать при конфликте в двусторонней синхронизации?
Найдите конфликтующие пути через mutagen sync list. Для исходного кода обычно достаточно режима two-way-resolved с приоритетом локальной (alpha) стороны; для бинарных ресурсов сравнивайте вручную. Запуск git status перед каждой сессией синхронизации сам по себе предотвращает большинство конфликтов.
HireVPS Cloud Mac
Попробуйте выделенный облачный Mac mini уже сегодня
Аренда от одного дня, доступ по SSH/VNC за 2 минуты, конфигурацию можно повысить в любой момент.