CI における Submodule と LFS のボトルネック分解
ボトルネックは大きく四層に分かれます。(1) 親リポジトリの履歴取得、(2) 各 submodule の git fetch 連鎖、(3) LFS ポインタ解決とオブジェクト転送、(4) ディスク I/O とキャッシュミスによる再ダウンロードです。リモート Mac CI では RTT と帯域が変動しやすいため、(2)(3) を同時に走らせると帯域競合で双方がタイムアウトしがちです。
実務では「親を浅く取る → submodule をジョブ並列で広げる → LFS はスマッジを遅延または帯域制限」と段階化すると安定します。ホームから MacPull のリモート Mac を検討する場合も、同一ノードに .git と LFS キャッシュを残す設計がヒット率に効きます。
- 1. 親を
git clone --recurse-submodules=no(または既存ワークスペースでgit fetch)し、対象コミットをgit checkout。 - 2.
git submodule sync --recursiveで.gitmodulesの URL 改変(insteadOf等)を各子に反映。 - 3.
git submodule update --init --recursive --depth 1 --jobs 8のように深さと並列を明示(ネットが細いときは--jobs 2〜4)。 - 4. LFS を使うリポでは、先に
GIT_LFS_SKIP_SMUDGE=1でポインタのみ取得し、ビルド直前にgit lfs pullまたはgit lfs checkout。 - 5. 検証:
git submodule status --recursiveとgit lfs ls-filesで欠損がないか確認。
並列プルと lockfile(固定 SHA)方針の比較表
「速さ」と「再現性」はトレードオフです。チーム規模とリリース頻度に合わせて行を選んでください。
| 方針 | Submodule/ロック | 向いているケース | 主なリスク |
|---|---|---|---|
| 高並列・浅い履歴 | --jobs N+--depth 1、親のみ固定 |
日次開発・フィーチャーブランチ CI | 子の浮動参照があると再現性が落ちる |
| 親コミット完全固定 | 親の git rev-parse HEAD を成果物キーに含める |
リリースビルド・ストア提出 | キャッシュミス時の初回が長い |
| LFS 遅延スマッジ | GIT_LFS_SKIP_SMUDGE=1 → 後段 lfs pull |
巨大アセット・多数ポインタ | pull 忘れでビルドが欠損ファイルになる |
| モノレポ+スパース | submodule 廃止、sparse-checkout | パス単位の CI が多い | 移行コスト・権限設計が重い |
キャッシュと増分 fetch のパラメータ一覧
CI のキャッシュ鍵は「親のロック情報」と「サブモジュール定義の変化」を分離すると無駄な無効化を減らせます。
- パス:
$CI_CACHE/git-lfs(git config lfs.storageで指定)、既定はリポ内.git/lfs/objects。$CI_CACHE/submodule-bundles(任意の事前 bundle)、親.git/modules/*/objectsを丸ごと載せる場合はディスク見積もり必須。 - 鍵(推奨スロット):
sha256(.gitmodules)+sha256(lockfile)+runner.os+xcode-select相当のツールチェーン版(iOS の場合)。親のみのジョブならHEADの短縮 SHA をサフィックスに。 - 増分 fetch:
git fetch --depth=1 origin <branch>のあとgit submodule update --init --recursive --depth=1 --jobs N。fetch.recurseSubmodules=noをデフォルトにし、明示ステップだけで子を取るとログが追いやすいです。
LFS 並列・帯域の設定例(環境変数/git config):
git config lfs.concurrenttransfers 4(混雑時は 2〜4、余裕時は 6〜8)git config lfs.activitytimeout 300(秒・アイドル打ち切りの目安)git config lfs.dialtimeout 30(接続確立の上限)- 帯域上限(任意):
git config lfs.basictransfersonly trueでカスタム転送を避けシンプルに、プロキシ配下ではhttp.version HTTP/1.1の検証も有効なことがあります。
- 遅延:直結の p95 RTT が 200ms 超(跨境)かつジョブ SLA に余裕がない → 近傍ミラーまたはリモート Mac リージョンの見直し。
- スループット:60〜120 秒の窓で実効 < 2〜5 Mbps が続く → ミラー試行、または
--jobsとlfs.concurrenttransfersを下げて安定優先。 - エラー率:同一ホストで 連続 3 回 5xx/TLS 失敗/429 → 直結とミラーを入れ替え、バックオフ後に再試行。
失敗リトライとタイムアウト閾値
Git/LFS は指数バックオフ付きのラッパー(シェルまたは CI の retry ステップ)に載せると効果が高いです。
GIT_HTTP_LOW_SPEED_LIMIT=1000、GIT_HTTP_LOW_SPEED_TIME=600(停滞検知の一例)http.postBufferを一時的に上げる(大 push 用。pull 主体なら優先度は低め)- ジョブレベル:最大 3〜5 回、初期待機 5〜15 秒、上限 60〜120 秒の指数バックオフ
- 429 応答は
Retry-Afterを尊重し、無い場合は 30 秒以上空ける
詳細な Git/Homebrew/npm のタイムアウト一覧はプル安定性 FAQを参照してください。
リモート Mac のネットワークとディスク水位チェック
Submodule と LFS を並列に走らせると、一時的にディスクとファイル記述子を大量に使います。ジョブ前のワンライナーで水位を確認しておくと原因切り分けが速くなります。
df -h:ビルドルートと/tmp、キャッシュボリュームに 15〜20% 以上の空きを確保ping/traceroute相当で Git ホストへの RTT、curl -o /dev/null -w '%{time_total}\n'で HTTPS のレイテンシ目安- 同時実行数を上げた直後に失敗が増える場合は、帯域ではなく IOPS 不足の可能性 →
--jobsとlfs.concurrenttransfersを下げる
FAQ(認証失敗・クォータ・ミラー)
Q. Submodule だけ 401/403 になる
A. 親用トークンが子のホストで無効なケースが典型です。URL ごとに credential を分ける、git config --global url."https://token@host/".insteadOf で揃える、SSH の場合は各リポに Deploy Key を配布してください。
Q. LFS の転送/ストレージのクォータを使い切った
A. ダッシュボードで残量を確認し、古いバージョンのオブジェクト整理、もしくは成果物をリリース添付に逃がす。自前 LFS またはキャッシュヒット率改善で転送を削ります。
Q. ミラーが最新でない
A. ミラーの同期遅延を監視し、HEAD が親の要求 SHA に届かないときは直結にフォールバックする条件を CI に入れてください。
運用面の問い合わせはヘルプセンターもご利用ください。