Skip to content
nomunomu0504.dev

BLOG #RUST

hidane: a Firestore emulator without Java, written in Rust

Hiroki Nomura 5 MIN READ rust firestore firebase oss
On this page

Why

Since firebase-tools 15, the official Cloud Firestore emulator requires Java 21 or newer. You install a JDK to run a local database, and your CI runs setup-java and downloads a jar for the same reason. It had bothered me for a long time.

Measuring turned the bother into numbers. In my benchmark environment (AWS c7i.xlarge, Ubuntu 24.04, Java 21, firebase-tools 15.33.0), the official emulator v1.22.0 takes 981 ms until its port accepts a connection and sits at 148 MiB idle. With one listener attached, writing in commits of 500 gets slower as data accumulates: each commit takes 195 ms longer per 10,000 documents already stored, and 100,000 documents take 191 seconds. The regression has been known for years as firebase-tools#3477.

The official emulator is a closed-source Java application, so it cannot be fixed from outside. It had to be replaced.

What I built

hidane (火種, “the seed of fire”) is a single Rust binary that works as a drop-in replacement.

The hidane.dev landing page
hidane.dev: a one-line install that stands in for the official emulator
  • It speaks gRPC, REST and WebChannel on one port, as the official emulator does
  • It evaluates Security Rules
  • Exports and imports use the official format, and each emulator reads the other’s files
  • It runs under firebase-tools: put hidane exec -- in front of firebase emulators:start
curl -fsSL https://hidane.dev/install.sh | sh    # or brew / npm / cargo / dart pub / Docker
hidane exec -- firebase emulators:exec "npm test"

On the same machine, hidane v0.6.0 opens its port in 1.56 ms, idles at 9.4 MiB, and writes the 100,000 documents with a listener attached in 2.51 seconds. A commit costs only what it changes, independent of how many documents are stored.

One honest caveat: the Admin SDKs send an emulator the owner credential, which skips Security Rules, and under that condition the official emulator writes almost as fast as hidane. The gap is large for signed-out clients and with a listener attached. The fairness rules are written down in bench/.

Decisions

The design decisions live as ADRs in docs/adr/. Three of the big ones:

In-memory storage (ADR 0002). The official emulator does not persist either; export and import play that role. hidane does the same. Each commit gets a monotonically increasing sequence number, which is the source of read_time, and recent commits are kept as a change log so a listener can resume from a resume_token. Change notification costs O(changed documents) per commit. That is where the listener benchmark comes from.

Launching under firebase-tools (ADR 0006). firebase-tools probes java -version and then runs java -jar <jar>. There is no supported way to start anything but the jar. So hidane exec -- <command> puts a temporary directory first on PATH with something named java in it, which is hidane itself. It answers the version probe with 21 and starts itself with the arguments after -jar. It applies to that one command and leaves the machine’s java alone.

How hidane exec starts under firebase-toolshidane exec --firebase emulators:starttemp dir first on PATHfirebase-toolsjava -version → java -jar <jar>the only way it knows to startresolves java on PATHjava (= the hidane shim)answers 21 to -version-jar … → starts hidane itselfhidane · one Rust binarygRPC + REST + WebChannel · :8080official jarnever downloadedThe shim exists only for thiscommand; the machine-widejava is untouched.your app / SDKfirebase-admin, js-sdk,Flutter, Go, Python …
How hidane exec starts under firebase-tools. The java placed first on PATH launches hidane itself

Proving compatibility (ADR 0008). The official jar is a black box that may be neither redistributed nor decompiled. Scripts in tools/oracle/ ask it questions over the wire and record the answers as JSON fixtures. CI replays every fixture against hidane without Java. In v0.6.0 that is 57 recordings and 26,556 recorded answers. Differences that are intentional, such as the official emulator’s performance bugs and known defects, are listed in docs/parity-exceptions.md, each naming the tests that pin it.

The whole thing is a clean-room implementation: the public protobuf definitions, the public documentation and black-box observation of the official emulator. docs/clean-room/ records where each behaviour may come from.

Around the emulator

Since v0.5 the tooling has grown. hidane doctor checks a project before it moves, without changing anything; hidane migrate shows the diff and applies it with --apply. hidane benchmark runs your own tests against both emulators, and savings turns the medians into CI minutes and cost per month. The setup-hidane GitHub Action verifies the release’s SHA-256 and build provenance before installing. In Testcontainers, hidane’s image stands in for the Cloud SDK image.

The benchmark lives in benchmark-results: both emulators measured on the same machine every night and for every release, raw samples included. Every number the README quotes links to the result it comes from.

What I learned

  • If you claim speed, publish the method. Medians, not a lucky run, in a form anyone can rerun with the same script
  • Claim compatibility only where a test exists. Showing verified and not verified side by side lets people decide for themselves
  • A differential fuzzer, sending the same request to both emulators and comparing, found differences no one would have thought to test. v0.6.0 alone fixed six batches of them
The Works with table on hidane.dev
The Works with table on hidane.dev. Only clients compared against the official emulator are marked verified

Next

Flutter on mobile and desktop speaks the same gRPC transport but nobody has compared it yet. The enterprise edition’s pipelines (ExecutePipeline) are not implemented. Both are tracked in Issues. If you find a parity gap, please attach the output of hidane report.

  1. Forecall: lessons from a linter for MCP tool descriptions 4 min
Hiroki Nomura @nomunomu0504 · CTO, MIRAI STUDIO

Flutter, AWS, homelab. A few side projects and open-source tools. For architecture questions or collaboration, reach out via X DM or the About page. About

COMMENTS · GISCUS