HHVM型チェッカーのIncremental Checkの最適化:大規模モノレポにおける型チェック時間の短縮術
テックリードの私たちがコードレビューで最も恐れるのは、表面的なバグの混入ではない。それは「システムのスケールに伴うフィードバックループの崩壊」だ。
数百万行を超えるHackのコードベースを単一のモノレポで管理しているチームにとって、型チェッカー(`hh_client`)の実行時間が数秒から数分へ、そして数十分へと膨れ上がる瞬間は、開発組織の死を意味する。厳格な静的型付け(Strict Mode)の恩恵を完全に受けるためには、型安全性を維持しながら、CI/CDパイプラインおよびローカルのプレコミットフックにおける型チェックをミリ秒単位で完結させなければならない。
本稿では、HHVM型チェッカーが内部でどのようにインクリメンタルな依存関係グラフを構築しているのかというアーキテクチャの核心に迫り、大規模モノレポで型チェック時間を極限まで削ぎ落とすための実践的な最適化手法を解説する。
—
1. HHVM型チェッカー(`hh_server`)のアーキテクチャとインクリメンタル評価の限界
Hackの型チェッカーは、単なる愚直な構文解析器ではない。常駐プロセスである `hh_server` がメモリ上に広大なAST(抽象構文木)と依存関係グラフ(Dependency Graph)を保持し、ファイルが変更された差分(Incremental)に対してのみ再評価を行う。
グラフ構造の汚染と「カスケード再評価」の罠
インクリメンタルチェック(Incremental Check)が機能するメカニズムは美しい。しかし、設計を誤ったモノレポでは、1つのファイルの変更がグローバルな依存関係を引き起こし、実質的なフルビルド(Full Check)と同等のコストを支払うことになる。
- ファットなインターフェースの弊害: あらゆるドメインモデルを網羅する「神クラス(God Class)」や、広範囲にインポートされる定数ファイルを変更した場合、チェッカーは依存する数千のファイルを連鎖的に再評価(Cascade Re-evaluation)する。
- エイリアスと型定義の乱用: `type` や `newtype` の定義変更は、それを消費するすべてのジェネリクス型のインスタンス化を無効化し、メモリ上のキャッシュをパージさせる。
これを防ぐには、「依存の方向性と粒度の制御」をコードレベルで強制しなければならない。
—
2. 厳格な境界設計:構造的サブタイピングとインターフェース分離の極意
大規模コードベースで型チェックを高速化する第一歩は、結合度の低いモジュール設計だ。以下のプロダクションコード例を見てほしい。
非効率な設計では、ドメインロジックが具象クラスに直接依存し、型チェッカーが巨大なグラフを走査せざるを得なくなる。これをインターフェースとジェネリクスを駆使して疎結合化する。
// strict
namespace HackOptimization\Domain;
/
- 【アンチパターン】
- 具象クラスに直接依存すると、GodServiceのわずかな変更が
- すべての呼び出し元の型チェックを無効化する。
/
/
- 【推奨パターン】
- 処理の境界を厳格なインターフェースで規定し、型チェッカーのグラフの深さを抑える。
/
interface IProcessor
requirezx toProcessed(TIdentity $id): Awaitable
}
<<__ConsistentConstruct>>
abstract class BaseEntity {
public abstract function getId(): string;
}
final class UserEntity extends BaseEntity {
public function __construct(private string $uuid) {}
public function getId(): string {
return $this->uuid;
}
}
/
- 依存性注入コンテナやサービスレイヤーのモジュール境界を明確にし、
- hh_clientがトラバースするエッジの数を最小化する。
/
final class UserProcessor implements IProcessor
public async function toProcessed(UserEntity $id): Awaitable
// 厳格な型安全性を担保した非同期データフェッチ
$userId = $id->getId();
// 実装ロジック
return shape(‘id’ => $userId, ‘status’ => ‘active’);
}
}
なぜこの設計が型チェッカーに優しいのか?
型チェッカーは `IProcessor` インターフェースのシグネチャさえ変わらなければ、`UserProcessor` 内部の実装コード(関数のボディ部分)が変更されても、依存する上位レイヤーの型再評価をスキップできる。これがインクリメンタルチェックを成功させるための最大の秘訣である。
—
3. `.hhconfig` のチューニング:チェッカーのスコープを絞り込む
コードの構造化だけでなく、プロジェクトルートにある `.hhconfig` の設定最適化は、CIの実行時間を劇的に短縮する。
以下の設定は、大規模モノレポにおいて不要なファイル監視や重いチェックを排除するためのプロダクション・レディな設定例である。
.hhconfig の最適化例
自動ロードされるが型チェック不要なテストデータやサードパーティ製スクリプトを除外
ignored_file_patterns = {
“./tests/fixtures/.$”,
“./benchmark/.$”
}
厳格モードの強制(緩いコード混入によるグラフの肥大化を防ぐ)
assume_php = false
メモリとパフォーマンスのトレードオフ調整
大規模リポジトリでは並列度を適切に設定
enable_experimental_tc_features = all
特に `ignored_file_patterns` の設定漏れは、開発者が触らないテストフィードバック用の巨大なJSONやダミーファイル群まで `hh_server` の監視対象にしてしまい、インクリメンタルチェックのメモリ効率を悪化させる最大の原因となる。
—
4. チーフアーキテクトからの提言:コードレビューでチェックすべき3カ条
明日からのコードレビューにおいて、以下の3点をチームメンバーに徹底させてほしい。これらはすべて型チェックのパフォーマンスと保守性に直結する。
1. 「ワイルドカード(`mixed` / `dynamic`)」の蔓延を許すな
`dynamic` 型や安易な `mixed` のキャストは、型チェッカーの推論アルゴリズムに無駄な重負荷をかけ、インクリメンタルキャッシュの効果を無効化する。「Strict Mode (`<<__STRICT__>>`)」の徹底は、セキュリティだけでなくコンパイル速度の最適化そのものである。
2. 巨大な共通定数ファイルの分割
アプリ全体の設定やEnumを一箇所に集約した「MegaConfig.hack」を作るな。ドメインごとに細分化し、影響範囲を局所化せよ。
3. ジェネリクスの制約(Constraints)の局所化
複雑すぎる型制約は `hh_server` の型推論エンジンを暴走させる。人間が読めてチェッカーが迷わない、明快な境界(Bounds)を定義すること。
大規模モノレポにおけるHackの優位性は、その圧倒的な実行時パフォーマンスと、妥協のない静的型安全性にある。アーキテクチャの原則を守り、型チェッカーの挙動をハックすることで、コードベースがいかに巨大化しようとも、私たちの開発フィールは常に軽快であり続ける。