Для команд, которые каждый прогон тянут монорепозиторий или несколько связанных репозиториев и собирают iOS/macOS на удалённом Mac, сочетание Git Submodule и Git LFS часто становится главным потребителем времени и сети. Ниже — исполняемый порядок команд, таблица стратегий параллели и lockfile, чеклист кеша и инкрементального fetch, пороги для зеркала и прямого доступа, а также FAQ по авторизации и квотам. Дополнительный контекст: стратегия кеша Git и npm, зеркало Git vs прокси. Навигация: тарифы, оформление аренды, все статьи блога.

Submodule и LFS в CI: где теряются минуты

На удалённом узле Mac CI задержка складывается из последовательных этапов: рукопожатие с каждым origin подмодуля, разрешение ссылок LFS, пакетная выгрузка крупных объектов и запись на диск. Если подмодули вложены или указывают на разные хосты, холодный прогон превращается в «водопад» запросов. LFS добавляет отдельный batch-API: при низком lfs.concurrenttransfers вы оставляете пропускную способность неиспользованной; при слишком высоком — упираетесь в лимиты провайдера или в исчерпание файловых дескрипторов.

Рекомендуемый порядок инициализации submodule (после клонирования корня): синхронизировать URL из .gitmodules, затем параллельно инициализировать и обновить дерево. Пример последовательности:

Порядок (копируйте в скрипт CI)
  • 1. git submodule sync --recursive — привести remote подмодулей к актуальным URL из .gitmodules (важно при смене зеркала).
  • 2. git submodule update --init --recursive --jobs 4 — параллельная выгрузка до четырёх подмодулей одновременно (подберите --jobs под CPU и лимиты сети).
  • 3. При shallow-политике: после checkout суперпроекта выполнить git submodule foreach --recursive 'git fetch --depth=1 origin HEAD && git checkout FETCH_HEAD' или задать submodule.<name>.shallow true в конфиге там, где это поддерживается вашим хостингом.
  • 4. LFS: при массовых объектах сначала export GIT_LFS_SKIP_SMUDGE=1 на этапе submodule update, затем один раз git lfs install и git lfs pull уже в корне и при необходимости git submodule foreach --recursive 'git lfs pull'.

Такой порядок уменьшает лишние smudge-проходы и даёт предсказуемую нагрузку на LFS-сервер. Общие принципы кеширования для Git см. также в материале о стабильности Git и повторах.

Параллельная выгрузка и политика lockfile: сравнение

Выбор между «максимальной параллелью» и «жёсткой фиксацией версий» зависит от того, монорепозиторий ли у вас с закреплёнными SHA подмодулей или несколько независимых репозиториев с отдельными pipeline. Таблица ниже помогает зафиксировать компромисс для удалённого Mac CI.

Критерий Монорепо + submodule на фиксированных SHA Несколько репо / плавающие ветки подмодулей
Параллель submodule update --jobs 4–8, стабильный кеш объектов --jobs 2–4 + чаще промах кеша по ветке
Lockfile / фиксация SHA в суперпроекте = единственный источник истины; ключ кеша от .gitmodules + gitlink Нужны отдельные lock-артефакты или конвенция по тегам; ключ сложнее
LFS: риск лимитов Пакеты объектов предсказуемы; можно поднять concurrenttransfers Выше риск 429/квот; снижайте конкуренцию
Инкрементальный fetch git fetch --depth=1 + сохранённый .git на раннере Частые полные пересборки URL; проще от зеркала с низкой задержкой
Рекомендация для iOS/macOS Закреплять подмодули, кешировать LFS и DerivedData отдельно Ввести политику «только теги» для зависимых репо и ночной прогрев кеша

Кеш и инкрементальный fetch: чеклист параметров

На удалённом Mac выгодно переиспользовать не только npm/Pods, но и объекты Git и LFS. Ниже — ориентиры для каталогов, ключей и команд fetch (подставьте пути к своему общему тому или образу раннера).

Если раннер эфемерный, разделяйте два уровня кеша: быстрый локальный на SSD узла (снимок .git после успешного прогона) и медленный восстанавливаемый в объектном хранилище или NFS с ключом, привязанным к коммиту суперпроекта. Так вы избегаете ситуации, когда при смене одного подмодуля инвалидируется гигабайтный архив целиком: включайте в ключ только идентификаторы изменившихся gitlink и хеш .gitmodules, а общие объекты оставляйте в alternates или в общем LFS-каталоге.

  • Каталог LFS: по умолчанию ~/.git/lfs/objects; в CI задайте git config --global lfs.storage "/Volumes/Shared/lfs-cache" (или общий том) и монтируйте его между джобами.
  • Ключ кеша (пример): sha256(.gitmodules) + короткие SHA gitlink суперпроекта для каждого submodule + версия Xcode/агента; избегайте ключа только от ветки без коммита.
  • Инкремент: git fetch origin +refs/heads/$BRANCH:refs/remotes/origin/$BRANCH --prune --depth=50 затем git submodule update --init --recursive --jobs N; при неизменных подмодулях пропускайте полный foreach.
  • Альтернативные объекты: при нескольких клонах одного зеркала используйте GIT_ALTERNATE_OBJECT_DIRECTORIES на общий objects (осторожно с правами и утечкой кросс-репо).
