[{"data":1,"prerenderedAt":811},["ShallowReactive",2],{"navigation":3,"\u002Fblog\u002Fhot-standby-high-availability":314,"\u002Fblog\u002Fhot-standby-high-availability-surround":800},[4,38,86,149,176,219,257,273],{"title":5,"path":6,"stem":7,"children":8,"icon":37},"Getting Started","\u002Fdocs\u002Fgetting-started","1.docs\u002F1.getting-started\u002F1.index",[9,12,17,22,27,32],{"title":10,"path":6,"stem":7,"icon":11},"Getting started","i-lucide-flag",{"title":13,"path":14,"stem":15,"icon":16},"Modules overview","\u002Fdocs\u002Fgetting-started\u002Fmodules-overview","1.docs\u002F1.getting-started\u002F2.modules-overview","i-lucide-boxes",{"title":18,"path":19,"stem":20,"icon":21},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","1.docs\u002F1.getting-started\u002F3.installation","i-lucide-download",{"title":23,"path":24,"stem":25,"icon":26},"License configuration","\u002Fdocs\u002Fgetting-started\u002Flicense-configuration","1.docs\u002F1.getting-started\u002F4.license-configuration","i-lucide-key-round",{"title":28,"path":29,"stem":30,"icon":31},"Your first app","\u002Fdocs\u002Fgetting-started\u002Ffirst-app","1.docs\u002F1.getting-started\u002F5.first-app","i-lucide-square-play",{"title":33,"path":34,"stem":35,"icon":36},"Project setup","\u002Fdocs\u002Fgetting-started\u002Fproject-setup","1.docs\u002F1.getting-started\u002F6.project-setup","i-lucide-package",false,{"title":39,"path":40,"stem":41,"children":42,"icon":45},"Core Concepts","\u002Fdocs\u002Fconcepts","1.docs\u002F2.concepts\u002F1.index",[43,46,51,56,61,66,71,76,81],{"title":44,"path":40,"stem":41,"icon":45},"Core concepts","i-lucide-book-open",{"title":47,"path":48,"stem":49,"icon":50},"Architecture","\u002Fdocs\u002Fconcepts\u002Farchitecture","1.docs\u002F2.concepts\u002F2.architecture","i-lucide-layers",{"title":52,"path":53,"stem":54,"icon":55},"Exactly-once semantics","\u002Fdocs\u002Fconcepts\u002Fexactly-once","1.docs\u002F2.concepts\u002F3.exactly-once","i-lucide-shield-check",{"title":57,"path":58,"stem":59,"icon":60},"Lanes and parallelism","\u002Fdocs\u002Fconcepts\u002Flanes-and-parallelism","1.docs\u002F2.concepts\u002F4.lanes-and-parallelism","i-lucide-split",{"title":62,"path":63,"stem":64,"icon":65},"State and thread-safety","\u002Fdocs\u002Fconcepts\u002Fstate-and-thread-safety","1.docs\u002F2.concepts\u002F5.state-and-thread-safety","i-lucide-lock",{"title":67,"path":68,"stem":69,"icon":70},"Event time and watermarks","\u002Fdocs\u002Fconcepts\u002Fevent-time-and-watermarks","1.docs\u002F2.concepts\u002F6.event-time-and-watermarks","i-lucide-clock",{"title":72,"path":73,"stem":74,"icon":75},"The configuration model","\u002Fdocs\u002Fconcepts\u002Fconfiguration-model","1.docs\u002F2.concepts\u002F7.configuration-model","i-lucide-sliders-horizontal",{"title":77,"path":78,"stem":79,"icon":80},"The error-handling model","\u002Fdocs\u002Fconcepts\u002Ferror-handling-model","1.docs\u002F2.concepts\u002F8.error-handling-model","i-lucide-triangle-alert",{"title":82,"path":83,"stem":84,"icon":85},"How StoatFlow differs from Kafka Streams","\u002Fdocs\u002Fconcepts\u002Fhow-stoatflow-differs-from-ks","1.docs\u002F2.concepts\u002F9.how-stoatflow-differs-from-ks","i-lucide-arrow-left-right",{"title":87,"path":88,"stem":89,"children":90,"icon":93},"Building Topologies","\u002Fdocs\u002Fbuilding","1.docs\u002F3.building\u002F1.index",[91,94,99,104,109,114,119,124,129,134,139,144],{"title":92,"path":88,"stem":89,"icon":93},"Building topologies","i-lucide-blocks",{"title":95,"path":96,"stem":97,"icon":98},"Error handling and DLQ","\u002Fdocs\u002Fbuilding\u002Ferror-handling-dlq","1.docs\u002F3.building\u002F10.error-handling-dlq","i-lucide-circle-x",{"title":100,"path":101,"stem":102,"icon":103},"State stores","\u002Fdocs\u002Fbuilding\u002Fstate-stores","1.docs\u002F3.building\u002F11.state-stores","i-lucide-database",{"title":105,"path":106,"stem":107,"icon":108},"Testing topologies","\u002Fdocs\u002Fbuilding\u002Ftesting","1.docs\u002F3.building\u002F12.testing","i-lucide-flask-conical",{"title":110,"path":111,"stem":112,"icon":113},"Sources and sinks","\u002Fdocs\u002Fbuilding\u002Fstreams-builder","1.docs\u002F3.building\u002F2.streams-builder","i-lucide-import",{"title":115,"path":116,"stem":117,"icon":118},"KStream and KTable operations","\u002Fdocs\u002Fbuilding\u002Fkstream-ktable","1.docs\u002F3.building\u002F3.kstream-ktable","i-lucide-waypoints",{"title":120,"path":121,"stem":122,"icon":123},"Aggregations","\u002Fdocs\u002Fbuilding\u002Faggregations","1.docs\u002F3.building\u002F4.aggregations","i-lucide-sigma",{"title":125,"path":126,"stem":127,"icon":128},"Windowing","\u002Fdocs\u002Fbuilding\u002Fwindowing","1.docs\u002F3.building\u002F5.windowing","i-lucide-calendar-clock",{"title":130,"path":131,"stem":132,"icon":133},"Joins","\u002Fdocs\u002Fbuilding\u002Fjoins","1.docs\u002F3.building\u002F6.joins","i-lucide-git-merge",{"title":135,"path":136,"stem":137,"icon":138},"The Processor API","\u002Fdocs\u002Fbuilding\u002Fprocessor-api","1.docs\u002F3.building\u002F7.processor-api","i-lucide-cpu",{"title":140,"path":141,"stem":142,"icon":143},"Scheduled sources","\u002Fdocs\u002Fbuilding\u002Fscheduled-sources","1.docs\u002F3.building\u002F8.scheduled-sources","i-lucide-timer",{"title":145,"path":146,"stem":147,"icon":148},"Serdes and Avro","\u002Fdocs\u002Fbuilding\u002Fserdes","1.docs\u002F3.building\u002F9.serdes","i-lucide-binary",{"title":150,"path":151,"stem":152,"children":153,"icon":75},"Configuration","\u002Fdocs\u002Fconfiguration","1.docs\u002F4.configuration\u002F1.index",[154,156,161,166,171],{"title":155,"path":151,"stem":152,"icon":75},"How configuration works",{"title":157,"path":158,"stem":159,"icon":160},"Engine configuration (:core)","\u002Fdocs\u002Fconfiguration\u002Fcore-config","1.docs\u002F4.configuration\u002F2.core-config","i-lucide-settings-2",{"title":162,"path":163,"stem":164,"icon":165},"Runtime configuration (:runtime)","\u002Fdocs\u002Fconfiguration\u002Fruntime-config","1.docs\u002F4.configuration\u002F3.runtime-config","i-lucide-server-cog",{"title":167,"path":168,"stem":169,"icon":170},"Defaults, adaptivity, and presets","\u002Fdocs\u002Fconfiguration\u002Fdefaults-and-presets","1.docs\u002F4.configuration\u002F4.defaults-and-presets","i-lucide-gauge",{"title":172,"path":173,"stem":174,"icon":175},"Kafka client configuration","\u002Fdocs\u002Fconfiguration\u002Fkafka-client-config","1.docs\u002F4.configuration\u002F5.kafka-client-config","i-lucide-plug",{"title":177,"path":178,"stem":179,"children":180,"icon":183},"Running in Production","\u002Fdocs\u002Fruntime","1.docs\u002F5.runtime\u002F1.index",[181,184,189,194,199,204,209,214],{"title":182,"path":178,"stem":179,"icon":183},"The runtime","i-lucide-server",{"title":185,"path":186,"stem":187,"icon":188},"The REST API","\u002Fdocs\u002Fruntime\u002Frest-api","1.docs\u002F5.runtime\u002F2.rest-api","i-lucide-globe",{"title":190,"path":191,"stem":192,"icon":193},"Health checks","\u002Fdocs\u002Fruntime\u002Fhealth-checks","1.docs\u002F5.runtime\u002F3.health-checks","i-lucide-heart-pulse",{"title":195,"path":196,"stem":197,"icon":198},"Metrics","\u002Fdocs\u002Fruntime\u002Fmetrics","1.docs\u002F5.runtime\u002F4.metrics","i-lucide-activity",{"title":200,"path":201,"stem":202,"icon":203},"Pause and resume","\u002Fdocs\u002Fruntime\u002Fpause-unpause","1.docs\u002F5.runtime\u002F5.pause-unpause","i-lucide-pause",{"title":205,"path":206,"stem":207,"icon":208},"Plugins and lifecycle hooks","\u002Fdocs\u002Fruntime\u002Fplugins","1.docs\u002F5.runtime\u002F6.plugins","i-lucide-puzzle",{"title":210,"path":211,"stem":212,"icon":213},"Docker images","\u002Fdocs\u002Fruntime\u002Fdocker","1.docs\u002F5.runtime\u002F7.docker","i-lucide-container",{"title":215,"path":216,"stem":217,"icon":218},"GraalVM native image","\u002Fdocs\u002Fruntime\u002Fnative-image","1.docs\u002F5.runtime\u002F8.native-image","i-lucide-zap",{"title":220,"path":221,"stem":222,"children":223,"icon":226},"Deploying & Operating","\u002Fdocs\u002Foperating","1.docs\u002F6.operating\u002F1.index",[224,227,232,237,242,247,252],{"title":225,"path":221,"stem":222,"icon":226},"Deploying and operating","i-lucide-life-buoy",{"title":228,"path":229,"stem":230,"icon":231},"Running on Kubernetes","\u002Fdocs\u002Foperating\u002Fkubernetes","1.docs\u002F6.operating\u002F2.kubernetes","i-lucide-ship",{"title":233,"path":234,"stem":235,"icon":236},"High availability","\u002Fdocs\u002Foperating\u002Fhigh-availability","1.docs\u002F6.operating\u002F3.high-availability","i-lucide-copy",{"title":238,"path":239,"stem":240,"icon":241},"Liveness and readiness probes","\u002Fdocs\u002Foperating\u002Fprobes","1.docs\u002F6.operating\u002F4.probes","i-lucide-stethoscope",{"title":243,"path":244,"stem":245,"icon":246},"Observability","\u002Fdocs\u002Foperating\u002Fobservability","1.docs\u002F6.operating\u002F5.observability","i-lucide-telescope",{"title":248,"path":249,"stem":250,"icon":251},"Tuning under load","\u002Fdocs\u002Foperating\u002Ftuning","1.docs\u002F6.operating\u002F6.tuning","i-lucide-sliders",{"title":253,"path":254,"stem":255,"icon":256},"Production checklist","\u002Fdocs\u002Foperating\u002Fproduction-checklist","1.docs\u002F6.operating\u002F7.production-checklist","i-lucide-clipboard-check",{"title":258,"path":259,"stem":260,"children":261,"icon":85},"Migrating from Kafka Streams","\u002Fdocs\u002Fmigration","1.docs\u002F7.migration\u002F1.index",[262,263,268],{"title":258,"path":259,"stem":260,"icon":85},{"title":264,"path":265,"stem":266,"icon":267},"Migration without carrying state","\u002Fdocs\u002Fmigration\u002Fwithout-data-migration","1.docs\u002F7.migration\u002F2.without-data-migration","i-lucide-sparkles",{"title":269,"path":270,"stem":271,"icon":272},"Migration carrying state","\u002Fdocs\u002Fmigration\u002Fwith-data-migration","1.docs\u002F7.migration\u002F3.with-data-migration","i-lucide-database-backup",{"title":274,"path":275,"stem":276,"children":277,"icon":279},"Reference","\u002Fdocs\u002Freference","1.docs\u002F8.reference\u002F1.index",[278,280,285,290,295,300,305,309],{"title":274,"path":275,"stem":276,"icon":279},"i-lucide-list",{"title":281,"path":282,"stem":283,"icon":284},"Configuration reference","\u002Fdocs\u002Freference\u002Fconfiguration-reference","1.docs\u002F8.reference\u002F2.configuration-reference","i-lucide-table",{"title":286,"path":287,"stem":288,"icon":289},"REST API reference","\u002Fdocs\u002Freference\u002Frest-api-reference","1.docs\u002F8.reference\u002F3.rest-api-reference","i-lucide-network",{"title":291,"path":292,"stem":293,"icon":294},"Gradle plugin reference","\u002Fdocs\u002Freference\u002Fgradle-plugin-reference","1.docs\u002F8.reference\u002F4.gradle-plugin-reference","i-lucide-box",{"title":296,"path":297,"stem":298,"icon":299},"Maven reference","\u002Fdocs\u002Freference\u002Fmaven-reference","1.docs\u002F8.reference\u002F5.maven-reference","i-simple-icons-apachemaven",{"title":301,"path":302,"stem":303,"icon":304},"Kafka Streams compatibility matrix","\u002Fdocs\u002Freference\u002Fks-compatibility-matrix","1.docs\u002F8.reference\u002F6.ks-compatibility-matrix","i-lucide-table-2",{"title":306,"path":307,"stem":308,"icon":170},"Metrics reference","\u002Fdocs\u002Freference\u002Fmetrics-reference","1.docs\u002F8.reference\u002F7.metrics-reference",{"title":310,"path":311,"stem":312,"icon":313},"Glossary","\u002Fdocs\u002Freference\u002Fglossary","1.docs\u002F8.reference\u002F8.glossary","i-lucide-book-a",{"id":315,"title":316,"authors":317,"badge":323,"body":325,"date":789,"description":790,"draft":37,"extension":791,"image":792,"meta":794,"navigation":795,"path":796,"seo":797,"stem":798,"__hash__":799},"posts\u002F3.blog\u002F4.hot-standby-high-availability.md","Hot standby for StoatFlow: failover in seconds, not a cold restore",[318],{"name":319,"to":320,"avatar":321},"Hartmut Armbruster","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fhartmutarmbruster",{"src":322},"\u002Fassets\u002Fhartmut_armbruster_monochromatic.jpg",{"label":324},"Announcement",{"type":326,"value":327,"toc":776},"minimark",[328,332,339,391,396,446,449,457,465,473,476,539,542,549,625,628,638,652,655,658,661,668,681,684,687,728,731,750,763,766,769],[329,330,331],"p",{},"A StoatFlow application recovers by restarting: the process dies, the orchestrator brings it back, and local state rebuilds from the Kafka changelog up to the last committed barrier — no data loss, no duplicate output. That is fast, and for most workloads it is enough. But the rebuild reads the changelog, so recovery time scales with how much state there is. At tens of gigabytes, a cold restore can run into minutes.",[329,333,334,335,338],{},"Hot standby removes that scaling. It is an opt-in active\u002Fpassive pair where a warm standby takes over in seconds, independent of state size — and under exactly-once, with no duplicates across the handoff. The full operator guide is on ",[336,337,233],"a",{"href":234},"; here is why it exists and what it gives you.",[340,341,342,348],"blockquote",{},[329,343,344],{},[345,346,347],"strong",{},"TL;DR",[349,350,351,358,364,370,376,382],"ul",{},[352,353,354,357],"li",{},[345,355,356],{},"What:"," Hot standby — an opt-in active\u002Fpassive pair. Off by default; the single-instance runtime is byte-for-byte unchanged when you don't enable it.",[352,359,360,363],{},[345,361,362],{},"Why:"," Recovery has always been fast restart, but a cold rebuild from the changelog scales with state size. For large state or a tight recovery-time objective, that window is too long.",[352,365,366,369],{},[345,367,368],{},"How:"," A passive standby continuously follows the changelog and stays warm. Promotion is seconds, independent of state size — no cold restore on the critical path.",[352,371,372,375],{},[345,373,374],{},"Guarantee:"," Under exactly-once, broker-enforced transactional fencing makes split-brain impossible — at most one instance ever commits. Under at-least-once, failover is bounded-duplicate.",[352,377,378,381],{},[345,379,380],{},"The catch:"," It is redundancy, not scale. Still one active instance, roughly double the footprint. To go faster, scale up, not out.",[352,383,384,387,388,390],{},[345,385,386],{},"Docs:"," ",[336,389,233],{"href":234},".",[392,393,395],"h2",{"id":394},"in-this-post","In this post",[349,397,398,404,410,416,422,428,434,440],{},[352,399,400],{},[336,401,403],{"href":402},"#why-this-exists","Why this exists",[352,405,406],{},[336,407,409],{"href":408},"#what-hot-standby-gives-you","What hot standby gives you",[352,411,412],{},[336,413,415],{"href":414},"#fast-restart-vs-hot-standby","Fast restart vs hot standby",[352,417,418],{},[336,419,421],{"href":420},"#how-it-works","How it works",[352,423,424],{},[336,425,427],{"href":426},"#exactly-once-across-failover","Exactly-once across failover",[352,429,430],{},[336,431,433],{"href":432},"#tradeoffs-and-limits","Tradeoffs and limits",[352,435,436],{},[336,437,439],{"href":438},"#running-it","Running it",[352,441,442],{},[336,443,445],{"href":444},"#when-to-turn-it-on","When to turn it on",[392,447,403],{"id":448},"why-this-exists",[329,450,451,452,456],{},"StoatFlow runs as exactly one instance per application. That is the whole point: no consumer-group rebalancing, no state migration, no repartition topics — you stop paying the ",[453,454,455],"em",{},"distribution tax"," for scale-out you don't need. Availability in that model comes from fast restart, and fast restart is a credible story because there is no rebalancing to wait out: restart cost is restoration cost, full stop.",[329,458,459,460,464],{},"The cost, though, is bounded by state size. On small-to-medium state the engine is processing again quickly — the ",[336,461,463],{"href":462},"\u002Fproduct\u002Fbenchmarks","benchmarks"," measure cold-start across representative workloads, and a persistent volume narrows it further to just the changelog gap since the last commit. But a cold start still reads the changelog, and a large store takes longer to replay than a small one. For a workload carrying tens of gigabytes of state with a tight recovery-time objective, minutes of recovery on an unplanned restart is the line between acceptable and not.",[329,466,467,468,472],{},"When we shipped the ",[336,469,471],{"href":470},"\u002Fblog\u002Fintroducing-stoatflow","first alpha",", failover behaviour was a fair question we said we hadn't answered yet. This is the answer: an availability tier for the workloads that can't absorb a cold-restore window — without giving up the single-instance model, and without reintroducing the distribution tax. There is still exactly one active instance. There is still no rebalancing and no horizontal scale. There is now a warm spare.",[392,474,409],{"id":475},"what-hot-standby-gives-you",[349,477,478,484,490,501,507,513],{},[352,479,480,483],{},[345,481,482],{},"Failover in seconds, independent of state size."," The standby is already warm, so taking over is not a restore — it is a handoff. The cold-start window that scales with your state is exactly what this removes.",[352,485,486,489],{},[345,487,488],{},"Near-zero-downtime rolling deploys."," A version roll costs about one graceful handoff: the active drains, commits, and hands over to the standby. No rebalance, no cluster-wide reconvergence.",[352,491,492,495,496,500],{},[345,493,494],{},"Exactly-once preserved across the handoff."," The recovery anchor is the last committed barrier whether you restart cold or fail over to a standby. Under exactly-once there are no duplicates downstream for ",[497,498,499],"code",{},"read_committed"," consumers.",[352,502,503,506],{},[345,504,505],{},"An order-independent rollout, with no custom controller."," Readiness gates the roll: a standby reports ready only once it has caught up, and a catching-up standby is not killed mid-catch-up. Kubernetes never advances the roll onto an instance that isn't ready — whichever pod rolls first.",[352,508,509,512],{},[345,510,511],{},"Opt-in, with the default untouched."," Hot standby is off unless you turn it on. Leave it off and you get exactly today's single-instance behaviour, with none of the extra moving parts.",[352,514,515,518,519,522,523,526,527,530,531,534,535,538],{},[345,516,517],{},"Observable and operable."," A ",[497,520,521],{},"\u002Fha\u002Fstatus"," endpoint reports each pod's role, replication lag, and the peers it sees; ",[497,524,525],{},"\u002Fha\u002Fswitch",", ",[497,528,529],{},"\u002Fha\u002Fpromote",", and ",[497,532,533],{},"\u002Fha\u002Fdemote"," drive a controlled handoff; and the pair exports ",[497,536,537],{},"stoatflow.ha.*"," metrics for dashboards and alerts.",[392,540,415],{"id":541},"fast-restart-vs-hot-standby",[329,543,544,545,548],{},"Neither tier involves two ",[453,546,547],{},"active"," instances — that remains the rule. You choose per application.",[550,551,552,567],"table",{},[553,554,555],"thead",{},[556,557,558,561,564],"tr",{},[559,560],"th",{},[559,562,563],{},"Fast restart (default)",[559,565,566],{},"Hot standby (opt-in)",[568,569,570,582,593,604,614],"tbody",{},[556,571,572,576,579],{},[573,574,575],"td",{},"Model",[573,577,578],{},"One instance",[573,580,581],{},"Active\u002Fpassive pair",[556,583,584,587,590],{},[573,585,586],{},"Recovery on failure",[573,588,589],{},"Restart + rebuild from changelog",[573,591,592],{},"Promote the warm standby",[556,594,595,598,601],{},[573,596,597],{},"Failover time",[573,599,600],{},"Scales with state size",[573,602,603],{},"Seconds, independent of state size",[556,605,606,609,611],{},[573,607,608],{},"Cost",[573,610,578],{},[573,612,613],{},"~2× — a second always-on instance + its volume",[556,615,616,619,622],{},[573,617,618],{},"Choose it for",[573,620,621],{},"Most workloads",[573,623,624],{},"Large state or a tight recovery-time objective",[392,626,421],{"id":627},"how-it-works",[329,629,630,631,633,634,637],{},"At any moment one instance is the ",[345,632,547],{}," — it owns the source partitions, processes records, and commits — and the other is a ",[345,635,636],{},"passive standby",". The standby consumes the same changelog the active writes, keeping its own local state warm and a step behind; it does not process source records or produce output. The two coordinate through a compacted internal Kafka topic, so each knows the other is alive and which role it holds.",[329,639,640,641,644,645,647,648,651],{},"Promotion happens two ways. A ",[345,642,643],{},"graceful handoff"," — a rolling deploy or an explicit ",[497,646,525],{}," — drains the active, commits, and hands the role over. A ",[345,649,650],{},"failover"," — the active crashes — is detected when the standby stops seeing the active's heartbeats, and the standby promotes itself. Because it is already warm, taking over does not wait on a cold restore.",[329,653,654],{},"The safety property underneath is Kafka's own. Under exactly-once, a transactional producer is fenced by its epoch: if a network partition ever left two instances briefly believing they are active, the broker lets only one commit and fences the other, which then shuts itself down. Correctness does not depend on the failure detector being perfect — it depends on the fence, which the broker enforces.",[392,656,427],{"id":657},"exactly-once-across-failover",[329,659,660],{},"How clean a failover is depends on the processing guarantee the application runs under — and the two behave differently enough to call out.",[329,662,663,664,667],{},"Under ",[345,665,666],{},"exactly-once"," (the default), failover is split-brain-proof. The transactional fence guarantees at most one instance can ever commit a given transaction, so the handoff produces no duplicates and loses no committed work. There is nothing extra to do downstream.",[329,669,663,670,673,674,677,678,390],{},[345,671,672],{},"at-least-once",", failover is ",[453,675,676],{},"bounded-duplicate",". The pair still coordinates promotion safely, but without transactional fencing an ungraceful failover can re-process a small, bounded window of records, so a few duplicate output records are possible. If you run hot standby under at-least-once, make downstream consumers idempotent or duplicate-tolerant. The guarantee model is covered on ",[336,679,680],{"href":53},"Exactly-once",[392,682,433],{"id":683},"tradeoffs-and-limits",[329,685,686],{},"Hot standby is a deliberate trade, and it has edges worth stating plainly.",[349,688,689,695,701,707,716,722],{},[352,690,691,694],{},[345,692,693],{},"It is redundancy, not scale."," A warm spare does not add throughput. There is still exactly one active instance — to go faster, give it more cores and memory.",[352,696,697,700],{},[345,698,699],{},"Roughly double the footprint."," A second always-on instance, its own persistent volume, and the licence that goes with it.",[352,702,703,706],{},[345,704,705],{},"At-least-once is bounded-duplicate",", not split-brain-proof. Exactly-once is the guarantee that makes a handoff clean.",[352,708,709,712,713,715],{},[345,710,711],{},"Crash failover is not zero-downtime."," It is bounded by detection plus a near-instant promotion — short, but not zero. A graceful handoff (deploy or ",[497,714,525],{},") is about one handoff.",[352,717,718,721],{},[345,719,720],{},"Single region."," Both instances must reach the same Kafka cluster; this is not a multi-region or active-active mechanism.",[352,723,724,727],{},[345,725,726],{},"The default is often the right answer."," If a cold-restore window of seconds-to-minutes is acceptable, fast restart is simpler and cheaper. Hot standby exists to remove that window, not because the default is unsafe.",[392,729,439],{"id":730},"running-it",[329,732,733,734,737,738,741,742,745,746,749],{},"You deploy the pair as a two-replica ",[497,735,736],{},"StatefulSet"," — the same single-replica manifest you already use, with ",[497,739,740],{},"replicas: 2",", a per-pod persistent volume, and a rolling update. The split ",[497,743,744],{},"\u002Fhealth\u002Flive"," and ",[497,747,748],{},"\u002Fhealth\u002Fready"," probes are what make the roll order-independent, and a termination grace period long enough for the active to finish its handoff keeps deploys clean.",[329,751,752,753,756,757,759,760,762],{},"Turning it on is one configuration knob: ",[497,754,755],{},"stoatflow.ha.mode: active-standby",". The rest — the coordination topic, the per-pod identity, the staleness and lag thresholds — has sensible defaults; on Kubernetes a typical deployment sets only the mode. ",[336,758,233],{"href":234}," has the manifest, the configuration reference, and the operator endpoints; ",[336,761,228],{"href":229}," covers the deployment shape it extends.",[392,764,445],{"id":765},"when-to-turn-it-on",[329,767,768],{},"Reach for hot standby when a cold-restore window is the thing you can't afford — large state, a tight recovery-time objective, or a deploy cadence where even a fast restart's gap shows up downstream. For everything else, the single-instance default remains the simpler, cheaper choice, and it is unchanged whether hot standby exists or not.",[329,770,771,772,775],{},"This is the first cut of the failover story, and it is built to get smarter: promotion selection becomes lag-aware so the freshest standby wins, and the readiness threshold becomes a tunable rather than a binary. The model — one active, a warm passive spare, the fence as the backstop — is the part that stays. Start with the ",[336,773,774],{"href":234},"High availability guide",", and if you're running large-state stateful Kafka Streams in production today, that is exactly the workload this was built for.",{"title":777,"searchDepth":778,"depth":778,"links":779},"",2,[780,781,782,783,784,785,786,787,788],{"id":394,"depth":778,"text":395},{"id":448,"depth":778,"text":403},{"id":475,"depth":778,"text":409},{"id":541,"depth":778,"text":415},{"id":627,"depth":778,"text":421},{"id":657,"depth":778,"text":427},{"id":683,"depth":778,"text":433},{"id":730,"depth":778,"text":439},{"id":765,"depth":778,"text":445},"2026-06-17","StoatFlow recovery has always been fast restart — but restart time scales with state. Hot standby is an opt-in active\u002Fpassive pair that fails over in seconds regardless of state size, with exactly-once preserved across the handoff. Redundancy, not scale-out.","md",{"src":793},"\u002Fassets\u002Fblog\u002Fog\u002FStoatFlow_og-2_20260517.jpg",{},true,"\u002Fblog\u002Fhot-standby-high-availability",{"title":316,"description":790},"3.blog\u002F4.hot-standby-high-availability","LsS5ctjtBd2Q51pnj52tQmQ4TKfJGhL-mFzT8ifEQX4",[801,806],{"title":802,"path":803,"stem":804,"description":805,"children":-1},"KIP-1271: record headers in state stores, and the cost of a value format","\u002Fblog\u002Fkip-1271-record-headers-in-state-stores","3.blog\u002F3.kip-1271-record-headers-in-state-stores","KIP-1271\u002F1285 let a state store keep a record's headers next to its value — shipped in Apache Kafka 4.3.0. What the KIP is, where Kafka Streams has got to (and what is still in flight), how StoatFlow 1.0.0 compares — and why a one-varint format change becomes an engine-wide one.",{"title":807,"path":808,"stem":809,"description":810,"children":-1},"Measuring StoatFlow failover: four scenarios, from the logs","\u002Fblog\u002Fha-failover-testing","3.blog\u002F5.ha-failover-testing","The obvious way to test HA failover — kubectl delete pod — is a graceful SIGTERM handoff, not a crash. Here are the four real failover scenarios, the millisecond timing measured from pod logs (because a 10-second Prometheus scrape cannot resolve a one-second event), and what the numbers revealed: a single JVM crash recovers in place without failing over, and stop-the-world is not the latency blip.",1783713348943]