200 MB のファイルをコピーすれば一瞬だ。同じディレクトリで git status を実行すると、待ち時間は他のことを始めたくなるほど長い。この二つが矛盾しないのは、バルク転送はスループットを測るが、Git は 毎秒何回の小さなファイルシステムへの問い合わせができるかを測るからであり、マウントされたリモートツリーはすべての問い合わせを往復に変える。VPN を調整しても、形の悪いワークロードは直らない。

なぜ status は間違った基準なのか

Git は多くのファイルについて知る必要がある:どれが変更され、どれが未追跡で、キャッシュされたインデックスのデータがまだディスク上の内容と一致しているか。SSHFS 越しでは、その観察の一つひとつがネットワーク操作になりうる。ここでの作業単位はファイル数であり、メガバイトではない。

クローン直後の最初の status が遅いのはそのためで、ビルド出力であふれたツリーでは次の status がさらに遅くなることもある。未追跡ファイルの走査は、関心のないディレクトリまで歩き、その一歩ごとがリンク越しのリクエストになる。40 ms の経路では、数千エントリのツリーが数千回の往復になる。この算術を確かめるのにパケットキャプチャは要らない。

ダウンロードのベンチマークは回線が良好だと教えてくれるが、千回の小さな読み取りが今日の午後に終わるかどうかは何も語らない。

作業ツリーを移し、調整つまみは動かさない

ローカルクローンなら、作業ツリー、インデックス、Git の操作が同じマシンに載る。pull が目に見えるネットワークの一歩になり、ファイルチェックのたびにそうなることはなくなる。

これは、開発者が必要なソースをローカルに置ける限り当てはまり、ほとんどの場合はそうだ。コミットとデプロイの境界もはっきりし、ローカルファイルの編集が動いているリモートサービスを偶発的に変えることはもうなく、変更は他の何かと同じように push してデプロイする必要がある。代償はコードが二か所に存在することなので、どちらのコピーが正なのか、サーバー上の緊急修正をどうリポジトリに戻すのかを決めよう。その会話は、誰も実行したくない status コマンドより安い。

ワークスペースをリモートに置かざるを得ないなら、そこで Git を動かす

ワークスペースを移せないことがある:ビルドがその環境を必要とする、データが大きすぎる、ポリシーがソースをホストに留める、といった場合だ。そのときはファイルの隣で Git と関連する開発ツールを、承認済みのリモートシェルかリモート開発環境を通して動かす。エディタはノート PC に残したまま、そのバックエンドが作業ツリーのそばで動く。

すると高価な操作はリモートホスト上でローカルに行われ、ネットワークは何千回もの stat 呼び出しではなくエディタの通信を運ぶ。ローカルクローンと同じ修正であり、移行ではなくデプロイの選択として表している。

同期は所有者のいるワークフローであり、キャッシュではない

同期されたワークスペースは繰り返しのリモート読み取りを減らせるが、透過的なキャッシュではない。変更ごとの所有者、競合のルール、双方が自分のものだと思う生成ファイルについての合意が必要だ。そのルールがなければ、この仕組みは便利さではなく不一致を生む。

それらを欠いた双方向同期は、解決した問題とは別の問題を生む——たいていは誰の責任か分からないマージだ。共有の作業ツリーに対して二つの Git プロセスを動かすのは、両者がどう調整するかを理解していない限り避けよう。

そのうえで調整し、SSH はまともに保つ

仕組みが正しくなれば、二つの設定が Git の問い合わせ先ファイル数を変える:status.showUntrackedFiles と、core.untrackedCache の背後にある未追跡キャッシュだ。どちらも 40 ms 越しの問い合わせ一回のコストは変えず、変わるのは問い合わせの数だけだ。往復がローカルになってから、つまみを回そう。

大きなファイルを一つコピーするベンチマークから推論するのではなく、不快なコマンドの時間を測り、対応している SSH 暗号化はそのままにしておく。古い暗号は往復を短くせず、最初の問題より長生きする二つ目の問題を足すだけだ。git status のリファレンスは一度読む価値がある、なぜならコマンドは名前から想像されるよりずっと多くの仕事をしているからだ。

関連記事