[{"data":1,"prerenderedAt":2551},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Foperating\u002Fkubernetes":314,"\u002Fdocs\u002Foperating\u002Fkubernetes-surround":2546},[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":228,"body":316,"description":2539,"extension":2540,"meta":2541,"navigation":2542,"pageType":2543,"path":229,"seo":2544,"stem":230,"__hash__":2545},"docs\u002F1.docs\u002F6.operating\u002F2.kubernetes.md",{"type":317,"value":318,"toc":2526},"minimark",[319,337,437,442,477,492,524,528,546,552,608,714,720,748,751,770,789,793,800,819,828,867,898,925,929,939,942,982,987,993,1081,1085,1092,1182,1267,1294,1297,1300,1368,1387,1391,1397,2261,2284,2288,2291,2369,2387,2393,2470,2474,2522],[320,321,322,323,327,328,333,334,336],"p",{},"StoatFlow runs as exactly one active JVM process, so on Kubernetes you deploy it as a ",[324,325,326],"strong",{},"single-replica StatefulSet"," — not a Deployment. A StatefulSet gives you a stable identity, a per-pod persistent volume for the state directory, and an update strategy that tears the old pod down before the new one starts, which is exactly what running ",[329,330,332],"a",{"href":331},"\u002Fdocs\u002Foperating#one-active-instance","one active instance"," requires. (For a hot-standby cluster, see ",[329,335,233],{"href":234}," — a readiness-gated StatefulSet of two or more replicas.)",[338,339,340],"tldr-panel",{},[341,342,343,379,393,406,420],"ul",{},[344,345,346,349,350,354,355,358,359,362,363,366,367,370,371,374,375,378],"li",{},[324,347,348],{},"Workload:"," ",[351,352,353],"code",{},"StatefulSet"," with ",[351,356,357],{},"replicas: 1",". Never a ",[351,360,361],{},"Deployment",", never ",[351,364,365],{},"replicas > 1"," — except the opt-in ",[329,368,369],{"href":234},"hot-standby"," cluster (",[351,372,373],{},"replicas: 2"," or more, ",[351,376,377],{},"ha.mode: active-standby",").",[344,380,381,384,385,388,389,392],{},[324,382,383],{},"State:"," a ",[351,386,387],{},"volumeClaimTemplates"," PVC mounted at the state directory; set ",[351,390,391],{},"stoatflow.state.dir"," to that path.",[344,394,395,349,398,401,402,405],{},[324,396,397],{},"Shutdown:",[351,399,400],{},"terminationGracePeriodSeconds"," ≥ your ",[351,403,404],{},"stoatflow.shutdown.timeout-ms"," (plus margin) so the final commit barrier completes.",[344,407,408,411,412,415,416,419],{},[324,409,410],{},"License:"," inject the key from a Kubernetes ",[351,413,414],{},"Secret"," — as ",[351,417,418],{},"STOATFLOW_LICENSE_KEY"," env, or as a mounted file. Never bake it into the image or a ConfigMap.",[344,421,422,349,425,428,429,432,433,436],{},[324,423,424],{},"Probes:",[351,426,427],{},"\u002Fhealth\u002Flive"," and ",[351,430,431],{},"\u002Fhealth\u002Fready"," on the HTTP port — see ",[329,434,435],{"href":239},"Probes",".",[438,439,441],"h2",{"id":440},"why-a-statefulset-not-a-deployment","Why a StatefulSet, not a Deployment",[320,443,444,445,354,447,449,450,453,454,457,458,461,462,465,466,470,471,473,474,476],{},"A ",[351,446,361],{},[351,448,357],{}," and the default ",[351,451,452],{},"RollingUpdate"," strategy will, during a rollout, ",[324,455,456],{},"start the new pod before terminating the old one"," (",[351,459,460],{},"maxSurge: 1","). For a moment two processes are alive and pointed at the same source topics — which is precisely the ",[329,463,464],{"href":331},"data-corruption scenario"," the engine cannot tolerate. (Hot standby is the supported way to run more than one replica: the standbys are ",[467,468,469],"em",{},"passive",", and a ",[351,472,452],{}," is readiness-gated so they never process concurrently — see ",[329,475,233],{"href":234},".)",[320,478,444,479,354,481,483,484,487,488,491],{},[351,480,353],{},[351,482,357],{}," does the opposite: on an update it ",[324,485,486],{},"terminates the old pod and waits for it to fully stop before creating the replacement",". Combined with ",[351,489,490],{},"podManagementPolicy: OrderedReady",", you get an at-most-one-running guarantee across rollouts. That, plus a stable PVC for the state directory, is why StatefulSet is the right primitive here.",[493,494,497],"callout",{"color":495,"icon":496},"error","i-lucide-alert-triangle",[320,498,499,500,502,503,506,507,510,511,370,513,374,515,517,518,521,522,436],{},"Do not deploy StoatFlow as a ",[351,501,361],{},", and never set ",[351,504,505],{},"replicas"," above ",[351,508,509],{},"1"," — unless you are running the opt-in ",[329,512,369],{"href":234},[351,514,373],{},[351,516,377],{},"), which keeps the standbys passive. Two ",[467,519,520],{},"active"," instances against the same source topics corrupt state. See ",[329,523,332],{"href":331},[438,525,527],{"id":526},"the-state-volume","The state volume",[320,529,530,531,533,534,537,538,541,542,545],{},"State stores are RocksDB-backed on local disk under ",[351,532,391],{}," (default ",[351,535,536],{},"${java.io.tmpdir}\u002Fstoatflow","). The state directory is ",[324,539,540],{},"durable, not authoritative"," — the changelog topics are the source of truth, and on restart the runtime rebuilds from them. A persistent volume doesn't change correctness; it changes ",[324,543,544],{},"restart speed",". With a surviving state directory the runtime replays only the changelog records since the last commit (a delta restore) instead of reading the whole changelog from scratch.",[320,547,548,549,551],{},"Mount a PVC at a fixed path and point ",[351,550,391],{}," at it:",[553,554,559],"pre",{"className":555,"code":556,"language":557,"meta":558,"style":558},"language-yaml shiki shiki-themes vitesse-light","# In the StatefulSet pod spec\nvolumeMounts:\n  - name: state\n    mountPath: \u002Fvar\u002Flib\u002Fstoatflow\u002Fstate\n","yaml","",[351,560,561,570,581,597],{"__ignoreMap":558},[562,563,566],"span",{"class":564,"line":565},"line",1,[562,567,569],{"class":568},"s8zF2","# In the StatefulSet pod spec\n",[562,571,573,577],{"class":564,"line":572},2,[562,574,576],{"class":575},"su6XF","volumeMounts",[562,578,580],{"class":579},"sYZai",":\n",[562,582,584,587,590,593],{"class":564,"line":583},3,[562,585,586],{"class":579},"  -",[562,588,589],{"class":575}," name",[562,591,592],{"class":579},":",[562,594,596],{"class":595},"spphp"," state\n",[562,598,600,603,605],{"class":564,"line":599},4,[562,601,602],{"class":575},"    mountPath",[562,604,592],{"class":579},[562,606,607],{"class":595}," \u002Fvar\u002Flib\u002Fstoatflow\u002Fstate\n",[553,609,611],{"className":555,"code":610,"language":557,"meta":558,"style":558},"# In the StatefulSet spec\nvolumeClaimTemplates:\n  - metadata:\n      name: state\n    spec:\n      accessModes: [\"ReadWriteOnce\"]\n      storageClassName: fast-ssd   # SSD\u002FNVMe — RocksDB is latency-sensitive\n      resources:\n        requests:\n          storage: 100Gi\n",[351,612,613,618,624,633,642,650,673,687,695,703],{"__ignoreMap":558},[562,614,615],{"class":564,"line":565},[562,616,617],{"class":568},"# In the StatefulSet spec\n",[562,619,620,622],{"class":564,"line":572},[562,621,387],{"class":575},[562,623,580],{"class":579},[562,625,626,628,631],{"class":564,"line":583},[562,627,586],{"class":579},[562,629,630],{"class":575}," metadata",[562,632,580],{"class":579},[562,634,635,638,640],{"class":564,"line":599},[562,636,637],{"class":575},"      name",[562,639,592],{"class":579},[562,641,596],{"class":595},[562,643,645,648],{"class":564,"line":644},5,[562,646,647],{"class":575},"    spec",[562,649,580],{"class":579},[562,651,653,656,658,661,665,668,670],{"class":564,"line":652},6,[562,654,655],{"class":575},"      accessModes",[562,657,592],{"class":579},[562,659,660],{"class":579}," [",[562,662,664],{"class":663},"sSP4y","\"",[562,666,667],{"class":595},"ReadWriteOnce",[562,669,664],{"class":663},[562,671,672],{"class":579},"]\n",[562,674,676,679,681,684],{"class":564,"line":675},7,[562,677,678],{"class":575},"      storageClassName",[562,680,592],{"class":579},[562,682,683],{"class":595}," fast-ssd",[562,685,686],{"class":568},"   # SSD\u002FNVMe — RocksDB is latency-sensitive\n",[562,688,690,693],{"class":564,"line":689},8,[562,691,692],{"class":575},"      resources",[562,694,580],{"class":579},[562,696,698,701],{"class":564,"line":697},9,[562,699,700],{"class":575},"        requests",[562,702,580],{"class":579},[562,704,706,709,711],{"class":564,"line":705},10,[562,707,708],{"class":575},"          storage",[562,710,592],{"class":579},[562,712,713],{"class":595}," 100Gi\n",[320,715,716,717,592],{},"Then set the directory to that mount, either in ",[351,718,719],{},"application.yaml",[553,721,723],{"className":555,"code":722,"language":557,"meta":558,"style":558},"stoatflow:\n  state:\n    dir: \u002Fvar\u002Flib\u002Fstoatflow\u002Fstate\n",[351,724,725,732,739],{"__ignoreMap":558},[562,726,727,730],{"class":564,"line":565},[562,728,729],{"class":575},"stoatflow",[562,731,580],{"class":579},[562,733,734,737],{"class":564,"line":572},[562,735,736],{"class":575},"  state",[562,738,580],{"class":579},[562,740,741,744,746],{"class":564,"line":583},[562,742,743],{"class":575},"    dir",[562,745,592],{"class":579},[562,747,607],{"class":595},[320,749,750],{},"…or via the env-var override (double underscore separates path segments):",[553,752,756],{"className":753,"code":754,"language":755,"meta":558,"style":558},"language-bash shiki shiki-themes vitesse-light","STOATFLOW__STATE__DIR=\u002Fvar\u002Flib\u002Fstoatflow\u002Fstate\n","bash",[351,757,758],{"__ignoreMap":558},[562,759,760,764,767],{"class":564,"line":565},[562,761,763],{"class":762},"svycV","STOATFLOW__STATE__DIR",[562,765,766],{"class":579},"=",[562,768,769],{"class":595},"\u002Fvar\u002Flib\u002Fstoatflow\u002Fstate\n",[493,771,774],{"color":772,"icon":773},"info","i-lucide-hard-drive",[320,775,776,777,780,781,784,785,436],{},"Size the PVC for your ",[324,778,779],{},"state size plus RocksDB working headroom"," (compaction, SST overhead) — not for the changelog. Use a fast ",[351,782,783],{},"storageClassName"," (local SSD or NVMe-backed): RocksDB read\u002Fwrite latency feeds directly into commit-barrier duration. See ",[329,786,788],{"href":787},"#resource-sizing","Resource sizing",[438,790,792],{"id":791},"graceful-shutdown-and-the-grace-period","Graceful shutdown and the grace period",[320,794,795,796,799],{},"On ",[351,797,798],{},"SIGTERM"," (which Kubernetes sends on pod termination) the runtime stops accepting new records, drains in-flight work, injects one final commit barrier, commits it, and closes the consumer and producer cleanly. That final commit is what keeps the shutdown exactly-once — interrupting it just means the next start resumes from the previous barrier, but letting it finish avoids re-processing on restart.",[320,801,802,803,533,805,808,809,533,811,814,815,818],{},"The runtime bounds its own graceful shutdown with ",[351,804,404],{},[351,806,807],{},"25000","). Kubernetes bounds it independently with ",[351,810,400],{},[351,812,813],{},"30","): when that elapses, Kubernetes sends ",[351,816,817],{},"SIGKILL"," regardless of progress.",[320,820,821,824,825,827],{},[324,822,823],{},"Align the two so Kubernetes doesn't kill a still-progressing shutdown."," Set ",[351,826,400],{}," comfortably above your shutdown timeout in seconds:",[553,829,831],{"className":555,"code":830,"language":557,"meta":558,"style":558},"spec:\n  template:\n    spec:\n      terminationGracePeriodSeconds: 60   # > stoatflow.shutdown.timeout-ms \u002F 1000, with margin\n",[351,832,833,840,847,853],{"__ignoreMap":558},[562,834,835,838],{"class":564,"line":565},[562,836,837],{"class":575},"spec",[562,839,580],{"class":579},[562,841,842,845],{"class":564,"line":572},[562,843,844],{"class":575},"  template",[562,846,580],{"class":579},[562,848,849,851],{"class":564,"line":583},[562,850,647],{"class":575},[562,852,580],{"class":579},[562,854,855,858,860,864],{"class":564,"line":599},[562,856,857],{"class":575},"      terminationGracePeriodSeconds",[562,859,592],{"class":579},[562,861,863],{"class":862},"s-TwI"," 60",[562,865,866],{"class":568},"   # > stoatflow.shutdown.timeout-ms \u002F 1000, with margin\n",[553,868,870],{"className":555,"code":869,"language":557,"meta":558,"style":558},"stoatflow:\n  shutdown:\n    timeout-ms: 25000   # default; raise for very large in-flight epochs\n",[351,871,872,878,885],{"__ignoreMap":558},[562,873,874,876],{"class":564,"line":565},[562,875,729],{"class":575},[562,877,580],{"class":579},[562,879,880,883],{"class":564,"line":572},[562,881,882],{"class":575},"  shutdown",[562,884,580],{"class":579},[562,886,887,890,892,895],{"class":564,"line":583},[562,888,889],{"class":575},"    timeout-ms",[562,891,592],{"class":579},[562,893,894],{"class":862}," 25000",[562,896,897],{"class":568},"   # default; raise for very large in-flight epochs\n",[320,899,900,901,904,905,907,908,910,911,914,915,918,919,921,922,924],{},"If your topology runs large epochs or slow stateful commits, raise ",[324,902,903],{},"both"," together — ",[351,906,404],{}," first, then ",[351,909,400],{}," so it stays larger. The runtime also has a hard-exit fallback that guarantees a clean process exit if shutdown itself hangs on a stuck lock (",[351,912,913],{},"stoatflow.shutdown.hard-exit-on-shutdown-hang",", default ",[351,916,917],{},"true","); leave it on so Kubernetes always observes a clean termination rather than relying solely on ",[351,920,817],{},". See ",[329,923,281],{"href":282}," for the full shutdown key set.",[438,926,928],{"id":927},"secure-license-injection","Secure license injection",[320,930,931,932,934,935,938],{},"StoatFlow validates a license at startup, so the pod needs the key — but the key is a secret and must never land in the image, a ConfigMap, or source control. Store it in a Kubernetes ",[351,933,414],{}," and surface it one of two ways. Both map onto the ",[329,936,937],{"href":24},"license resolution order",": an explicit key (env var) beats a key file.",[320,940,941],{},"First, create the Secret:",[553,943,945],{"className":753,"code":944,"language":755,"meta":558,"style":558},"kubectl create secret generic stoatflow-license \\\n  --from-literal=license-key=\"key\u002F...your production key...\"\n",[351,946,947,969],{"__ignoreMap":558},[562,948,949,953,956,959,962,965],{"class":564,"line":565},[562,950,952],{"class":951},"sySUi","kubectl",[562,954,955],{"class":595}," create",[562,957,958],{"class":595}," secret",[562,960,961],{"class":595}," generic",[562,963,964],{"class":595}," stoatflow-license",[562,966,968],{"class":967},"sEi1f"," \\\n",[562,970,971,974,976,979],{"class":564,"line":572},[562,972,973],{"class":967},"  --from-literal=license-key=",[562,975,664],{"class":663},[562,977,978],{"class":595},"key\u002F...your production key...",[562,980,981],{"class":663},"\"\n",[983,984,986],"h3",{"id":985},"option-a-secret-as-an-environment-variable","Option A — Secret as an environment variable",[320,988,989,990,992],{},"The simplest approach: project the secret value into ",[351,991,418],{},". The runtime reads the env var directly.",[553,994,996],{"className":555,"code":995,"language":557,"meta":558,"style":558},"# In the container spec\nenv:\n  - name: STOATFLOW_LICENSE_KEY\n    valueFrom:\n      secretKeyRef:\n        name: stoatflow-license\n        key: license-key\n  # Production REQUIRES an explicit environment label — set it per deployment:\n  - name: STOATFLOW_LICENSE_ENVIRONMENT\n    value: prod-eu-west\n",[351,997,998,1003,1010,1021,1028,1035,1045,1055,1060,1071],{"__ignoreMap":558},[562,999,1000],{"class":564,"line":565},[562,1001,1002],{"class":568},"# In the container spec\n",[562,1004,1005,1008],{"class":564,"line":572},[562,1006,1007],{"class":575},"env",[562,1009,580],{"class":579},[562,1011,1012,1014,1016,1018],{"class":564,"line":583},[562,1013,586],{"class":579},[562,1015,589],{"class":575},[562,1017,592],{"class":579},[562,1019,1020],{"class":595}," STOATFLOW_LICENSE_KEY\n",[562,1022,1023,1026],{"class":564,"line":599},[562,1024,1025],{"class":575},"    valueFrom",[562,1027,580],{"class":579},[562,1029,1030,1033],{"class":564,"line":644},[562,1031,1032],{"class":575},"      secretKeyRef",[562,1034,580],{"class":579},[562,1036,1037,1040,1042],{"class":564,"line":652},[562,1038,1039],{"class":575},"        name",[562,1041,592],{"class":579},[562,1043,1044],{"class":595}," stoatflow-license\n",[562,1046,1047,1050,1052],{"class":564,"line":675},[562,1048,1049],{"class":575},"        key",[562,1051,592],{"class":579},[562,1053,1054],{"class":595}," license-key\n",[562,1056,1057],{"class":564,"line":689},[562,1058,1059],{"class":568},"  # Production REQUIRES an explicit environment label — set it per deployment:\n",[562,1061,1062,1064,1066,1068],{"class":564,"line":697},[562,1063,586],{"class":579},[562,1065,589],{"class":575},[562,1067,592],{"class":579},[562,1069,1070],{"class":595}," STOATFLOW_LICENSE_ENVIRONMENT\n",[562,1072,1073,1076,1078],{"class":564,"line":705},[562,1074,1075],{"class":575},"    value",[562,1077,592],{"class":579},[562,1079,1080],{"class":595}," prod-eu-west\n",[983,1082,1084],{"id":1083},"option-b-secret-mounted-as-a-file","Option B — Secret mounted as a file",[320,1086,1087,1088,1091],{},"Mount the secret as a file and point ",[351,1089,1090],{},"STOATFLOW_LICENSE_FILE"," at it. Useful when your platform standardises on mounted secrets over env vars.",[553,1093,1095],{"className":555,"code":1094,"language":557,"meta":558,"style":558},"# In the container spec\nenv:\n  - name: STOATFLOW_LICENSE_FILE\n    value: \u002Fetc\u002Fstoatflow\u002Flicense\u002Flicense.key\n  - name: STOATFLOW_LICENSE_ENVIRONMENT\n    value: prod-eu-west\nvolumeMounts:\n  - name: license\n    mountPath: \u002Fetc\u002Fstoatflow\u002Flicense\n    readOnly: true\n",[351,1096,1097,1101,1107,1118,1127,1137,1145,1151,1162,1171],{"__ignoreMap":558},[562,1098,1099],{"class":564,"line":565},[562,1100,1002],{"class":568},[562,1102,1103,1105],{"class":564,"line":572},[562,1104,1007],{"class":575},[562,1106,580],{"class":579},[562,1108,1109,1111,1113,1115],{"class":564,"line":583},[562,1110,586],{"class":579},[562,1112,589],{"class":575},[562,1114,592],{"class":579},[562,1116,1117],{"class":595}," STOATFLOW_LICENSE_FILE\n",[562,1119,1120,1122,1124],{"class":564,"line":599},[562,1121,1075],{"class":575},[562,1123,592],{"class":579},[562,1125,1126],{"class":595}," \u002Fetc\u002Fstoatflow\u002Flicense\u002Flicense.key\n",[562,1128,1129,1131,1133,1135],{"class":564,"line":644},[562,1130,586],{"class":579},[562,1132,589],{"class":575},[562,1134,592],{"class":579},[562,1136,1070],{"class":595},[562,1138,1139,1141,1143],{"class":564,"line":652},[562,1140,1075],{"class":575},[562,1142,592],{"class":579},[562,1144,1080],{"class":595},[562,1146,1147,1149],{"class":564,"line":675},[562,1148,576],{"class":575},[562,1150,580],{"class":579},[562,1152,1153,1155,1157,1159],{"class":564,"line":689},[562,1154,586],{"class":579},[562,1156,589],{"class":575},[562,1158,592],{"class":579},[562,1160,1161],{"class":595}," license\n",[562,1163,1164,1166,1168],{"class":564,"line":697},[562,1165,602],{"class":575},[562,1167,592],{"class":579},[562,1169,1170],{"class":595}," \u002Fetc\u002Fstoatflow\u002Flicense\n",[562,1172,1173,1176,1178],{"class":564,"line":705},[562,1174,1175],{"class":575},"    readOnly",[562,1177,592],{"class":579},[562,1179,1181],{"class":1180},"sbBg2"," true\n",[553,1183,1185],{"className":555,"code":1184,"language":557,"meta":558,"style":558},"# In the pod spec\nvolumes:\n  - name: license\n    secret:\n      secretName: stoatflow-license\n      items:\n        - key: license-key\n          path: license.key\n      defaultMode: 0400   # owner read-only\n",[351,1186,1187,1192,1199,1209,1216,1225,1232,1244,1254],{"__ignoreMap":558},[562,1188,1189],{"class":564,"line":565},[562,1190,1191],{"class":568},"# In the pod spec\n",[562,1193,1194,1197],{"class":564,"line":572},[562,1195,1196],{"class":575},"volumes",[562,1198,580],{"class":579},[562,1200,1201,1203,1205,1207],{"class":564,"line":583},[562,1202,586],{"class":579},[562,1204,589],{"class":575},[562,1206,592],{"class":579},[562,1208,1161],{"class":595},[562,1210,1211,1214],{"class":564,"line":599},[562,1212,1213],{"class":575},"    secret",[562,1215,580],{"class":579},[562,1217,1218,1221,1223],{"class":564,"line":644},[562,1219,1220],{"class":575},"      secretName",[562,1222,592],{"class":579},[562,1224,1044],{"class":595},[562,1226,1227,1230],{"class":564,"line":652},[562,1228,1229],{"class":575},"      items",[562,1231,580],{"class":579},[562,1233,1234,1237,1240,1242],{"class":564,"line":675},[562,1235,1236],{"class":579},"        -",[562,1238,1239],{"class":575}," key",[562,1241,592],{"class":579},[562,1243,1054],{"class":595},[562,1245,1246,1249,1251],{"class":564,"line":689},[562,1247,1248],{"class":575},"          path",[562,1250,592],{"class":579},[562,1252,1253],{"class":595}," license.key\n",[562,1255,1256,1259,1261,1264],{"class":564,"line":697},[562,1257,1258],{"class":575},"      defaultMode",[562,1260,592],{"class":579},[562,1262,1263],{"class":862}," 0400",[562,1265,1266],{"class":568},"   # owner read-only\n",[493,1268,1270],{"color":1269,"icon":26},"warning",[320,1271,1272,1275,1276,1279,1280,1283,1284,1287,1288,1291,1292,436],{},[324,1273,1274],{},"Production requires an explicit environment label."," The Production tier refuses to start if it has to auto-generate one — set ",[351,1277,1278],{},"STOATFLOW_LICENSE_ENVIRONMENT"," (e.g. ",[351,1281,1282],{},"prod",", ",[351,1285,1286],{},"prod-eu-west",") per deployment so each gets its own node-locked seat. A mounted key file must be readable only by the runtime user (",[351,1289,1290],{},"defaultMode: 0400","); the runtime refuses an insecurely-permissioned file. Full details on ",[329,1293,23],{"href":24},[438,1295,788],{"id":1296},"resource-sizing",[320,1298,1299],{},"StoatFlow scales vertically — give the one process the CPU, memory, and disk it needs. As a starting point:",[1301,1302,1303,1322],"table",{},[1304,1305,1306],"thead",{},[1307,1308,1309,1313,1316,1319],"tr",{},[1310,1311,1312],"th",{},"Workload",[1310,1314,1315],{},"CPU",[1310,1317,1318],{},"Memory",[1310,1320,1321],{},"Disk",[1323,1324,1325,1340,1354],"tbody",{},[1307,1326,1327,1331,1334,1337],{},[1328,1329,1330],"td",{},"Light (~10K events\u002Fsec, ~1 GB state)",[1328,1332,1333],{},"4 cores",[1328,1335,1336],{},"8 GB",[1328,1338,1339],{},"20 GB SSD",[1307,1341,1342,1345,1348,1351],{},[1328,1343,1344],{},"Medium (~100K events\u002Fsec, ~10 GB state)",[1328,1346,1347],{},"16 cores",[1328,1349,1350],{},"64 GB",[1328,1352,1353],{},"100 GB SSD",[1307,1355,1356,1359,1362,1365],{},[1328,1357,1358],{},"Heavy (~500K events\u002Fsec, ~50 GB state)",[1328,1360,1361],{},"32 cores",[1328,1363,1364],{},"128 GB",[1328,1366,1367],{},"500 GB NVMe",[320,1369,1370,1371,1374,1375,1378,1379,1382,1383,1386],{},"Memory splits roughly into JVM heap (state cache), off-heap (RocksDB block cache, write buffers, producer buffers), and OS overhead — budget for all three, not just the heap. Set container ",[351,1372,1373],{},"requests"," equal to ",[351,1376,1377],{},"limits"," for the ",[324,1380,1381],{},"Guaranteed"," QoS class, so the kernel doesn't reclaim memory from the process under node pressure. These are starting points; ",[329,1384,1385],{"href":249},"Tuning"," covers how to refine lane count, commit cadence, and memory caps against your workload.",[438,1388,1390],{"id":1389},"a-representative-manifest","A representative manifest",[320,1392,1393,1394,1396],{},"A complete single-replica StatefulSet: ConfigMap for ",[351,1395,719],{},", Secret-injected license, persistent state volume, grace period aligned with shutdown, and both health probes. Adjust image, topic\u002Fbroker config, resources, and the license-injection option to your environment.",[553,1398,1400],{"className":555,"code":1399,"language":557,"meta":558,"style":558},"apiVersion: v1\nkind: ConfigMap\nmetadata:\n  name: stoatflow-config\ndata:\n  application.yaml: |\n    stoatflow:\n      application-id: my-stream-app\n      bootstrap-servers: kafka-broker:9092\n      state:\n        dir: \u002Fvar\u002Flib\u002Fstoatflow\u002Fstate\n      shutdown:\n        timeout-ms: 25000\n      license:\n        # The literal key is injected from a Secret via STOATFLOW_LICENSE_KEY (below);\n        # the explicit env var wins over anything in this file.\n        environment: prod-eu-west\n    runtime:\n      http:\n        enabled: true\n        port: 8080\n      metrics:\n        enabled: true\n---\napiVersion: apps\u002Fv1\nkind: StatefulSet\nmetadata:\n  name: my-stream-app\nspec:\n  replicas: 1                      # exactly one — never more\n  serviceName: my-stream-app\n  podManagementPolicy: OrderedReady\n  updateStrategy:\n    type: RollingUpdate            # StatefulSet rolling update stops the old pod before starting the new\n  selector:\n    matchLabels:\n      app: my-stream-app\n  template:\n    metadata:\n      labels:\n        app: my-stream-app\n    spec:\n      terminationGracePeriodSeconds: 60   # > stoatflow.shutdown.timeout-ms \u002F 1000, with margin\n      containers:\n        - name: app\n          image: my-registry\u002Fmy-stream-app:latest\n          ports:\n            - name: http\n              containerPort: 8080\n          env:\n            - name: STOATFLOW_LICENSE_KEY\n              valueFrom:\n                secretKeyRef:\n                  name: stoatflow-license\n                  key: license-key\n            # application.yaml is loaded from the mounted ConfigMap:\n            - name: STOATFLOW_CONFIG_FILES\n              value: \u002Fetc\u002Fstoatflow\u002Fapplication.yaml\n          readinessProbe:\n            httpGet:\n              path: \u002Fhealth\u002Fready\n              port: http\n            initialDelaySeconds: 30\n            periodSeconds: 10\n          livenessProbe:\n            httpGet:\n              path: \u002Fhealth\u002Flive\n              port: http\n            initialDelaySeconds: 60\n            periodSeconds: 30\n          resources:\n            requests:\n              cpu: \"16\"\n              memory: 64Gi\n            limits:\n              cpu: \"16\"\n              memory: 64Gi          # match requests → Guaranteed QoS\n          volumeMounts:\n            - name: config\n              mountPath: \u002Fetc\u002Fstoatflow\n              readOnly: true\n            - name: state\n              mountPath: \u002Fvar\u002Flib\u002Fstoatflow\u002Fstate\n      volumes:\n        - name: config\n          configMap:\n            name: stoatflow-config\n  volumeClaimTemplates:\n    - metadata:\n        name: state\n      spec:\n        accessModes: [\"ReadWriteOnce\"]\n        storageClassName: fast-ssd\n        resources:\n          requests:\n            storage: 100Gi\n",[351,1401,1402,1412,1422,1429,1439,1446,1456,1461,1466,1471,1476,1482,1488,1494,1500,1506,1512,1518,1524,1530,1536,1542,1548,1553,1559,1569,1579,1586,1596,1603,1617,1627,1638,1646,1660,1668,1676,1686,1693,1701,1709,1719,1726,1737,1745,1757,1768,1776,1789,1800,1808,1819,1827,1835,1845,1855,1861,1873,1884,1892,1900,1911,1921,1932,1943,1951,1958,1968,1977,1987,1996,2004,2012,2028,2039,2047,2060,2073,2081,2093,2104,2114,2125,2134,2142,2153,2161,2171,2179,2189,2198,2206,2224,2235,2243,2251],{"__ignoreMap":558},[562,1403,1404,1407,1409],{"class":564,"line":565},[562,1405,1406],{"class":575},"apiVersion",[562,1408,592],{"class":579},[562,1410,1411],{"class":595}," v1\n",[562,1413,1414,1417,1419],{"class":564,"line":572},[562,1415,1416],{"class":575},"kind",[562,1418,592],{"class":579},[562,1420,1421],{"class":595}," ConfigMap\n",[562,1423,1424,1427],{"class":564,"line":583},[562,1425,1426],{"class":575},"metadata",[562,1428,580],{"class":579},[562,1430,1431,1434,1436],{"class":564,"line":599},[562,1432,1433],{"class":575},"  name",[562,1435,592],{"class":579},[562,1437,1438],{"class":595}," stoatflow-config\n",[562,1440,1441,1444],{"class":564,"line":644},[562,1442,1443],{"class":575},"data",[562,1445,580],{"class":579},[562,1447,1448,1451,1453],{"class":564,"line":652},[562,1449,1450],{"class":575},"  application.yaml",[562,1452,592],{"class":579},[562,1454,1455],{"class":1180}," |\n",[562,1457,1458],{"class":564,"line":675},[562,1459,1460],{"class":595},"    stoatflow:\n",[562,1462,1463],{"class":564,"line":689},[562,1464,1465],{"class":595},"      application-id: my-stream-app\n",[562,1467,1468],{"class":564,"line":697},[562,1469,1470],{"class":595},"      bootstrap-servers: kafka-broker:9092\n",[562,1472,1473],{"class":564,"line":705},[562,1474,1475],{"class":595},"      state:\n",[562,1477,1479],{"class":564,"line":1478},11,[562,1480,1481],{"class":595},"        dir: \u002Fvar\u002Flib\u002Fstoatflow\u002Fstate\n",[562,1483,1485],{"class":564,"line":1484},12,[562,1486,1487],{"class":595},"      shutdown:\n",[562,1489,1491],{"class":564,"line":1490},13,[562,1492,1493],{"class":595},"        timeout-ms: 25000\n",[562,1495,1497],{"class":564,"line":1496},14,[562,1498,1499],{"class":595},"      license:\n",[562,1501,1503],{"class":564,"line":1502},15,[562,1504,1505],{"class":595},"        # The literal key is injected from a Secret via STOATFLOW_LICENSE_KEY (below);\n",[562,1507,1509],{"class":564,"line":1508},16,[562,1510,1511],{"class":595},"        # the explicit env var wins over anything in this file.\n",[562,1513,1515],{"class":564,"line":1514},17,[562,1516,1517],{"class":595},"        environment: prod-eu-west\n",[562,1519,1521],{"class":564,"line":1520},18,[562,1522,1523],{"class":595},"    runtime:\n",[562,1525,1527],{"class":564,"line":1526},19,[562,1528,1529],{"class":595},"      http:\n",[562,1531,1533],{"class":564,"line":1532},20,[562,1534,1535],{"class":595},"        enabled: true\n",[562,1537,1539],{"class":564,"line":1538},21,[562,1540,1541],{"class":595},"        port: 8080\n",[562,1543,1545],{"class":564,"line":1544},22,[562,1546,1547],{"class":595},"      metrics:\n",[562,1549,1551],{"class":564,"line":1550},23,[562,1552,1535],{"class":595},[562,1554,1556],{"class":564,"line":1555},24,[562,1557,1558],{"class":951},"---\n",[562,1560,1562,1564,1566],{"class":564,"line":1561},25,[562,1563,1406],{"class":575},[562,1565,592],{"class":579},[562,1567,1568],{"class":595}," apps\u002Fv1\n",[562,1570,1572,1574,1576],{"class":564,"line":1571},26,[562,1573,1416],{"class":575},[562,1575,592],{"class":579},[562,1577,1578],{"class":595}," StatefulSet\n",[562,1580,1582,1584],{"class":564,"line":1581},27,[562,1583,1426],{"class":575},[562,1585,580],{"class":579},[562,1587,1589,1591,1593],{"class":564,"line":1588},28,[562,1590,1433],{"class":575},[562,1592,592],{"class":579},[562,1594,1595],{"class":595}," my-stream-app\n",[562,1597,1599,1601],{"class":564,"line":1598},29,[562,1600,837],{"class":575},[562,1602,580],{"class":579},[562,1604,1606,1609,1611,1614],{"class":564,"line":1605},30,[562,1607,1608],{"class":575},"  replicas",[562,1610,592],{"class":579},[562,1612,1613],{"class":862}," 1",[562,1615,1616],{"class":568},"                      # exactly one — never more\n",[562,1618,1620,1623,1625],{"class":564,"line":1619},31,[562,1621,1622],{"class":575},"  serviceName",[562,1624,592],{"class":579},[562,1626,1595],{"class":595},[562,1628,1630,1633,1635],{"class":564,"line":1629},32,[562,1631,1632],{"class":575},"  podManagementPolicy",[562,1634,592],{"class":579},[562,1636,1637],{"class":595}," OrderedReady\n",[562,1639,1641,1644],{"class":564,"line":1640},33,[562,1642,1643],{"class":575},"  updateStrategy",[562,1645,580],{"class":579},[562,1647,1649,1652,1654,1657],{"class":564,"line":1648},34,[562,1650,1651],{"class":575},"    type",[562,1653,592],{"class":579},[562,1655,1656],{"class":595}," RollingUpdate",[562,1658,1659],{"class":568},"            # StatefulSet rolling update stops the old pod before starting the new\n",[562,1661,1663,1666],{"class":564,"line":1662},35,[562,1664,1665],{"class":575},"  selector",[562,1667,580],{"class":579},[562,1669,1671,1674],{"class":564,"line":1670},36,[562,1672,1673],{"class":575},"    matchLabels",[562,1675,580],{"class":579},[562,1677,1679,1682,1684],{"class":564,"line":1678},37,[562,1680,1681],{"class":575},"      app",[562,1683,592],{"class":579},[562,1685,1595],{"class":595},[562,1687,1689,1691],{"class":564,"line":1688},38,[562,1690,844],{"class":575},[562,1692,580],{"class":579},[562,1694,1696,1699],{"class":564,"line":1695},39,[562,1697,1698],{"class":575},"    metadata",[562,1700,580],{"class":579},[562,1702,1704,1707],{"class":564,"line":1703},40,[562,1705,1706],{"class":575},"      labels",[562,1708,580],{"class":579},[562,1710,1712,1715,1717],{"class":564,"line":1711},41,[562,1713,1714],{"class":575},"        app",[562,1716,592],{"class":579},[562,1718,1595],{"class":595},[562,1720,1722,1724],{"class":564,"line":1721},42,[562,1723,647],{"class":575},[562,1725,580],{"class":579},[562,1727,1729,1731,1733,1735],{"class":564,"line":1728},43,[562,1730,857],{"class":575},[562,1732,592],{"class":579},[562,1734,863],{"class":862},[562,1736,866],{"class":568},[562,1738,1740,1743],{"class":564,"line":1739},44,[562,1741,1742],{"class":575},"      containers",[562,1744,580],{"class":579},[562,1746,1748,1750,1752,1754],{"class":564,"line":1747},45,[562,1749,1236],{"class":579},[562,1751,589],{"class":575},[562,1753,592],{"class":579},[562,1755,1756],{"class":595}," app\n",[562,1758,1760,1763,1765],{"class":564,"line":1759},46,[562,1761,1762],{"class":575},"          image",[562,1764,592],{"class":579},[562,1766,1767],{"class":595}," my-registry\u002Fmy-stream-app:latest\n",[562,1769,1771,1774],{"class":564,"line":1770},47,[562,1772,1773],{"class":575},"          ports",[562,1775,580],{"class":579},[562,1777,1779,1782,1784,1786],{"class":564,"line":1778},48,[562,1780,1781],{"class":579},"            -",[562,1783,589],{"class":575},[562,1785,592],{"class":579},[562,1787,1788],{"class":595}," http\n",[562,1790,1792,1795,1797],{"class":564,"line":1791},49,[562,1793,1794],{"class":575},"              containerPort",[562,1796,592],{"class":579},[562,1798,1799],{"class":862}," 8080\n",[562,1801,1803,1806],{"class":564,"line":1802},50,[562,1804,1805],{"class":575},"          env",[562,1807,580],{"class":579},[562,1809,1811,1813,1815,1817],{"class":564,"line":1810},51,[562,1812,1781],{"class":579},[562,1814,589],{"class":575},[562,1816,592],{"class":579},[562,1818,1020],{"class":595},[562,1820,1822,1825],{"class":564,"line":1821},52,[562,1823,1824],{"class":575},"              valueFrom",[562,1826,580],{"class":579},[562,1828,1830,1833],{"class":564,"line":1829},53,[562,1831,1832],{"class":575},"                secretKeyRef",[562,1834,580],{"class":579},[562,1836,1838,1841,1843],{"class":564,"line":1837},54,[562,1839,1840],{"class":575},"                  name",[562,1842,592],{"class":579},[562,1844,1044],{"class":595},[562,1846,1848,1851,1853],{"class":564,"line":1847},55,[562,1849,1850],{"class":575},"                  key",[562,1852,592],{"class":579},[562,1854,1054],{"class":595},[562,1856,1858],{"class":564,"line":1857},56,[562,1859,1860],{"class":568},"            # application.yaml is loaded from the mounted ConfigMap:\n",[562,1862,1864,1866,1868,1870],{"class":564,"line":1863},57,[562,1865,1781],{"class":579},[562,1867,589],{"class":575},[562,1869,592],{"class":579},[562,1871,1872],{"class":595}," STOATFLOW_CONFIG_FILES\n",[562,1874,1876,1879,1881],{"class":564,"line":1875},58,[562,1877,1878],{"class":575},"              value",[562,1880,592],{"class":579},[562,1882,1883],{"class":595}," \u002Fetc\u002Fstoatflow\u002Fapplication.yaml\n",[562,1885,1887,1890],{"class":564,"line":1886},59,[562,1888,1889],{"class":575},"          readinessProbe",[562,1891,580],{"class":579},[562,1893,1895,1898],{"class":564,"line":1894},60,[562,1896,1897],{"class":575},"            httpGet",[562,1899,580],{"class":579},[562,1901,1903,1906,1908],{"class":564,"line":1902},61,[562,1904,1905],{"class":575},"              path",[562,1907,592],{"class":579},[562,1909,1910],{"class":595}," \u002Fhealth\u002Fready\n",[562,1912,1914,1917,1919],{"class":564,"line":1913},62,[562,1915,1916],{"class":575},"              port",[562,1918,592],{"class":579},[562,1920,1788],{"class":595},[562,1922,1924,1927,1929],{"class":564,"line":1923},63,[562,1925,1926],{"class":575},"            initialDelaySeconds",[562,1928,592],{"class":579},[562,1930,1931],{"class":862}," 30\n",[562,1933,1935,1938,1940],{"class":564,"line":1934},64,[562,1936,1937],{"class":575},"            periodSeconds",[562,1939,592],{"class":579},[562,1941,1942],{"class":862}," 10\n",[562,1944,1946,1949],{"class":564,"line":1945},65,[562,1947,1948],{"class":575},"          livenessProbe",[562,1950,580],{"class":579},[562,1952,1954,1956],{"class":564,"line":1953},66,[562,1955,1897],{"class":575},[562,1957,580],{"class":579},[562,1959,1961,1963,1965],{"class":564,"line":1960},67,[562,1962,1905],{"class":575},[562,1964,592],{"class":579},[562,1966,1967],{"class":595}," \u002Fhealth\u002Flive\n",[562,1969,1971,1973,1975],{"class":564,"line":1970},68,[562,1972,1916],{"class":575},[562,1974,592],{"class":579},[562,1976,1788],{"class":595},[562,1978,1980,1982,1984],{"class":564,"line":1979},69,[562,1981,1926],{"class":575},[562,1983,592],{"class":579},[562,1985,1986],{"class":862}," 60\n",[562,1988,1990,1992,1994],{"class":564,"line":1989},70,[562,1991,1937],{"class":575},[562,1993,592],{"class":579},[562,1995,1931],{"class":862},[562,1997,1999,2002],{"class":564,"line":1998},71,[562,2000,2001],{"class":575},"          resources",[562,2003,580],{"class":579},[562,2005,2007,2010],{"class":564,"line":2006},72,[562,2008,2009],{"class":575},"            requests",[562,2011,580],{"class":579},[562,2013,2015,2018,2020,2023,2026],{"class":564,"line":2014},73,[562,2016,2017],{"class":575},"              cpu",[562,2019,592],{"class":579},[562,2021,2022],{"class":663}," \"",[562,2024,2025],{"class":595},"16",[562,2027,981],{"class":663},[562,2029,2031,2034,2036],{"class":564,"line":2030},74,[562,2032,2033],{"class":575},"              memory",[562,2035,592],{"class":579},[562,2037,2038],{"class":595}," 64Gi\n",[562,2040,2042,2045],{"class":564,"line":2041},75,[562,2043,2044],{"class":575},"            limits",[562,2046,580],{"class":579},[562,2048,2050,2052,2054,2056,2058],{"class":564,"line":2049},76,[562,2051,2017],{"class":575},[562,2053,592],{"class":579},[562,2055,2022],{"class":663},[562,2057,2025],{"class":595},[562,2059,981],{"class":663},[562,2061,2063,2065,2067,2070],{"class":564,"line":2062},77,[562,2064,2033],{"class":575},[562,2066,592],{"class":579},[562,2068,2069],{"class":595}," 64Gi",[562,2071,2072],{"class":568},"          # match requests → Guaranteed QoS\n",[562,2074,2076,2079],{"class":564,"line":2075},78,[562,2077,2078],{"class":575},"          volumeMounts",[562,2080,580],{"class":579},[562,2082,2084,2086,2088,2090],{"class":564,"line":2083},79,[562,2085,1781],{"class":579},[562,2087,589],{"class":575},[562,2089,592],{"class":579},[562,2091,2092],{"class":595}," config\n",[562,2094,2096,2099,2101],{"class":564,"line":2095},80,[562,2097,2098],{"class":575},"              mountPath",[562,2100,592],{"class":579},[562,2102,2103],{"class":595}," \u002Fetc\u002Fstoatflow\n",[562,2105,2107,2110,2112],{"class":564,"line":2106},81,[562,2108,2109],{"class":575},"              readOnly",[562,2111,592],{"class":579},[562,2113,1181],{"class":1180},[562,2115,2117,2119,2121,2123],{"class":564,"line":2116},82,[562,2118,1781],{"class":579},[562,2120,589],{"class":575},[562,2122,592],{"class":579},[562,2124,596],{"class":595},[562,2126,2128,2130,2132],{"class":564,"line":2127},83,[562,2129,2098],{"class":575},[562,2131,592],{"class":579},[562,2133,607],{"class":595},[562,2135,2137,2140],{"class":564,"line":2136},84,[562,2138,2139],{"class":575},"      volumes",[562,2141,580],{"class":579},[562,2143,2145,2147,2149,2151],{"class":564,"line":2144},85,[562,2146,1236],{"class":579},[562,2148,589],{"class":575},[562,2150,592],{"class":579},[562,2152,2092],{"class":595},[562,2154,2156,2159],{"class":564,"line":2155},86,[562,2157,2158],{"class":575},"          configMap",[562,2160,580],{"class":579},[562,2162,2164,2167,2169],{"class":564,"line":2163},87,[562,2165,2166],{"class":575},"            name",[562,2168,592],{"class":579},[562,2170,1438],{"class":595},[562,2172,2174,2177],{"class":564,"line":2173},88,[562,2175,2176],{"class":575},"  volumeClaimTemplates",[562,2178,580],{"class":579},[562,2180,2182,2185,2187],{"class":564,"line":2181},89,[562,2183,2184],{"class":579},"    -",[562,2186,630],{"class":575},[562,2188,580],{"class":579},[562,2190,2192,2194,2196],{"class":564,"line":2191},90,[562,2193,1039],{"class":575},[562,2195,592],{"class":579},[562,2197,596],{"class":595},[562,2199,2201,2204],{"class":564,"line":2200},91,[562,2202,2203],{"class":575},"      spec",[562,2205,580],{"class":579},[562,2207,2209,2212,2214,2216,2218,2220,2222],{"class":564,"line":2208},92,[562,2210,2211],{"class":575},"        accessModes",[562,2213,592],{"class":579},[562,2215,660],{"class":579},[562,2217,664],{"class":663},[562,2219,667],{"class":595},[562,2221,664],{"class":663},[562,2223,672],{"class":579},[562,2225,2227,2230,2232],{"class":564,"line":2226},93,[562,2228,2229],{"class":575},"        storageClassName",[562,2231,592],{"class":579},[562,2233,2234],{"class":595}," fast-ssd\n",[562,2236,2238,2241],{"class":564,"line":2237},94,[562,2239,2240],{"class":575},"        resources",[562,2242,580],{"class":579},[562,2244,2246,2249],{"class":564,"line":2245},95,[562,2247,2248],{"class":575},"          requests",[562,2250,580],{"class":579},[562,2252,2254,2257,2259],{"class":564,"line":2253},96,[562,2255,2256],{"class":575},"            storage",[562,2258,592],{"class":579},[562,2260,713],{"class":595},[493,2262,2264],{"color":772,"icon":2263},"i-lucide-info",[320,2265,2266,2269,2270,2272,2273,1283,2276,2279,2280,2283],{},[351,2267,2268],{},"STOATFLOW_CONFIG_FILES"," points the runtime at the mounted ",[351,2271,719],{},". The container image is the one built by the StoatFlow build conventions (Jib, on Gradle or Maven) — it already sets the required JVM flags (",[351,2274,2275],{},"--enable-preview",[351,2277,2278],{},"--enable-native-access=ALL-UNNAMED",") and runs as a non-root user. See ",[329,2281,2282],{"href":211},"Docker"," for image build details.",[438,2285,2287],{"id":2286},"verify-the-rollout","Verify the rollout",[320,2289,2290],{},"After applying the manifests, confirm the single pod comes up and reports ready:",[553,2292,2294],{"className":753,"code":2293,"language":755,"meta":558,"style":558},"kubectl rollout status statefulset\u002Fmy-stream-app\nkubectl get pods -l app=my-stream-app          # exactly one pod, 1\u002F1 Running\n\n# Port-forward the HTTP port and check readiness:\nkubectl port-forward statefulset\u002Fmy-stream-app 8080:8080 &\ncurl -s localhost:8080\u002Fhealth\u002Fready            # {\"status\":\"UP\", ...} once state restoration completes\n",[351,2295,2296,2309,2328,2334,2339,2355],{"__ignoreMap":558},[562,2297,2298,2300,2303,2306],{"class":564,"line":565},[562,2299,952],{"class":951},[562,2301,2302],{"class":595}," rollout",[562,2304,2305],{"class":595}," status",[562,2307,2308],{"class":595}," statefulset\u002Fmy-stream-app\n",[562,2310,2311,2313,2316,2319,2322,2325],{"class":564,"line":572},[562,2312,952],{"class":951},[562,2314,2315],{"class":595}," get",[562,2317,2318],{"class":595}," pods",[562,2320,2321],{"class":967}," -l",[562,2323,2324],{"class":595}," app=my-stream-app",[562,2326,2327],{"class":568},"          # exactly one pod, 1\u002F1 Running\n",[562,2329,2330],{"class":564,"line":583},[562,2331,2333],{"emptyLinePlaceholder":2332},true,"\n",[562,2335,2336],{"class":564,"line":599},[562,2337,2338],{"class":568},"# Port-forward the HTTP port and check readiness:\n",[562,2340,2341,2343,2346,2349,2352],{"class":564,"line":644},[562,2342,952],{"class":951},[562,2344,2345],{"class":595}," port-forward",[562,2347,2348],{"class":595}," statefulset\u002Fmy-stream-app",[562,2350,2351],{"class":595}," 8080:8080",[562,2353,2354],{"class":579}," &\n",[562,2356,2357,2360,2363,2366],{"class":564,"line":652},[562,2358,2359],{"class":951},"curl",[562,2361,2362],{"class":967}," -s",[562,2364,2365],{"class":595}," localhost:8080\u002Fhealth\u002Fready",[562,2367,2368],{"class":568},"            # {\"status\":\"UP\", ...} once state restoration completes\n",[320,2370,2371,2372,2374,2375,2378,2379,2382,2383,2386],{},"During cold start ",[351,2373,431],{}," returns ",[351,2376,2377],{},"503"," until state restoration finishes — that's expected and is exactly why the readiness probe gates traffic. On a rolling update, watch that the old pod reaches ",[351,2380,2381],{},"Terminating"," and stops ",[324,2384,2385],{},"before"," the replacement is created; the StatefulSet guarantees this ordering, but it's worth confirming on first deploy.",[320,2388,2389,2390,592],{},"The rollout you should observe, end to end — for the multi-replica hot-standby roll, where readiness additionally enforces the redundancy floor, see ",[329,2391,233],{"href":2392},"\u002Fdocs\u002Foperating\u002Fhigh-availability#how-readiness-gates-a-rolling-deploy",[553,2394,2398],{"className":2395,"code":2396,"language":2397,"meta":558,"style":558},"language-mermaid shiki shiki-themes vitesse-light","sequenceDiagram\n    autonumber\n    participant K as StatefulSet controller\n    participant O as Old pod\n    participant N as New pod\n\n    K->>O: SIGTERM\n    O->>O: drain in-flight work, commit final barrier\n    O-->>K: terminated (fully stopped)\n    note over K: only now is the\u003Cbr\u002F>replacement created\n    K->>N: create pod\n    N->>N: restore state (delta replay\u003Cbr\u002F>if the PVC survived)\n    note over N: \u002Fhealth\u002Fready 503\u003Cbr\u002F>while restoring\n    N-->>K: ready — rollout complete\n","mermaid",[351,2399,2400,2406,2411,2416,2421,2426,2430,2435,2440,2445,2450,2455,2460,2465],{"__ignoreMap":558},[562,2401,2402],{"class":564,"line":565},[562,2403,2405],{"class":2404},"suHK_","sequenceDiagram\n",[562,2407,2408],{"class":564,"line":572},[562,2409,2410],{"class":2404},"    autonumber\n",[562,2412,2413],{"class":564,"line":583},[562,2414,2415],{"class":2404},"    participant K as StatefulSet controller\n",[562,2417,2418],{"class":564,"line":599},[562,2419,2420],{"class":2404},"    participant O as Old pod\n",[562,2422,2423],{"class":564,"line":644},[562,2424,2425],{"class":2404},"    participant N as New pod\n",[562,2427,2428],{"class":564,"line":652},[562,2429,2333],{"emptyLinePlaceholder":2332},[562,2431,2432],{"class":564,"line":675},[562,2433,2434],{"class":2404},"    K->>O: SIGTERM\n",[562,2436,2437],{"class":564,"line":689},[562,2438,2439],{"class":2404},"    O->>O: drain in-flight work, commit final barrier\n",[562,2441,2442],{"class":564,"line":697},[562,2443,2444],{"class":2404},"    O-->>K: terminated (fully stopped)\n",[562,2446,2447],{"class":564,"line":705},[562,2448,2449],{"class":2404},"    note over K: only now is the\u003Cbr\u002F>replacement created\n",[562,2451,2452],{"class":564,"line":1478},[562,2453,2454],{"class":2404},"    K->>N: create pod\n",[562,2456,2457],{"class":564,"line":1484},[562,2458,2459],{"class":2404},"    N->>N: restore state (delta replay\u003Cbr\u002F>if the PVC survived)\n",[562,2461,2462],{"class":564,"line":1490},[562,2463,2464],{"class":2404},"    note over N: \u002Fhealth\u002Fready 503\u003Cbr\u002F>while restoring\n",[562,2466,2467],{"class":564,"line":1496},[562,2468,2469],{"class":2404},"    N-->>K: ready — rollout complete\n",[438,2471,2473],{"id":2472},"next-steps","Next steps",[341,2475,2476,2483,2490,2501,2508,2515],{},[344,2477,2478,2482],{},[324,2479,2480],{},[329,2481,233],{"href":234}," — the opt-in hot-standby cluster: a multi-replica StatefulSet for near-instant failover.",[344,2484,2485,2489],{},[324,2486,2487],{},[329,2488,435],{"href":239}," — liveness vs. readiness, the JSON each returns, and how to tune probe timings.",[344,2491,2492,2496,2497,2500],{},[324,2493,2494],{},[329,2495,243],{"href":244}," — scraping ",[351,2498,2499],{},"\u002Fmetrics",", the key metrics to alert on, and structured logs.",[344,2502,2503,2507],{},[324,2504,2505],{},[329,2506,1385],{"href":249}," — lane count, commit cadence, and memory caps for your workload.",[344,2509,2510,2514],{},[324,2511,2512],{},[329,2513,253],{"href":254}," — everything to verify before going live.",[344,2516,2517,2521],{},[324,2518,2519],{},[329,2520,23],{"href":24}," — the full license reference, including CI\u002FCD.",[2523,2524,2525],"style",{},"html pre.shiki code .s8zF2, html code.shiki .s8zF2{--shiki-default:#A0ADA0}html pre.shiki code .su6XF, html code.shiki .su6XF{--shiki-default:#998418}html pre.shiki code .sYZai, html code.shiki .sYZai{--shiki-default:#999999}html pre.shiki code .spphp, html code.shiki .spphp{--shiki-default:#B56959}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sSP4y, html code.shiki .sSP4y{--shiki-default:#B5695977}html pre.shiki code .svycV, html code.shiki .svycV{--shiki-default:#B07D48}html pre.shiki code .s-TwI, html code.shiki .s-TwI{--shiki-default:#2F798A}html pre.shiki code .sySUi, html code.shiki .sySUi{--shiki-default:#59873A}html pre.shiki code .sEi1f, html code.shiki .sEi1f{--shiki-default:#A65E2B}html pre.shiki code .sbBg2, html code.shiki .sbBg2{--shiki-default:#1E754F}html pre.shiki code .suHK_, html code.shiki .suHK_{--shiki-default:#393A34}",{"title":558,"searchDepth":572,"depth":572,"links":2527},[2528,2529,2530,2531,2535,2536,2537,2538],{"id":440,"depth":572,"text":441},{"id":526,"depth":572,"text":527},{"id":791,"depth":572,"text":792},{"id":927,"depth":572,"text":928,"children":2532},[2533,2534],{"id":985,"depth":583,"text":986},{"id":1083,"depth":583,"text":1084},{"id":1296,"depth":572,"text":788},{"id":1389,"depth":572,"text":1390},{"id":2286,"depth":572,"text":2287},{"id":2472,"depth":572,"text":2473},"Deploy a StoatFlow application as a single-replica StatefulSet — persistent state volume, termination grace aligned with shutdown, secure license injection, and a representative manifest.","md",{},{"icon":231},"how-to",{"title":228,"description":2539},"mHJKY5x-BLUniuu6MbhnV2EhE0CHj4YIE7nbGBb7UhY",[2547,2549],{"title":225,"path":221,"stem":222,"description":2548,"icon":226,"children":-1},"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.",{"title":233,"path":234,"stem":235,"description":2550,"icon":236,"children":-1},"Two HA tiers for StoatFlow — fast restart by default, or an opt-in hot-standby cluster (one active plus one or more warm standbys) with near-instant, lag-aware failover — how to choose, configure, deploy, and operate each.",1783713362704]