Параметры LFS (пример для агрессивного канала)
  • git config --global lfs.concurrenttransfers 16 — до 16 параллельных загрузок объектов (начните с 8 и измеряйте).
  • git config --global lfs.batchsize 100 — размер batch-запроса к API (на GitHub Enterprise иногда безопаснее 50).
  • git config --global lfs.activitytimeout 300 и lfs.dialtimeout 60 — устойчивость на длинных объектах и нестабильном маршруте.
  • Ограничение исходящей полосы (если нужно делить канал с CocoaPods): git config --global lfs.basictransfersonly true в редких средах без multipart; чаще полезнее lfs.concurrenttransfers 4.

Сбои, повторы и таймауты: пороговые значения

Зафиксируйте в pipeline явные числа, чтобы ночные сбои не превращались в ручной разбор. Ниже — практичные ориентиры для скриптов оболочки или оркестратора (GitHub Actions, Jenkins, Buildkite и т.д.).

1

Git fetch / submodule. Три попытки с экспоненциальной задержкой: базовая пауза 5 с, множитель 2, максимум 60 с; таймаут команды 45–90 с на один fetch (зависит от размера истории).

2

LFS batch. При HTTP 429 или сетевом обрыве: уменьшить lfs.concurrenttransfers вдвое и повторить; не более 5 попыток на весь этап LFS.

3

Зеркало → прямой доступ. Переключаться на origin, если подряд три ошибки на зеркале, или если p95 задержка TCP/TLS к зеркалу > 2–3 с при стабильном пинге до публичного DNS; логируйте факт переключения в артефакты CI.

Сеть и диск удалённого Mac: быстрые проверки перед джобой

LFS и подмодули быстро заполняют том: перед тяжёлым прогоном проверьте свободное место и задержку до вашего Git/LFS endpoint. На macOS достаточно df -h по тому с ~ и рабочими каталогами; при использовании APFS обратите внимание на общий пул «System Data». Для сети выполните networkQuality (если доступно) или измерьте RTT до хоста зеркала и до первоисточника — расхождение в пользу зеркала должно быть устойчивым, иначе нет смысла в дополнительном звене.

  • Диск: держите минимум 20–30 ГБ свободными на томе с lfs.storage (или домашним каталогом LFS) и рабочей копией до старта джобы с крупными ассетами.
  • Сеть: зафиксируйте в runbook порог потерь пакетов > 1–2% к LFS-хосту как сигнал к переключению маршрута или VPN/релея.
  • CPU/IO: при --jobs 8 на малом Mac следите за sysctl hw.ncpu и временем ожидания диска; иногда выгоднее 4 параллельных submodule, чем контенция на SSD.

FAQ: авторизация, квоты и зеркала

Почему LFS пишет «credentials not found» только в подмодуле? Часто lfs.url или insteadOf заданы в корне, но подмодуль клонирован с другим хостом. Пропишите учётные данные для каждого базового URL или используйте единый .netrc/CI-секрет с шаблоном хоста.

Как отличить квоту от обычного таймаута? Коды 402/403 в ответе batch API, сообщения о billing или резкий обрыв после N успешных объектов. Снизьте параллелизм, проверьте биллинг LFS у провайдера и убедитесь, что зеркало не режет анонимный трафик.

Стоит ли отключать LFS в CI? Только если ассеты подтягиваются иным каналом (внутренний CDN) и коммиты без LFS воспроизводимы. Иначе сборка Xcode может молча получить pointer-файлы вместо бинарников.

Почему submodule update «зависает» на одном репозитории? Часто это не зависание, а ожидание блокировки на сервере при слишком большом --jobs или антивирус/Time Machine, душащие I/O на macOS. Снизьте параллель, включите GIT_TRACE_PACKET на короткий прогон для диагностики и проверьте, не ушёл ли трафик в медленное зеркало без таймаута.

Итог

Submodule + LFS на удалённом Mac CI управляются не «магией», а порядком команд, разумной параллелью, ключами кеша и явными порогами повторов. Закрепите SHA подмодулей, синхронизируйте URL, отложите smudge, настройте lfs.concurrenttransfers и мониторьте диск. Чтобы сократить время pull и стабилизировать сборки iOS/macOS, выберите узел рядом с вашим трафиком и управляйте кешем сами: тарифы MacPull, оформление аренды удалённого Mac, центр помощи, главная и блог с гайдами по Git и ускорению зависимостей.

Удалённый Mac для стабильного CI с Submodule и LFS

Нативный macOS, SSH/VNC и полный контроль над кешем Git/LFS. Оформите аренду или откройте тарифы — без лишних шагов.

Быстрая выдача
Контроль зеркал и кеша
iOS/macOS CI