目次
なぜ作ったか
firebase-tools 15 から、公式の Cloud Firestore エミュレータを動かすには Java 21 以上が必須になった。ローカルのデータベースを立てるためだけに JDK を入れ、CI でも setup-java を走らせて jar を落としてくる。ずっと小さな違和感があった。
違和感は測ると数字になった。手元の計測環境(AWS c7i.xlarge、Ubuntu 24.04、Java 21、firebase-tools 15.33.0)で公式エミュレータ v1.22.0 を動かすと、ポートが開くまで 981 ms、アイドルで 148 MiB。リスナーを 1 本付けたまま 500 件ずつ書き込むと、保存済みドキュメントが 1 万件増えるごとにコミットが 195 ms ずつ遅くなり、10 万件を書き切るのに 191 秒かかった。この退行は firebase-tools#3477 として昔から知られている。
公式エミュレータはクローズドソースの Java アプリなので、直すことはできない。置き換えるしかない。
何を作ったか
hidane(火種)は、公式エミュレータの置き換えとして動く Rust の単一バイナリだ。

- gRPC、REST、WebChannel の 3 つのワイヤプロトコルを公式と同じく 1 ポートで話す
- Security Rules を評価する
- export / import は公式と同じ形式を読み書きし、どちらのエミュレータで作ったものも相互に読める
firebase-toolsの下で動く。firebase emulators:startの前にhidane exec --を付けるだけ
curl -fsSL https://hidane.dev/install.sh | sh # または brew / npm / cargo / dart pub / Docker
hidane exec -- firebase emulators:exec "npm test"
同じ環境で hidane v0.6.0 を測ると、ポートが開くまで 1.56 ms、アイドルで 9.4 MiB、リスナー付きの 10 万件書き込みは 2.51 秒。コミットのコストは変更したドキュメント数だけで決まり、保存済みの総数には依存しない。
ひとつ正直に書いておくと、Admin SDK がエミュレータに送る owner credential ではルール評価が飛ぶため、その条件では公式エミュレータも hidane とほぼ同じ速さで書ける。差が大きいのはサインアウト状態のクライアントと、リスナーを付けたときだ。ベンチマークの公平性の条件は bench/ に書いてある。
決めたこと
設計の判断は ADR として docs/adr/ に残している。大きいものを 3 つ。
ストレージはインメモリ(ADR 0002)。 公式エミュレータも永続化はせず、export / import がその役割を持つ。同じにした。コミットごとに単調増加するシーケンス番号を read_time の源にし、直近のコミットを変更ログとして持って resume_token からの再開に答える。変更通知は「変わったドキュメント数」に比例するコストで出す。これが上のリスナー付き書き込みの差になっている。
firebase-tools の下での起動(ADR 0006)。 firebase-tools は java -version でバージョンを確かめてから java -jar <jar> を実行する。jar 以外を起動する公式の手段はない。そこで hidane exec -- <command> は、一時ディレクトリを PATH の先頭に置き、その中に java という名前で hidane 自身を置く。バージョン確認には 21 と答え、-jar の後ろの引数で自分を起動する。そのコマンドの間だけ効き、マシン全体の java は汚さない。
互換性の証明(ADR 0008)。 公式の jar はブラックボックスで、再配布も逆コンパイルもできない。そこで tools/oracle/ のスクリプトがワイヤ越しに公式エミュレータへ質問し、答えを JSON の fixture として記録する。CI はそれを Java なしで hidane に再生する。v0.6.0 では 57 の記録、26,556 件の答えを再生している。公式の性能バグや既知の欠陥のように意図的に再現しない差分は docs/parity-exceptions.md に列挙し、それぞれを固定するテストを名指ししている。
全体はクリーンルームで書いた。公開の protobuf 定義、公開ドキュメント、そして公式エミュレータの黒箱観察だけが根拠で、何がどこから来たかは docs/clean-room/ に書いてある。
エミュレータの周り
v0.5 からは道具を足している。hidane doctor は移行前のプロジェクトを何も変えずに点検し、hidane migrate は差分を見せてから --apply で書き換える。hidane benchmark は自分のテストを両方のエミュレータで回して比べ、savings で月あたりの CI 時間と費用に換算する。GitHub Actions には setup-hidane があり、リリースの SHA-256 とビルド来歴を検証してから入れる。Testcontainers では Cloud SDK イメージの代わりに使える。
ベンチマークは benchmark-results に、毎晩と毎リリース、同じマシンで両方を測った結果を生サンプルごと置いている。README が引く数字はすべてそこへのリンク付きだ。
学び
- 「速い」と言うなら測り方ごと公開する。都合のいい 1 回ではなく中央値を、他の人が同じスクリプトで再現できる形で
- 互換性は「テストがあるものだけ」を名乗る。verified と not verified を分けて表に出すと、使う側が判断できる
- 差分ファザー(同じリクエストを両方に投げて答えを比べる)は、人が思いつかない差分を見つけてくれた。v0.6.0 だけで 6 バッチ分の差分を潰した

これから
Flutter のモバイル / デスクトップ版は同じ gRPC を話すが、まだ誰も突き合わせていない。エンタープライズ版のパイプライン(ExecutePipeline)も未実装。どちらも Issues で追っている。パリティのずれを見つけたら、hidane report の出力を添えて教えてほしい。
RELATED
Flutter / AWS / 自宅サーバー。個人開発と OSS をいくつか。設計相談や協業の話は X の DM か About から。 About