[{"data":1,"prerenderedAt":2123},["ShallowReactive",2],{"navigation":3,"\u002Fdocs\u002Fconcepts\u002Flanes-and-parallelism":314,"\u002Fdocs\u002Fconcepts\u002Flanes-and-parallelism-surround":2118},[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":57,"body":316,"description":2111,"extension":2112,"meta":2113,"navigation":2114,"pageType":2115,"path":58,"seo":2116,"stem":59,"__hash__":2117},"docs\u002F1.docs\u002F2.concepts\u002F4.lanes-and-parallelism.md",{"type":317,"value":318,"toc":2101},"minimark",[319,332,369,374,381,409,412,416,439,446,450,453,460,482,719,729,782,787,790,824,832,836,850,853,1581,1591,1601,1605,1618,1621,1627,2049,2059,2063,2097],[320,321,322,323,327,328,331],"p",{},"A ",[324,325,326],"strong",{},"lane"," is StoatFlow's unit of concurrent processing inside the single JVM. Records are routed to lanes by their key: the same key always lands on the same lane, so the topology sees that key's events in arrival order; different keys run on different lanes in parallel. This page explains what that buys you and how to think about lane count. For the runtime mechanism behind it, see ",[329,330,47],"a",{"href":48},".",[333,334,335],"tldr-panel",{},[336,337,338,345,351,357,363],"ul",{},[339,340,341,344],"li",{},[324,342,343],{},"Same key → same lane → in-order."," Per-key ordering is preserved without a global lock.",[339,346,347,350],{},[324,348,349],{},"Different keys → different lanes → parallel."," Concurrency comes from key spread, not partition count.",[339,352,353,356],{},[324,354,355],{},"Lane count is decoupled from Kafka partitions."," It scales with CPU cores, configured at startup.",[339,358,359,362],{},[324,360,361],{},"Lanes run on virtual threads",", so a lane blocked on a REST call, DB query, or AI inference parks at near-zero cost — in-line enrichment is natural.",[339,364,365,368],{},[324,366,367],{},"Re-keying happens in-memory"," between lanes — no repartition topic, no broker round-trip.",[370,371,373],"h2",{"id":372},"key-affinity-same-key-same-lane","Key affinity: same key, same lane",[320,375,376,377,380],{},"When a record arrives, the engine inspects its key and consistently routes it to one lane — the same key maps to the same lane every time. Because one lane processes one key's events sequentially, in the order Kafka delivered them, ",[324,378,379],{},"per-key ordering is guaranteed"," even though many lanes run at once.",[320,382,383,384,388,389,388,392,395,396,399,400,404,405,408],{},"This is also what makes state safe under concurrency. A stateful operator — ",[385,386,387],"code",{},"count",", ",[385,390,391],{},"reduce",[385,393,394],{},"aggregate",", a join, or a custom ",[385,397,398],{},"Processor"," reading and writing a store — only ever touches a given key from the single lane that owns it. Updates to one key are serialized on that lane; updates to different keys proceed in parallel on other lanes. No global lock, no contention on the common path. The reasoning is spelled out under ",[329,401,403],{"href":402},"\u002Fdocs\u002Fconcepts\u002Farchitecture#state-stores-and-durability","State stores and durability"," on the architecture page; the ",[329,406,407],{"href":63},"State and thread safety"," concept page goes deeper on the rare cross-key case.",[320,410,411],{},"The flip side: parallelism is a function of how your keys spread across lanes. A stream where every record carries the same key runs effectively single-threaded — every record routes to one lane, by design, to preserve that key's order. Throughput on a stateful topology comes from having many distinct, evenly distributed keys, not from adding lanes alone.",[370,413,415],{"id":414},"different-keys-run-in-parallel","Different keys run in parallel",[320,417,418,419,388,422,425,426,429,430,434,435,438],{},"Distinct keys are spread across lanes, so independent keys process concurrently. In a word-count topology, ",[385,420,421],{},"the",[385,423,424],{},"quick",", and ",[385,427,428],{},"fox"," each own their running total on whichever lane their key maps to, and all three advance at the same time. The aggregation you write stays sequential ",[431,432,433],"em",{},"per key"," — exactly what correctness needs — while the engine extracts parallelism ",[431,436,437],{},"across keys"," for free.",[320,440,441,442,445],{},"You do not wire any of this up. The DSL operators and your custom processors are written as if single-threaded per key; the engine provides the cross-key concurrency underneath. The grouping key you choose (",[385,443,444],{},"groupBy",", the join key, the record key on a source) is therefore also your unit of parallelism — pick a key with enough distinct values to keep lanes busy.",[370,447,449],{"id":448},"lane-count-is-decoupled-from-partition-count","Lane count is decoupled from partition count",[320,451,452],{},"In standard Kafka Streams, processing parallelism is capped by the partition count of the input topics: one task per partition, with each stream thread running one or more tasks. To process more in parallel you add partitions — a topic-level change with downstream consequences.",[320,454,455,456,459],{},"StoatFlow breaks that coupling. The single consumer reads ",[324,457,458],{},"every"," partition of every source topic, and the engine then distributes work across however many lanes you configure. Lane count is independent of partition count — it scales with the cores on the machine, set once at startup.",[320,461,462,463,466,467,470,471,474,475,478,479,331],{},"Configure it with ",[385,464,465],{},"numLanes"," in ",[385,468,469],{},"StreamsConfig",", or the ",[385,472,473],{},"stoatflow.lanes.count"," key in ",[385,476,477],{},"application.yaml",". The default is ",[385,480,481],{},"max(2, available CPU cores)",[483,484,486,603],"code-tabs",{"group":485},"lang",[487,488,494],"pre",{"className":489,"code":490,"filename":491,"language":492,"meta":493,"style":493},"language-kotlin shiki shiki-themes vitesse-light","val config =\n    StreamsConfig(\n        applicationId = \"map-filter-example\",\n        bootstrapServers = \"localhost:9092\",\n        defaultKeySerde = Serdes.String(),\n        defaultValueSerde = Serdes.String(),\n        numLanes = 6,\n    )\n","Kotlin","kotlin","",[385,495,496,513,523,539,552,569,583,597],{"__ignoreMap":493},[497,498,501,505,509],"span",{"class":499,"line":500},"line",1,[497,502,504],{"class":503},"sbBg2","val",[497,506,508],{"class":507},"suHK_"," config ",[497,510,512],{"class":511},"sYZai","=\n",[497,514,516,520],{"class":499,"line":515},2,[497,517,519],{"class":518},"sySUi","    StreamsConfig",[497,521,522],{"class":507},"(\n",[497,524,526,529,532,536],{"class":499,"line":525},3,[497,527,528],{"class":507},"        applicationId ",[497,530,531],{"class":511},"=",[497,533,535],{"class":534},"spphp"," \"map-filter-example\"",[497,537,538],{"class":507},",\n",[497,540,542,545,547,550],{"class":499,"line":541},4,[497,543,544],{"class":507},"        bootstrapServers ",[497,546,531],{"class":511},[497,548,549],{"class":534}," \"localhost:9092\"",[497,551,538],{"class":507},[497,553,555,558,560,563,566],{"class":499,"line":554},5,[497,556,557],{"class":507},"        defaultKeySerde ",[497,559,531],{"class":511},[497,561,562],{"class":507}," Serdes.",[497,564,565],{"class":518},"String",[497,567,568],{"class":507},"(),\n",[497,570,572,575,577,579,581],{"class":499,"line":571},6,[497,573,574],{"class":507},"        defaultValueSerde ",[497,576,531],{"class":511},[497,578,562],{"class":507},[497,580,565],{"class":518},[497,582,568],{"class":507},[497,584,586,589,591,595],{"class":499,"line":585},7,[497,587,588],{"class":507},"        numLanes ",[497,590,531],{"class":511},[497,592,594],{"class":593},"s-TwI"," 6",[497,596,538],{"class":507},[497,598,600],{"class":499,"line":599},8,[497,601,602],{"class":507},"    )\n",[487,604,609],{"className":605,"code":606,"filename":607,"language":608,"meta":493,"style":493},"language-java shiki shiki-themes vitesse-light","var config =\n    StreamsConfig.builder(\"map-filter-example\", \"localhost:9092\")\n        .defaultKeySerde(Serdes.String())\n        .defaultValueSerde(Serdes.String())\n        .numLanes(6)\n        .build();\n","Java","java",[385,610,611,624,659,679,696,709],{"__ignoreMap":493},[497,612,613,617,621],{"class":499,"line":500},[497,614,616],{"class":615},"si04Y","var",[497,618,620],{"class":619},"svycV"," config",[497,622,623],{"class":511}," =\n",[497,625,626,628,630,633,636,640,643,645,648,651,654,656],{"class":499,"line":515},[497,627,519],{"class":619},[497,629,331],{"class":511},[497,631,632],{"class":518},"builder",[497,634,635],{"class":511},"(",[497,637,639],{"class":638},"sSP4y","\"",[497,641,642],{"class":534},"map-filter-example",[497,644,639],{"class":638},[497,646,647],{"class":511},",",[497,649,650],{"class":638}," \"",[497,652,653],{"class":534},"localhost:9092",[497,655,639],{"class":638},[497,657,658],{"class":511},")\n",[497,660,661,664,667,669,672,674,676],{"class":499,"line":525},[497,662,663],{"class":511},"        .",[497,665,666],{"class":518},"defaultKeySerde",[497,668,635],{"class":511},[497,670,671],{"class":619},"Serdes",[497,673,331],{"class":511},[497,675,565],{"class":518},[497,677,678],{"class":511},"())\n",[497,680,681,683,686,688,690,692,694],{"class":499,"line":541},[497,682,663],{"class":511},[497,684,685],{"class":518},"defaultValueSerde",[497,687,635],{"class":511},[497,689,671],{"class":619},[497,691,331],{"class":511},[497,693,565],{"class":518},[497,695,678],{"class":511},[497,697,698,700,702,704,707],{"class":499,"line":554},[497,699,663],{"class":511},[497,701,465],{"class":518},[497,703,635],{"class":511},[497,705,706],{"class":593},"6",[497,708,658],{"class":511},[497,710,711,713,716],{"class":499,"line":571},[497,712,663],{"class":511},[497,714,715],{"class":518},"build",[497,717,718],{"class":511},"();\n",[320,720,721,722,725,726,728],{},"On the ",[385,723,724],{},":runtime"," module the same setting lives in ",[385,727,477],{},":",[487,730,734],{"className":731,"code":732,"language":733,"meta":493,"style":493},"language-yaml shiki shiki-themes vitesse-light","stoatflow:\n  application-id: map-filter-example\n  bootstrap-servers: localhost:9092\n  lanes:\n    count: 6\n","yaml",[385,735,736,745,755,765,772],{"__ignoreMap":493},[497,737,738,742],{"class":499,"line":500},[497,739,741],{"class":740},"su6XF","stoatflow",[497,743,744],{"class":511},":\n",[497,746,747,750,752],{"class":499,"line":515},[497,748,749],{"class":740},"  application-id",[497,751,728],{"class":511},[497,753,754],{"class":534}," map-filter-example\n",[497,756,757,760,762],{"class":499,"line":525},[497,758,759],{"class":740},"  bootstrap-servers",[497,761,728],{"class":511},[497,763,764],{"class":534}," localhost:9092\n",[497,766,767,770],{"class":499,"line":541},[497,768,769],{"class":740},"  lanes",[497,771,744],{"class":511},[497,773,774,777,779],{"class":499,"line":554},[497,775,776],{"class":740},"    count",[497,778,728],{"class":511},[497,780,781],{"class":593}," 6\n",[783,784,786],"h3",{"id":785},"how-many-lanes","How many lanes?",[320,788,789],{},"Lane count is a throughput-vs-overhead trade-off, not a correctness knob — ordering and exactly-once hold at any value. Some guidance at the architecture level:",[336,791,792,802,812,818],{},[339,793,794,797,798,801],{},[324,795,796],{},"Start at the default"," (",[385,799,800],{},"max(2, CPU cores)",") and tune from measured throughput, not guesswork.",[339,803,804,807,808,811],{},[324,805,806],{},"More lanes than cores"," can help when lanes spend time ",[431,809,810],{},"blocked"," — on external calls, disk, or downstream systems — because parked lanes free their carrier thread for others. It rarely helps a purely CPU-bound topology, where you can't run more concurrent work than you have cores.",[339,813,814,817],{},[324,815,816],{},"More lanes cost more."," Each lane carries its own queue and dispatch bookkeeping; past a point you pay overhead without gaining throughput.",[339,819,820,823],{},[324,821,822],{},"Your keyspace caps the benefit."," Lanes beyond the number of distinct, well-distributed keys sit idle — they can't manufacture parallelism the data doesn't contain.",[320,825,826,827,831],{},"Pin numbers come from ",[329,828,830],{"href":829},"\u002Fproduct\u002Fbenchmarks","Benchmarks","; the precise scheduling and dispatch behaviour is an implementation concern and stays in the source.",[370,833,835],{"id":834},"virtual-threads-make-blocking-io-cheap","Virtual threads make blocking I\u002FO cheap",[320,837,838,839,842,843,845,846,849],{},"Lanes run on ",[324,840,841],{},"virtual threads"," — JDK 21's GA primitive. A lane that blocks on a REST call, a database query, or an AI-inference response parks at near-zero cost while the JVM keeps making progress on every other lane. That changes how you write enrichment: a synchronous, blocking call inside a ",[385,844,398],{}," is the idiomatic approach — no ",[385,847,848],{},"CompletableFuture"," chains, no reactive frameworks, no callback wiring just to keep throughput up while a call is in flight.",[320,851,852],{},"This processor enriches each flight by looking values up from a state store and forwarding the result — straight-line, blocking-style code. A remote lookup against an external service would read the same way; the lane simply parks until it returns:",[483,854,855,1146],{"group":485},[487,856,858],{"className":489,"code":857,"filename":491,"language":492,"meta":493,"style":493},"class AirportEnrichmentProcessor :\n    ContextualProcessor\u003CString, Flight, String, FlightEnriched>() {\n\n    private lateinit var airportInfoStore: ReadOnlyKeyValueStore\u003CString, AirportInfoI18n>\n\n    override fun init(context: ProcessorContext\u003CString, FlightEnriched>) {\n        super.init(context)\n        airportInfoStore = context.getStateStore(STATE_STORE_AIRPORT_INFO)\n    }\n\n    override fun process(record: Record\u003CString, Flight>) {\n        val flight = record.value ?: return\n        \u002F\u002F Blocking-style lookups; a remote enrichment call would read the same way.\n        val depInfo = flight.departureAirport?.let { airportInfoStore.get(it) }\n        val arrInfo = flight.arrivalAirport?.let { airportInfoStore.get(it) }\n        context().forward(record.withValue(toFlightEnriched(flight, depInfo, arrInfo)))\n    }\n}\n",[385,859,860,872,899,905,934,938,966,980,996,1002,1007,1033,1056,1063,1088,1109,1135,1140],{"__ignoreMap":493},[497,861,862,865,869],{"class":499,"line":500},[497,863,864],{"class":503},"class",[497,866,868],{"class":867},"sUxyF"," AirportEnrichmentProcessor",[497,870,871],{"class":507}," :\n",[497,873,874,877,880,882,884,887,889,891,893,896],{"class":499,"line":515},[497,875,876],{"class":518},"    ContextualProcessor",[497,878,879],{"class":507},"\u003C",[497,881,565],{"class":867},[497,883,388],{"class":507},[497,885,886],{"class":867},"Flight",[497,888,388],{"class":507},[497,890,565],{"class":867},[497,892,388],{"class":507},[497,894,895],{"class":867},"FlightEnriched",[497,897,898],{"class":507},">() {\n",[497,900,901],{"class":499,"line":525},[497,902,904],{"emptyLinePlaceholder":903},true,"\n",[497,906,907,910,913,916,919,922,924,926,928,931],{"class":499,"line":541},[497,908,909],{"class":615},"    private",[497,911,912],{"class":615}," lateinit",[497,914,915],{"class":503}," var",[497,917,918],{"class":507}," airportInfoStore: ",[497,920,921],{"class":867},"ReadOnlyKeyValueStore",[497,923,879],{"class":507},[497,925,565],{"class":867},[497,927,388],{"class":507},[497,929,930],{"class":867},"AirportInfoI18n",[497,932,933],{"class":507},">\n",[497,935,936],{"class":499,"line":554},[497,937,904],{"emptyLinePlaceholder":903},[497,939,940,943,946,949,952,955,957,959,961,963],{"class":499,"line":571},[497,941,942],{"class":615},"    override",[497,944,945],{"class":503}," fun",[497,947,948],{"class":518}," init",[497,950,951],{"class":507},"(context: ",[497,953,954],{"class":867},"ProcessorContext",[497,956,879],{"class":507},[497,958,565],{"class":867},[497,960,388],{"class":507},[497,962,895],{"class":867},[497,964,965],{"class":507},">) {\n",[497,967,968,972,974,977],{"class":499,"line":585},[497,969,971],{"class":970},"sEi1f","        super",[497,973,331],{"class":507},[497,975,976],{"class":518},"init",[497,978,979],{"class":507},"(context)\n",[497,981,982,985,987,990,993],{"class":499,"line":599},[497,983,984],{"class":507},"        airportInfoStore ",[497,986,531],{"class":511},[497,988,989],{"class":507}," context.",[497,991,992],{"class":518},"getStateStore",[497,994,995],{"class":507},"(STATE_STORE_AIRPORT_INFO)\n",[497,997,999],{"class":499,"line":998},9,[497,1000,1001],{"class":507},"    }\n",[497,1003,1005],{"class":499,"line":1004},10,[497,1006,904],{"emptyLinePlaceholder":903},[497,1008,1010,1012,1014,1017,1020,1023,1025,1027,1029,1031],{"class":499,"line":1009},11,[497,1011,942],{"class":615},[497,1013,945],{"class":503},[497,1015,1016],{"class":518}," process",[497,1018,1019],{"class":507},"(record: ",[497,1021,1022],{"class":867},"Record",[497,1024,879],{"class":507},[497,1026,565],{"class":867},[497,1028,388],{"class":507},[497,1030,886],{"class":867},[497,1032,965],{"class":507},[497,1034,1036,1039,1042,1044,1047,1050,1053],{"class":499,"line":1035},12,[497,1037,1038],{"class":503},"        val",[497,1040,1041],{"class":507}," flight ",[497,1043,531],{"class":511},[497,1045,1046],{"class":507}," record.",[497,1048,1049],{"class":615},"value",[497,1051,1052],{"class":507}," ?: ",[497,1054,1055],{"class":503},"return\n",[497,1057,1059],{"class":499,"line":1058},13,[497,1060,1062],{"class":1061},"s8zF2","        \u002F\u002F Blocking-style lookups; a remote enrichment call would read the same way.\n",[497,1064,1066,1068,1071,1073,1076,1079,1082,1085],{"class":499,"line":1065},14,[497,1067,1038],{"class":503},[497,1069,1070],{"class":507}," depInfo ",[497,1072,531],{"class":511},[497,1074,1075],{"class":507}," flight.departureAirport?.",[497,1077,1078],{"class":518},"let",[497,1080,1081],{"class":507}," { airportInfoStore.",[497,1083,1084],{"class":518},"get",[497,1086,1087],{"class":507},"(it) }\n",[497,1089,1091,1093,1096,1098,1101,1103,1105,1107],{"class":499,"line":1090},15,[497,1092,1038],{"class":503},[497,1094,1095],{"class":507}," arrInfo ",[497,1097,531],{"class":511},[497,1099,1100],{"class":507}," flight.arrivalAirport?.",[497,1102,1078],{"class":518},[497,1104,1081],{"class":507},[497,1106,1084],{"class":518},[497,1108,1087],{"class":507},[497,1110,1112,1115,1118,1121,1124,1127,1129,1132],{"class":499,"line":1111},16,[497,1113,1114],{"class":518},"        context",[497,1116,1117],{"class":507},"().",[497,1119,1120],{"class":518},"forward",[497,1122,1123],{"class":507},"(record.",[497,1125,1126],{"class":518},"withValue",[497,1128,635],{"class":507},[497,1130,1131],{"class":518},"toFlightEnriched",[497,1133,1134],{"class":507},"(flight, depInfo, arrInfo)))\n",[497,1136,1138],{"class":499,"line":1137},17,[497,1139,1001],{"class":507},[497,1141,1143],{"class":499,"line":1142},18,[497,1144,1145],{"class":507},"}\n",[487,1147,1149],{"className":605,"code":1148,"filename":607,"language":608,"meta":493,"style":493},"public class AirportEnrichmentProcessor\n        extends ContextualProcessor\u003CString, Flight, String, FlightEnriched> {\n\n    private ReadOnlyKeyValueStore\u003CString, AirportInfoI18n> airportInfoStore;\n\n    @Override\n    public void init(ProcessorContext\u003CString, FlightEnriched> context) {\n        super.init(context);\n        this.airportInfoStore = context.getStateStore(STATE_STORE_AIRPORT_INFO);\n    }\n\n    @Override\n    public void process(Record\u003CString, Flight> record) {\n        final Flight flight = record.value();\n        if (flight == null) {\n            return;\n        }\n        \u002F\u002F Blocking-style lookups; a remote enrichment call would read the same way.\n        final String dep = flight.getDepartureAirport();\n        final String arr = flight.getArrivalAirport();\n        final AirportInfoI18n depInfo = dep != null ? airportInfoStore.get(dep) : null;\n        final AirportInfoI18n arrInfo = arr != null ? airportInfoStore.get(arr) : null;\n        context().forward(record.withValue(toFlightEnriched(flight, depInfo, arrInfo)));\n    }\n}\n",[385,1150,1151,1162,1195,1199,1223,1227,1235,1267,1283,1309,1313,1317,1323,1352,1372,1392,1399,1404,1408,1429,1450,1493,1533,1571,1576],{"__ignoreMap":493},[497,1152,1153,1156,1159],{"class":499,"line":500},[497,1154,1155],{"class":615},"public",[497,1157,1158],{"class":615}," class",[497,1160,1161],{"class":867}," AirportEnrichmentProcessor\n",[497,1163,1164,1167,1170,1172,1174,1176,1179,1181,1184,1186,1189,1192],{"class":499,"line":515},[497,1165,1166],{"class":615},"        extends",[497,1168,1169],{"class":518}," ContextualProcessor",[497,1171,879],{"class":511},[497,1173,565],{"class":615},[497,1175,647],{"class":511},[497,1177,1178],{"class":615}," Flight",[497,1180,647],{"class":511},[497,1182,1183],{"class":615}," String",[497,1185,647],{"class":511},[497,1187,1188],{"class":615}," FlightEnriched",[497,1190,1191],{"class":511},">",[497,1193,1194],{"class":511}," {\n",[497,1196,1197],{"class":499,"line":525},[497,1198,904],{"emptyLinePlaceholder":903},[497,1200,1201,1203,1206,1208,1210,1212,1215,1217,1220],{"class":499,"line":541},[497,1202,909],{"class":615},[497,1204,1205],{"class":507}," ReadOnlyKeyValueStore",[497,1207,879],{"class":511},[497,1209,565],{"class":615},[497,1211,647],{"class":511},[497,1213,1214],{"class":615}," AirportInfoI18n",[497,1216,1191],{"class":511},[497,1218,1219],{"class":619}," airportInfoStore",[497,1221,1222],{"class":511},";\n",[497,1224,1225],{"class":499,"line":554},[497,1226,904],{"emptyLinePlaceholder":903},[497,1228,1229,1232],{"class":499,"line":571},[497,1230,1231],{"class":511},"    @",[497,1233,1234],{"class":615},"Override\n",[497,1236,1237,1240,1243,1245,1247,1249,1251,1253,1255,1257,1259,1262,1265],{"class":499,"line":585},[497,1238,1239],{"class":615},"    public",[497,1241,1242],{"class":615}," void",[497,1244,948],{"class":518},[497,1246,635],{"class":511},[497,1248,954],{"class":507},[497,1250,879],{"class":511},[497,1252,565],{"class":615},[497,1254,647],{"class":511},[497,1256,1188],{"class":615},[497,1258,1191],{"class":511},[497,1260,1261],{"class":619}," context",[497,1263,1264],{"class":511},")",[497,1266,1194],{"class":511},[497,1268,1269,1271,1273,1275,1277,1280],{"class":499,"line":599},[497,1270,971],{"class":970},[497,1272,331],{"class":511},[497,1274,976],{"class":518},[497,1276,635],{"class":511},[497,1278,1279],{"class":507},"context",[497,1281,1282],{"class":511},");\n",[497,1284,1285,1288,1290,1293,1296,1298,1300,1302,1304,1307],{"class":499,"line":998},[497,1286,1287],{"class":970},"        this",[497,1289,331],{"class":511},[497,1291,1292],{"class":619},"airportInfoStore",[497,1294,1295],{"class":511}," =",[497,1297,1261],{"class":619},[497,1299,331],{"class":511},[497,1301,992],{"class":518},[497,1303,635],{"class":511},[497,1305,1306],{"class":507},"STATE_STORE_AIRPORT_INFO",[497,1308,1282],{"class":511},[497,1310,1311],{"class":499,"line":1004},[497,1312,1001],{"class":511},[497,1314,1315],{"class":499,"line":1009},[497,1316,904],{"emptyLinePlaceholder":903},[497,1318,1319,1321],{"class":499,"line":1035},[497,1320,1231],{"class":511},[497,1322,1234],{"class":615},[497,1324,1325,1327,1329,1331,1333,1335,1337,1339,1341,1343,1345,1348,1350],{"class":499,"line":1058},[497,1326,1239],{"class":615},[497,1328,1242],{"class":615},[497,1330,1016],{"class":518},[497,1332,635],{"class":511},[497,1334,1022],{"class":507},[497,1336,879],{"class":511},[497,1338,565],{"class":615},[497,1340,647],{"class":511},[497,1342,1178],{"class":615},[497,1344,1191],{"class":511},[497,1346,1347],{"class":619}," record",[497,1349,1264],{"class":511},[497,1351,1194],{"class":511},[497,1353,1354,1357,1359,1362,1364,1366,1368,1370],{"class":499,"line":1065},[497,1355,1356],{"class":615},"        final",[497,1358,1178],{"class":507},[497,1360,1361],{"class":619}," flight",[497,1363,1295],{"class":511},[497,1365,1347],{"class":619},[497,1367,331],{"class":511},[497,1369,1049],{"class":518},[497,1371,718],{"class":511},[497,1373,1374,1377,1379,1382,1385,1388,1390],{"class":499,"line":1090},[497,1375,1376],{"class":503},"        if",[497,1378,797],{"class":511},[497,1380,1381],{"class":507},"flight ",[497,1383,1384],{"class":615},"==",[497,1386,1387],{"class":503}," null",[497,1389,1264],{"class":511},[497,1391,1194],{"class":511},[497,1393,1394,1397],{"class":499,"line":1111},[497,1395,1396],{"class":503},"            return",[497,1398,1222],{"class":511},[497,1400,1401],{"class":499,"line":1137},[497,1402,1403],{"class":511},"        }\n",[497,1405,1406],{"class":499,"line":1142},[497,1407,1062],{"class":1061},[497,1409,1411,1413,1415,1418,1420,1422,1424,1427],{"class":499,"line":1410},19,[497,1412,1356],{"class":615},[497,1414,1183],{"class":507},[497,1416,1417],{"class":619}," dep",[497,1419,1295],{"class":511},[497,1421,1361],{"class":619},[497,1423,331],{"class":511},[497,1425,1426],{"class":518},"getDepartureAirport",[497,1428,718],{"class":511},[497,1430,1432,1434,1436,1439,1441,1443,1445,1448],{"class":499,"line":1431},20,[497,1433,1356],{"class":615},[497,1435,1183],{"class":507},[497,1437,1438],{"class":619}," arr",[497,1440,1295],{"class":511},[497,1442,1361],{"class":619},[497,1444,331],{"class":511},[497,1446,1447],{"class":518},"getArrivalAirport",[497,1449,718],{"class":511},[497,1451,1453,1455,1457,1460,1462,1465,1468,1470,1473,1475,1477,1479,1481,1484,1486,1489,1491],{"class":499,"line":1452},21,[497,1454,1356],{"class":615},[497,1456,1214],{"class":507},[497,1458,1459],{"class":619}," depInfo",[497,1461,1295],{"class":511},[497,1463,1464],{"class":507}," dep ",[497,1466,1467],{"class":615},"!=",[497,1469,1387],{"class":503},[497,1471,1472],{"class":503}," ?",[497,1474,1219],{"class":619},[497,1476,331],{"class":511},[497,1478,1084],{"class":518},[497,1480,635],{"class":511},[497,1482,1483],{"class":507},"dep",[497,1485,1264],{"class":511},[497,1487,1488],{"class":503}," :",[497,1490,1387],{"class":503},[497,1492,1222],{"class":511},[497,1494,1496,1498,1500,1503,1505,1508,1510,1512,1514,1516,1518,1520,1522,1525,1527,1529,1531],{"class":499,"line":1495},22,[497,1497,1356],{"class":615},[497,1499,1214],{"class":507},[497,1501,1502],{"class":619}," arrInfo",[497,1504,1295],{"class":511},[497,1506,1507],{"class":507}," arr ",[497,1509,1467],{"class":615},[497,1511,1387],{"class":503},[497,1513,1472],{"class":503},[497,1515,1219],{"class":619},[497,1517,331],{"class":511},[497,1519,1084],{"class":518},[497,1521,635],{"class":511},[497,1523,1524],{"class":507},"arr",[497,1526,1264],{"class":511},[497,1528,1488],{"class":503},[497,1530,1387],{"class":503},[497,1532,1222],{"class":511},[497,1534,1536,1538,1540,1542,1544,1547,1549,1551,1553,1555,1557,1560,1562,1564,1566,1568],{"class":499,"line":1535},23,[497,1537,1114],{"class":518},[497,1539,1117],{"class":511},[497,1541,1120],{"class":518},[497,1543,635],{"class":511},[497,1545,1546],{"class":619},"record",[497,1548,331],{"class":511},[497,1550,1126],{"class":518},[497,1552,635],{"class":511},[497,1554,1131],{"class":518},[497,1556,635],{"class":511},[497,1558,1559],{"class":507},"flight",[497,1561,647],{"class":511},[497,1563,1459],{"class":507},[497,1565,647],{"class":511},[497,1567,1502],{"class":507},[497,1569,1570],{"class":511},")));\n",[497,1572,1574],{"class":499,"line":1573},24,[497,1575,1001],{"class":511},[497,1577,1579],{"class":499,"line":1578},25,[497,1580,1145],{"class":511},[1582,1583,1585],"callout",{"color":1584,"icon":80},"warning",[320,1586,1587,1588,331],{},"Virtual threads scale blocking I\u002FO; they don't make CPU-bound work faster. A lane crunching numbers occupies a real carrier thread for the whole computation — concurrency there is still bounded by cores. Lanes win when work ",[431,1589,1590],{},"waits",[320,1592,1593,1594,1596,1597,1600],{},"See ",[329,1595,407],{"href":63}," for the rules on what a lane may touch, and ",[329,1598,1599],{"href":136},"Processor API"," for the full processor surface.",[370,1602,1604],{"id":1603},"in-memory-re-keying-instead-of-repartition-topics","In-memory re-keying instead of repartition topics",[320,1606,1607,1608,388,1611,1613,1614,1617],{},"When the topology changes a record's key — ",[385,1609,1610],{},"selectKey",[385,1612,444],{},", or a key-changing join — that record may now belong to a different lane. StoatFlow re-hashes the new key and routes it to the lane that owns it ",[324,1615,1616],{},"in-memory",", between lanes. There is no internal repartition topic, no extra serialization round-trip, and no broker hop.",[320,1619,1620],{},"In Kafka Streams the same key change forces a write to a repartition topic and a re-read on the other side. StoatFlow's single-instance model removes that round-trip entirely — re-keying is an in-process handoff. The output is identical; the path is shorter.",[320,1622,1623,1624,1626],{},"The e-commerce daily-summary topology re-keys twice — once on ",[385,1625,444],{}," to aggregate per customer, then again coming out of the windowed table — and both are in-memory handoffs:",[483,1628,1629,1779],{"group":485},[487,1630,1632],{"className":489,"code":1631,"filename":491,"language":492,"meta":493,"style":493},"\u002F\u002F Re-key the merged stream by customerId for aggregation.\nval windowedStream =\n    mergedStreams\n        .groupBy(\n            { _, v -> v.purchase?.customerId ?: v.webActivity.customerId },\n            Grouped.with(\"grouped-by-customer-id\", stringSerde, webActivityOrPurchaseSerde),\n        )\n        .windowedBy(dailyWindows)\n\n\u002F\u002F ...aggregate, then re-key back to customerId on the way out.\nval dailyAggregatesStream =\n    dailyAggregation\n        .mapValues({ wk, v -> \u002F* set date from window start *\u002F }, Named.`as`(\"daily-aggregates-mapvalues\"))\n        .toStream({ wk, _ -> wk.key }, Named.`as`(\"daily-aggregates-to-stream\"))\n",[385,1633,1634,1639,1648,1653,1661,1672,1688,1693,1703,1707,1712,1721,1726,1755],{"__ignoreMap":493},[497,1635,1636],{"class":499,"line":500},[497,1637,1638],{"class":1061},"\u002F\u002F Re-key the merged stream by customerId for aggregation.\n",[497,1640,1641,1643,1646],{"class":499,"line":515},[497,1642,504],{"class":503},[497,1644,1645],{"class":507}," windowedStream ",[497,1647,512],{"class":511},[497,1649,1650],{"class":499,"line":525},[497,1651,1652],{"class":507},"    mergedStreams\n",[497,1654,1655,1657,1659],{"class":499,"line":541},[497,1656,663],{"class":507},[497,1658,444],{"class":518},[497,1660,522],{"class":507},[497,1662,1663,1666,1669],{"class":499,"line":554},[497,1664,1665],{"class":507},"            { _, v ",[497,1667,1668],{"class":511},"->",[497,1670,1671],{"class":507}," v.purchase?.customerId ?: v.webActivity.customerId },\n",[497,1673,1674,1677,1680,1682,1685],{"class":499,"line":571},[497,1675,1676],{"class":507},"            Grouped.",[497,1678,1679],{"class":518},"with",[497,1681,635],{"class":507},[497,1683,1684],{"class":534},"\"grouped-by-customer-id\"",[497,1686,1687],{"class":507},", stringSerde, webActivityOrPurchaseSerde),\n",[497,1689,1690],{"class":499,"line":585},[497,1691,1692],{"class":507},"        )\n",[497,1694,1695,1697,1700],{"class":499,"line":599},[497,1696,663],{"class":507},[497,1698,1699],{"class":518},"windowedBy",[497,1701,1702],{"class":507},"(dailyWindows)\n",[497,1704,1705],{"class":499,"line":998},[497,1706,904],{"emptyLinePlaceholder":903},[497,1708,1709],{"class":499,"line":1004},[497,1710,1711],{"class":1061},"\u002F\u002F ...aggregate, then re-key back to customerId on the way out.\n",[497,1713,1714,1716,1719],{"class":499,"line":1009},[497,1715,504],{"class":503},[497,1717,1718],{"class":507}," dailyAggregatesStream ",[497,1720,512],{"class":511},[497,1722,1723],{"class":499,"line":1035},[497,1724,1725],{"class":507},"    dailyAggregation\n",[497,1727,1728,1730,1733,1736,1738,1741,1744,1747,1749,1752],{"class":499,"line":1058},[497,1729,663],{"class":507},[497,1731,1732],{"class":518},"mapValues",[497,1734,1735],{"class":507},"({ wk, v ",[497,1737,1668],{"class":511},[497,1739,1740],{"class":1061}," \u002F* set date from window start *\u002F",[497,1742,1743],{"class":507}," }, Named.",[497,1745,1746],{"class":518},"`as`",[497,1748,635],{"class":507},[497,1750,1751],{"class":534},"\"daily-aggregates-mapvalues\"",[497,1753,1754],{"class":507},"))\n",[497,1756,1757,1759,1762,1765,1767,1770,1772,1774,1777],{"class":499,"line":1065},[497,1758,663],{"class":507},[497,1760,1761],{"class":518},"toStream",[497,1763,1764],{"class":507},"({ wk, _ ",[497,1766,1668],{"class":511},[497,1768,1769],{"class":507}," wk.key }, Named.",[497,1771,1746],{"class":518},[497,1773,635],{"class":507},[497,1775,1776],{"class":534},"\"daily-aggregates-to-stream\"",[497,1778,1754],{"class":507},[487,1780,1782],{"className":605,"code":1781,"filename":607,"language":608,"meta":493,"style":493},"\u002F\u002F Re-key the merged stream by customerId for aggregation.\nTimeWindowedKStream\u003CString, WebActivityOrPurchase> windowedStream = mergedStreams\n    .groupBy(\n        (k, v) -> v.purchase() != null ? v.purchase().customerId() : v.webActivity().customerId(),\n        Grouped.with(\"grouped-by-customer-id\", stringSerde, webActivityOrPurchaseSerde))\n    .windowedBy(dailyWindows);\n\n\u002F\u002F ...aggregate, then re-key back to customerId on the way out.\nKStream\u003CString, DailyAggregates> dailyAggregatesStream = dailyAggregation\n    .mapValues((wk, v) -> \u002F* set date from window start *\u002F, Named.as(\"daily-aggregates-mapvalues\"))\n    .toStream((wk, v) -> wk.getKey(), Named.as(\"daily-aggregates-to-stream\"));\n",[385,1783,1784,1788,1812,1821,1884,1914,1927,1931,1935,1959,2002],{"__ignoreMap":493},[497,1785,1786],{"class":499,"line":500},[497,1787,1638],{"class":1061},[497,1789,1790,1793,1795,1797,1799,1802,1804,1807,1809],{"class":499,"line":515},[497,1791,1792],{"class":507},"TimeWindowedKStream",[497,1794,879],{"class":511},[497,1796,565],{"class":615},[497,1798,647],{"class":511},[497,1800,1801],{"class":615}," WebActivityOrPurchase",[497,1803,1191],{"class":511},[497,1805,1806],{"class":619}," windowedStream",[497,1808,1295],{"class":511},[497,1810,1811],{"class":507}," mergedStreams\n",[497,1813,1814,1817,1819],{"class":499,"line":525},[497,1815,1816],{"class":511},"    .",[497,1818,444],{"class":518},[497,1820,522],{"class":511},[497,1822,1823,1826,1829,1831,1834,1836,1839,1841,1843,1846,1849,1852,1854,1856,1858,1860,1862,1864,1867,1869,1871,1873,1875,1878,1880,1882],{"class":499,"line":541},[497,1824,1825],{"class":511},"        (",[497,1827,1828],{"class":507},"k",[497,1830,647],{"class":511},[497,1832,1833],{"class":507}," v",[497,1835,1264],{"class":511},[497,1837,1838],{"class":511}," ->",[497,1840,1833],{"class":619},[497,1842,331],{"class":511},[497,1844,1845],{"class":518},"purchase",[497,1847,1848],{"class":511},"()",[497,1850,1851],{"class":615}," !=",[497,1853,1387],{"class":503},[497,1855,1472],{"class":503},[497,1857,1833],{"class":619},[497,1859,331],{"class":511},[497,1861,1845],{"class":518},[497,1863,1117],{"class":511},[497,1865,1866],{"class":518},"customerId",[497,1868,1848],{"class":511},[497,1870,1488],{"class":503},[497,1872,1833],{"class":619},[497,1874,331],{"class":511},[497,1876,1877],{"class":518},"webActivity",[497,1879,1117],{"class":511},[497,1881,1866],{"class":518},[497,1883,568],{"class":511},[497,1885,1886,1889,1891,1893,1895,1897,1900,1902,1904,1907,1909,1912],{"class":499,"line":554},[497,1887,1888],{"class":619},"        Grouped",[497,1890,331],{"class":511},[497,1892,1679],{"class":518},[497,1894,635],{"class":511},[497,1896,639],{"class":638},[497,1898,1899],{"class":534},"grouped-by-customer-id",[497,1901,639],{"class":638},[497,1903,647],{"class":511},[497,1905,1906],{"class":507}," stringSerde",[497,1908,647],{"class":511},[497,1910,1911],{"class":507}," webActivityOrPurchaseSerde",[497,1913,1754],{"class":511},[497,1915,1916,1918,1920,1922,1925],{"class":499,"line":571},[497,1917,1816],{"class":511},[497,1919,1699],{"class":518},[497,1921,635],{"class":511},[497,1923,1924],{"class":507},"dailyWindows",[497,1926,1282],{"class":511},[497,1928,1929],{"class":499,"line":585},[497,1930,904],{"emptyLinePlaceholder":903},[497,1932,1933],{"class":499,"line":599},[497,1934,1711],{"class":1061},[497,1936,1937,1940,1942,1944,1946,1949,1951,1954,1956],{"class":499,"line":998},[497,1938,1939],{"class":507},"KStream",[497,1941,879],{"class":511},[497,1943,565],{"class":615},[497,1945,647],{"class":511},[497,1947,1948],{"class":615}," DailyAggregates",[497,1950,1191],{"class":511},[497,1952,1953],{"class":619}," dailyAggregatesStream",[497,1955,1295],{"class":511},[497,1957,1958],{"class":507}," dailyAggregation\n",[497,1960,1961,1963,1965,1968,1971,1973,1975,1977,1979,1981,1983,1986,1988,1991,1993,1995,1998,2000],{"class":499,"line":1004},[497,1962,1816],{"class":511},[497,1964,1732],{"class":518},[497,1966,1967],{"class":511},"((",[497,1969,1970],{"class":507},"wk",[497,1972,647],{"class":511},[497,1974,1833],{"class":507},[497,1976,1264],{"class":511},[497,1978,1838],{"class":511},[497,1980,1740],{"class":1061},[497,1982,647],{"class":511},[497,1984,1985],{"class":619}," Named",[497,1987,331],{"class":511},[497,1989,1990],{"class":518},"as",[497,1992,635],{"class":511},[497,1994,639],{"class":638},[497,1996,1997],{"class":534},"daily-aggregates-mapvalues",[497,1999,639],{"class":638},[497,2001,1754],{"class":511},[497,2003,2004,2006,2008,2010,2012,2014,2016,2018,2020,2023,2025,2028,2031,2033,2035,2037,2039,2041,2044,2046],{"class":499,"line":1009},[497,2005,1816],{"class":511},[497,2007,1761],{"class":518},[497,2009,1967],{"class":511},[497,2011,1970],{"class":507},[497,2013,647],{"class":511},[497,2015,1833],{"class":507},[497,2017,1264],{"class":511},[497,2019,1838],{"class":511},[497,2021,2022],{"class":619}," wk",[497,2024,331],{"class":511},[497,2026,2027],{"class":518},"getKey",[497,2029,2030],{"class":511},"(),",[497,2032,1985],{"class":619},[497,2034,331],{"class":511},[497,2036,1990],{"class":518},[497,2038,635],{"class":511},[497,2040,639],{"class":638},[497,2042,2043],{"class":534},"daily-aggregates-to-stream",[497,2045,639],{"class":638},[497,2047,2048],{"class":511},"));\n",[320,2050,2051,2052,2054,2055,2058],{},"After each re-key, the new key's lane assignment again guarantees per-(new-)key ordering — the affinity property travels with the record across the handoff. This is also why your grouping key is your parallelism unit: the lane spread of a ",[385,2053,444],{}," aggregation follows the distribution of the ",[431,2056,2057],{},"grouped"," key, not the source key.",[370,2060,2062],{"id":2061},"where-to-go-next","Where to go next",[336,2064,2065,2070,2075,2081,2086,2091],{},[339,2066,2067,2069],{},[329,2068,47],{"href":48}," — the single-instance engine, lane dispatch, and commit barriers in one place",[339,2071,2072,2074],{},[329,2073,407],{"href":63}," — what a lane may touch, and the cross-key exception",[339,2076,2077,2080],{},[329,2078,2079],{"href":53},"Exactly-once"," — how per-lane processing commits atomically",[339,2082,2083,2085],{},[329,2084,1599],{"href":136}," — writing custom processors that run on lanes",[339,2087,2088,2090],{},[329,2089,830],{"href":829}," — measured throughput across lane counts and workloads",[339,2092,2093,2096],{},[329,2094,2095],{"href":83},"How StoatFlow differs from KS"," — the partition-coupling and repartition-topic contrasts in full",[2098,2099,2100],"style",{},"html pre.shiki code .sbBg2, html code.shiki .sbBg2{--shiki-default:#1E754F}html pre.shiki code .suHK_, html code.shiki .suHK_{--shiki-default:#393A34}html pre.shiki code .sYZai, html code.shiki .sYZai{--shiki-default:#999999}html pre.shiki code .sySUi, html code.shiki .sySUi{--shiki-default:#59873A}html pre.shiki code .spphp, html code.shiki .spphp{--shiki-default:#B56959}html pre.shiki code .s-TwI, html code.shiki .s-TwI{--shiki-default:#2F798A}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 .si04Y, html code.shiki .si04Y{--shiki-default:#AB5959}html pre.shiki code .svycV, html code.shiki .svycV{--shiki-default:#B07D48}html pre.shiki code .sSP4y, html code.shiki .sSP4y{--shiki-default:#B5695977}html pre.shiki code .su6XF, html code.shiki .su6XF{--shiki-default:#998418}html pre.shiki code .sUxyF, html code.shiki .sUxyF{--shiki-default:#2E8F82}html pre.shiki code .sEi1f, html code.shiki .sEi1f{--shiki-default:#A65E2B}html pre.shiki code .s8zF2, html code.shiki .s8zF2{--shiki-default:#A0ADA0}",{"title":493,"searchDepth":515,"depth":515,"links":2102},[2103,2104,2105,2108,2109,2110],{"id":372,"depth":515,"text":373},{"id":414,"depth":515,"text":415},{"id":448,"depth":515,"text":449,"children":2106},[2107],{"id":785,"depth":525,"text":786},{"id":834,"depth":515,"text":835},{"id":1603,"depth":515,"text":1604},{"id":2061,"depth":515,"text":2062},"How key-affinity lanes give StoatFlow per-key ordering and cross-key parallelism — decoupled from partition count, cheap to scale with cores, and friendly to in-line blocking I\u002FO.","md",{},{"icon":60},"explanation",{"title":57,"description":2111},"Cf1TxC8gzOIPUF5cVVujZRyX6nvKFYscPhTyARQR6-Y",[2119,2121],{"title":52,"path":53,"stem":54,"description":2120,"icon":55,"children":-1},"How StoatFlow's commit barrier delivers end-to-end exactly-once — one Kafka transaction commits state, output, and offsets atomically — plus crash recovery and the at-least-once trade-off.",{"title":62,"path":63,"stem":64,"description":2122,"icon":65,"children":-1},"How StoatFlow's global state model works — why concurrent access across lanes is safe, when you need the key-lock utility, and which store types are available.",1783713351401]