Deploying and operating
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.
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: 1on 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 ofreplicas: 2or 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-idshare 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
Kubernetes
The single-replica StatefulSet manifest, rollout strategy, resource requests, persistent volumes for warm restart, and graceful shutdown on SIGTERM.
High availability
The two HA tiers, the opt-in hot-standby cluster (one active + one or more warm standbys), the exactly-once-versus-at-least-once failover trade-off, and the multi-replica StatefulSet that deploys it.
Probes
Liveness versus readiness, how readiness holds traffic off during state restoration, and how to wire the /health/live and /health/ready endpoints into an orchestrator.
Observability
The Prometheus metrics surface, the introspection and debug endpoints, structured logs, and what to alert on for a single-instance application.
Tuning
Sizing CPU and memory, lane count, the commit-cadence bounds, and the levers that trade latency against throughput and restart time.
Production checklist
The pre-flight list before going live — replica count, probes, resources, persistence, license, metrics, and the failure policies you've chosen.
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
:runtimemodule 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.
GraalVM native image
Compile your StoatFlow app to a GraalVM native binary — a one-line opt-in. The framework ships its own reflection/JNI/FFM metadata; you choose the GC, optionally PGO, and add metadata only for your own serde value classes.
Running on Kubernetes
Deploy a StoatFlow application as a single-replica StatefulSet — persistent state volume, termination grace aligned with shutdown, secure license injection, and a representative manifest.