Многорепозиторный Mac CI: DerivedData, кэш SwiftPM и параллельный xcodebuild на SSD

Когда в одной команде параллельно крутятся десятки репозиториев, узким местом чаще становится не CPU, а SSD: xcodebuild одновременно раздувает DerivedData, кэши SwiftPM и временные каталоги линковки. Два job, которые пишут в один и тот же корень, легко «перетирают» тёплый инкрементальный кэш или ловят гонки блокировок, а пик записи при нескольких архивах может на минуты насытить дисковый канал. Ниже — как выбрать между фиксированным путём DerivedData и изоляцией на запуск, как проектировать ключи кэша SwiftPM, что происходит с пиками диска при параллельных сборках и какие уровни очистки согласовать с платформенной командой, плюс короткий FAQ про корпоративный пул (общие ноды против выделенных). Параллельный codesign и профили лучше читать вместе с Пул ресурсов Mac CI для предприятий в 2026 году: параллельные job и codesign — проектирование Keychain, Provisioning Profile и изоляции рабочей области: пошаговое внедрение и сравнительный FAQ, а метки, права и совместную работу нескольких runner — в материале 2026 OpenClaw: пошаговое развёртывание и интеграция с автоматизацией — офлайн‑устойчивость кроссплатформенных агентов, права выполнения и совместная работа нескольких runner в GitHub Actions.

1. Фиксированный DerivedData или отдельный каталог на job

Общий «тёплый» DerivedData ускоряет повторные сборки, пока все таргеты совместимы с одной версией Xcode и одинаковыми флагами, но при мультирепо и параллельных PR он превращается в поле боя: разные схемы, разные Swift toolchain и даже разные пути к submodule ломают инварианты кэша. Практичный компромисс — единый быстрый том с подпапкой на идентификатор запуска (RUN_ID, SHA, номер билда) и явный -derivedDataPath, чтобы два xcodebuild никогда не делили один каталог модулей. Если нужен долгоживущий кэш между job одного репозитория, ключуйте подпапку по ветке + версии Xcode и чистите при смене toolchain, иначе вы получите «плавающие» ошибки линковки. Зафиксируйте в runbook, что системный дефолт в домашнем каталоге запрещён для CI: он почти гарантирует пересечения с ручными сборками на той же учётке.

Правило: если два процесса могут одновременно писать в один ModuleCache, под нагрузке они это сделают — даже если локально всё выглядело последовательным.

2. Ключи кэша SwiftPM: что включать, чтобы не ломать параллель

Глобальный кэш разрешений SwiftPM экономит сеть, но ключ должен отражать версию Xcode, lockfile или хеш манифеста, а также архитектуру симулятора, если вы кросс-компилируете. Добавляйте в ключ идентификатор репозитория, когда несколько mono-repo и service-пакетов делят один runner: иначе «чужой» Package.resolved восстановит неверный граф. Для корпоративных зеркал фиксируйте ещё и базовый URL реестра, чтобы кэш не перепутал внутренний индекс с публичным. Если используете удалённый кэш оркестратора, трактуйте его как best-effort: локальный SSD по-прежнему выигрывает на инкрементальных компиляциях, а сетевой слой оставьте для редких полных пересборок.

3. Пики диска при параллельном xcodebuild: линковка, архивы, симулятор

Параллельные xcodebuild archive дают всплески записи на порядок выше обычной debug-сборки: компиляторы уже отработали, а линкер и упаковка ресурсов пишут крупные блобы одновременно. Добавьте скачивание dSYM, кеши тест-планов и снапшоты симулятора — и свободное место начинает уходить ступеньками, а не плавной кривой. Держите отдельный scratch-том для сборок, отдельно от системного, чтобы заполненный data-диск не ломал логин или сервис runner. Мониторьте не только df, но и I/O wait: иногда очередь растёт раньше, чем заканчиваются гигабайты, и новые job стоит отправлять на другую ноду до каскадных таймаутов.

4. Три уровня очистки: мягкая, плановая и авральная

Мягкий уровень после каждого job удаляет workspace и временные каталоги запуска, оставляя версионированные кэши. Плановый по расписанию снимает устаревшие ветки, старые архивы и тяжёлые логи тестов, пока дежурный не вынужден заходить по SSH ночью. Авральный срабатывает при пороге свободного места: останавливает выдачу новых job на хост, дренирует известно безопасные деревья и эскалирует на соседние ноды. Всегда документируйте, какие пути нельзя трогать из-за удержания артефактов, и синхронизируйте TTL с политикой артефактов в оркестраторе, чтобы не удалить то, что ещё нужно расследованию инцидента.

5. Корпоративный пул: общие Mac против выделенных нод — короткий FAQ

В: Делить один пул между продуктовыми командами? Да, если политики секретов совпадают и вы жёстко изолируете пути; иначе выделите классы машин по чувствительности данных.

В: Когда выгоднее выделенный кластер на команду? Когда разные Xcode/toolchain или требования к кэшу несовместимы: меньше «шума» от чужих PR и проще объяснить ИБ радиус компрометации.

В: Нужен ли отдельный пул под ночные тяжёлые архивы? Часто да: выносите archive+notarize на машины с большим SSD и сериализуйте по ключу, чтобы не конкурировать с быстрыми PR-сборками.

В: Как сравнивать облачные выделенные Mac и свой парк? Считайте не только цену часа, но и время на обслуживание ОС, дисковую гигиену и простой очереди; выделенный облачный Mac снимает пик без закупки железа на квартал вперёд.

6. Чеклист платформенной команды перед ростом параллелизма

  • Каждый параллельный xcodebuild использует свой -derivedDataPath или эквивалентный префикс?
  • Ключи кэша SwiftPM включают Xcode, lockfile и источник пакетов без коллизий между репозиториями?
  • На дашборде виден запас SSD и I/O wait до критического порога, а не только CPU?
  • Scratch-том отделён от системного, а очистка знает белые и чёрные списки путей?
  • Есть runbook на перенос job на соседнюю ноду, если дисковый пик блокирует очередь?

Почему Mac mini на macOS остаётся практичной базой для плотного xcodebuild

Дисциплина путей и кэшей работает, пока узел стабилен и предсказуем. Mac mini на Apple Silicon даёт высокую пропускную способность памяти и низкое энергопотребление в простое — это важно для runner, которые простаивают между merge. macOS на поддерживаемом железе снимает целый класс проблем «самосборных» хакинтошей, а Gatekeeper и SIP упрощают разговор с ИБ о том, что именно исполняется на CI. Для длинных цепочек «скомпилировал — связал — заархивировал» унифицированная память снижает риск thrashing по сравнению с компактными ПК, где память и диск конкурируют за одни и те же ватты.

Если вы планируете следующую волну параллельных сборок, сначала измерьте пики диска и длину очереди, а уже потом добавляйте ядра. Когда нужен быстрый burst без длинного цикла закупки, Mac mini M4 — сильный стартовый SKU: сочетайте его с изолированным DerivedData и ключами SwiftPM выше, и горизонтальное масштабирование станет предсказуемым. Чтобы подключить выделенные облачные Mac под ваш регион и политику данных, откройте главную страницу Macstripe и сравните модели — это самый прямой шаг от runbook к реальной мощности без лотереи на перегруженном общем пуле.