夜中の2時、9ドルで香港リージョンのMac mini M4インスタンスを借りたばかりだ。SSH鍵は5分以内にメールで届いた。意気込んで接続し、最初に叩いたコマンドは brew install node。ところが進捗バーはbottleのダウンロードでぴたりと止まったまま動かない。ターミナルをリロードするとcurlが接続タイムアウトを表示して再接続を繰り返す。20分経っても、本来なら3秒で終わるはずのtarball一つをまだ待っている状態だ。これはネットワーク障害ではなく、海外リージョンからHomebrewの公式CDNにアクセスする際の"日常的な遅延"にすぎない——しかも、新しいインスタンスを立てるたびにこの遅延を毎回味わうことになる。
シナリオ:新規マシン到着、依存インストールがレンタル期間の大半を食う
クラウドのMac miniを日単位で借りるメリットは、いつでも起動して使える手軽さだ。だが裏返せば、インスタンスを新規に立てるたびにゼロからのスタートを強いられる。Xcode Command Line Toolsを入れ、チームのsetup.shを一通り実行する——このありふれた手順の中でbrew installが大量のパッケージを、npm installが大量の依存関係を、pod installがCocoaPodsのインデックスを一気に取りに行く。公式ソースはすべて海外のオリジンサーバーを指しているため、ダウンロード速度だけで、マシン1日分のレンタル料に相当する作業時間が消えていく。1日だけ借りて単発タスクをこなすつもりなら、このコストは特に痛い。
クラウドノードで公式ソースが極端に遅くなる理由
Homebrewのbottle(ビルド済みバイナリ)はデフォルトでGitHub Packagesに置かれており、npmの公式レジストリやCocoaPodsのspecリポジトリも同様に海外インフラ上でホストされている。クラウド事業者の国際回線帯域自体は十分足りているケースが多いが、具体的なCDNノードまでのルーティング品質はまちまちだ。特に初回接続時のTLSハンドシェイクとDNS解決を積み重ねると、それだけで数十〜数百ミリ秒の遅延になり、これが数百個の依存パッケージ分積み重なることで、分単位、時には時間単位の待ち時間になる。マシンが遅いのではなく、回線が遅いのだ。
再利用可能な高速化パイプラインを3ステップで構築する
ステップ1:Homebrewのリポジトリとbottleドメインを切り替える
まず現在のHomebrewが国内到達性の良いミラーを既に向いているかを確認し、環境変数をまとめて設定する。ターミナルセッションを再起動すれば反映される。
git -C "$(brew --repo)" remote set-url origin \
https://mirrors.example-mirror.net/homebrew-core.git
export HOMEBREW_API_DOMAIN="https://mirrors.example-mirror.net/homebrew-bottles/api"
export HOMEBREW_BOTTLE_DOMAIN="https://mirrors.example-mirror.net/homebrew-bottles"
brew update
この3行を~/.zprofileに書いておけば、次回起動時にもう一度設定する必要はない。
ステップ2:Brewfileで依存関係リストを固定化する
brew installを1つずつ手で打つのはやめ、チームでよく使うパッケージをBrewfileにまとめて書き、一度のbundle実行でまとめてインストールする。複数ノード間でバージョンを揃えるのにも役立つ。
tap "homebrew/cask"
brew "node@20"
brew "cocoapods"
brew "watchman"
brew "git-lfs"
cask "visual-studio-code"
brew bundle --file=~/Brewfile
Brewfile自体は数十行程度で、コードリポジトリに保存しておく。新しいマシンを入手したら、プロジェクトのリポジトリをgit cloneしてbundleを一度実行するだけでよく、インストールコマンドを一つずつ検索するよりはるかに速く、隠れた依存の入れ忘れも防げる。
ステップ3:言語系パッケージマネージャーにも同じミラーを設定する
Homebrewの高速化だけでは問題の一部しか解決しない。npm、pip、CocoaPodsにはそれぞれ独自のデフォルトソースがあり、1つずつ設定する必要がある。
npm config set registry https://registry.example-npm-mirror.com
pip config set global.index-url https://pypi.example-mirror.com/simple
pod repo-art add master https://cdn.example-cocoapods-mirror.com/master.git
4つの経路をすべて切り替えた後、まっさらなMac miniで依存関係の展開から最初のビルドが通るまでの時間を比較すると、差は一目瞭然だ。
| 工程 | 公式ソース実測 | ミラー適用後 |
|---|---|---|
| Xcode CLT + Homebrewコアパッケージ | 約22分 | 約4分 |
| npm install(中規模プロジェクト) | 約9分 | 約2分 |
| pod install(iOSプロジェクト) | 約7分 | 約2分 |
| 合計 | 約40分 | 約8分 |
ミラーへの切り替えは無料ランチではない。ミラーサイトが公式リポジトリと同期するまでには数分〜数時間のタイムラグが発生する。重要なバージョンをアップグレードした後は、まず
brew doctorでセルフチェックを走らせ、コアとなるbottleに対してshasum -a 256で検証を行い、ハッシュ値が公式リリースと一致していることを確認してから本番フローに進めることを推奨する。
温めた環境をスナップショットとして保存し、次回起動時にそのまま再利用する
同じノードを週単位・月単位で継続してレンタルする予定があるなら、高速化パイプラインをさらに一歩進められる。依存関係を入れてビルドが一度通ったら、~/Library/Caches/Homebrew、node_modulesの共有キャッシュディレクトリ、~/.cocoapodsをまとめて圧縮し、自分のオブジェクトストレージにアップロードするか、OnceMiniのコントロールパネルから現在のインスタンスのスナップショットを取得する。次回の継続利用やOS再インストール後は、そのスナップショットからディスク状態を復元するだけで、ミラー設定の手順すら省略できる。実測ではこのユーザーディレクトリキャッシュのアーカイブサイズは8GB〜15GBほどで、OnceMiniの256GBからのSSDプランにはこの分の余裕は十分にあり、通常のプロジェクトファイルの保存容量を圧迫することもない。
チェックリスト
本番投入前に一つずつ確認しよう。ビルドが失敗して初めて見落としに気づく、という事態は避けたい。
- Homebrewの3つの環境変数を
~/.zprofileに書き込んだか(現在のセッションでexportしただけになっていないか) Brewfileをプロジェクトリポジトリにコミットしたか(誰か個人のローカル端末の履歴に残ったままになっていないか)- npm/pip/CocoaPodsの3か所すべてのミラーを切り替えたか(Homebrewだけ変更して終わっていないか)
- 重要なbottleに対して
shasum検証を実施したか - 同じノードを継続利用する予定なら、環境スナップショットを一度取得済みか
よくある質問
ミラーに切り替えるとbottleが不完全になりませんか?
正規のミラーは公式リポジトリと定期的に全量同期していますが、数分から数時間の遅延があります。インストール後にbrew doctorとshasumで主要bottleを検証してください。
環境スナップショットにはどれくらいの容量が必要ですか?
Command Line Tools、Homebrewのコアパッケージ、主要な言語ランタイムを含むキャッシュは実測で8~15GB程度で、OnceMiniの256GB以上のSSDプランなら十分収まります。
1日だけの利用でもこの高速化は有効ですか?
単発タスクなら3つのexportコマンドでミラーに切り替えるだけで十分で、スナップショットは不要です。同じノードを週次・月次で借り直す場合に真価を発揮します。
専用のMac miniでビルドフローをスムーズに回したいですか?
コンソールでMac miniをレンタルする