Deploying and operating

The deployment model for operators — one active instance and why two corrupt state, the two HA tiers (fast restart and opt-in hot standby), and a map of the operating pages.

This section is for the people who run StoatFlow in production: deploying it, sizing it, probing it, and keeping it healthy. The model is unusual in one way that shapes everything else — a StoatFlow application has exactly one active process. Most of operating it follows from that.

The deployment model

A StoatFlow application runs as exactly one JVM process. That process opens a Kafka consumer group with a single member — itself — and that member is assigned every partition of every source topic the topology reads from. There is no cluster, no scheduler, no worker pool, and no second instance sharing the load.

For an operator, this collapses a lot of moving parts. You deploy one pod, not a StatefulSet of N. There is no rebalancing to wait out on a rolling restart, no partition reassignment to reason about, no inter-instance coordination to monitor. The unit you scale, probe, restart, and observe is a single process.

You scale it vertically — more CPU and memory for the one process — not horizontally by adding replicas. Processing parallelism inside the process comes from lanes running on virtual threads, and lane count is decoupled from the partition count of your input topics. The full conceptual model is on Architecture; how lanes turn one process into many concurrent units of work is on Lanes and parallelism.

One active instance

This is the one rule that an operator must not break: run exactly one active instance. By default that means exactly one replica — not zero, not two.

Two active instances of the same application, pointed at the same source topics and writing the same state, will corrupt that state. By default StoatFlow runs a single process with no second instance to coordinate with, and the exactly-once commit protocol is built around that assumption. The one supported way to run more than one instance is the opt-in hot-standby cluster: the extra instances stay passive standbys — each follows the changelog and keeps its state warm, but never processes or commits — and the cluster fences a single active so that, under exactly-once, split-brain is impossible. Outside that mode, never run more than one.

Kafka's consumer-group semantics make the default-mode failure concrete. If you did start a second instance in the same group, Kafka would assign all partitions to one member and idle the other — at best you'd have a hot spare doing nothing useful, at worst a flapping reassignment if both keep trying to claim partitions. And because each instance also holds its own local state and writes its own changelog, two instances racing on the same changelog topics and the same transactional output produce interleaved, inconsistent writes that neither instance can reconcile. The result is corrupted state, not graceful degradation.

Never run two active replicas of the same StoatFlow application against the same source topics. With the default ha.mode: off, set your Kubernetes Deployment to replicas: 1 and a rollout strategy where a new pod fully replaces the old one — not one that briefly runs both. For a hot-standby cluster, use hot standby (replicas: 2 or more, ha.mode: active-standby), which keeps the standbys passive. The Kubernetes page shows the exact manifest.

The practical consequences for an operator are small and specific:

  • Set replicas: 1 on the Deployment under the default mode, and use a rollout strategy that tears the old pod down before — or as — the new one comes up, so the two never process concurrently. (Hot standby is the exception: a readiness-gated cluster of replicas: 2 or more — see High availability.) See Kubernetes.
  • Don't run a manual instance against production topics while the deployed one is live — for a debug session, point a throwaway instance at a different consumer group and, ideally, copies of the topics.
  • Treat the application id as identity. Two processes sharing the application-id share a consumer group and changelog topics; that's the collision to avoid.

High availability

StoatFlow offers two HA tiers, and you choose per application. Neither involves two active instances — that remains the rule.

  • Fast restart (the default). The one instance comes back fast after it goes down, with no data loss and no duplicate output. Recovery is a restart plus a changelog restore. This is the right answer for most workloads; the reasoning and the trade-offs are on Motivation.
  • Hot standby (opt-in). One active plus one or more warm passive standbys that continuously follow the changelog; on failover a lag-aware election promotes the freshest in seconds — independent of state size. Choose it for large state or a tight recovery-time objective where a cold restore is too slow. The full how-to is on High availability.

The rest of this section describes the default, fast-restart tier. Three properties make fast restart a credible availability story rather than a downgrade:

  • No rebalancing tax. A standard multi-instance stream processor pays a rebalance whenever an instance joins or leaves — partitions stop, get reassigned, and resume. With one instance and one group member, there is nothing to rebalance. Restart cost is restoration cost, full stop.
  • Bounded, parallel state recovery. On restart, local state rebuilds from the Kafka changelog topics, and restoration runs in parallel across stores rather than one at a time. Restart time scales with state size; see Benchmarks for measured cold-start numbers on representative workloads.
  • Exactly-once across the restart. The last successful commit barrier is the recovery point. Work the crashed process hadn't committed was aborted at the broker, so a restart resumes from the last committed offsets with no duplicate output downstream and no lost state. The mechanism is on Exactly-once.

For an operator this means availability is an orchestration concern, not an application-clustering one. Let Kubernetes restart the pod on failure; let the readiness probe hold traffic off until state has recovered. The probe contract is the lever that makes this clean — /health/ready returns 503 throughout restoration, so nothing routes to a process that isn't caught up yet. See Probes.

If your tolerance for restart-window downtime is tight, the levers are state size (smaller state restores faster), disk persistence (a warm local store on a persistent volume means restoration only reads the changelog gap since the last commit, not the whole topic), and broker read throughput. Tuning covers these.

The operating pages

Where to go next

  • Architecture — the single-instance model in full: data flow, lanes, state, commit barriers, and the operational surface.
  • The runtime — the batteries-included :runtime module that exposes the health, metrics, and admin endpoints operators rely on.
  • Kubernetes — start here when you're ready to deploy.
  • High availability — the opt-in hot-standby cluster, for when fast restart isn't fast enough.
  • Motivation — why StoatFlow is single-active-instance, and the trade-offs that come with it.