La lezione 42 ha coperto Kafka, il log dove i record stanno fermi. I record fermi in un log non fanno nulla da soli. Qualcosa deve leggerli, trasformarli, aggregarli, fare join, e scrivere i risultati da qualche parte. Quel qualcosa è uno stream processor, e nel 2026 ci sono tre opzioni che contano per la maggior parte dei team: Apache Flink, Kafka Streams, e Spark Structured Streaming. La scelta tra loro è la seconda decisione architetturale più grande in un sistema di streaming, dopo la scelta del log. Questa lezione percorre il confronto.
La versione corta, prima del dettaglio. Flink è il pesomassimo: un engine standalone di stream processing con le API più ricche per event-time e state, la latenza più bassa, e la curva operativa più ripida. Kafka Streams è la libreria: un JAR Java che incorpori nella tua applicazione, deployment più semplice, strettamente legato a Kafka. Spark Structured Streaming è il compromesso: streaming a micro-batch sopra Spark, la risposta giusta quando già fai girare Spark per il batch e vuoi codice unificato.
I tre non sono intercambiabili. Sceglierne uno sbagliato è recuperabile ma doloroso, e il costo della migrazione è abbastanza alto da far valere la pena di prendere la scelta sul serio.
Apache Flink: il pesomassimo stream-native
Flink è nato alla Berlin Technical University come progetto Stratosphere intorno al 2010, è diventato un progetto Apache nel 2014, ed è l’implementazione di riferimento dello stream processing dal 2017 circa. Stream processing qui è inteso in senso letterale: Flink processa i record uno alla volta man mano che arrivano, senza alcun batching a livello di engine.
Il modello dello state è ciò che fa risaltare Flink. Ogni operatore mantiene il proprio keyed state, partizionato dalla stessa chiave dello stream di input. Lo state è supportato da RocksDB su disco locale per state grandi e da managed memory per state piccoli, con checkpoint periodici verso uno storage durevole (S3, HDFS, GCS) per il recovery. I savepoint, checkpoint triggerati dall’utente con versioning, ti permettono di fermare un job, aggiornare il codice, e riprendere dallo stesso punto con lo stesso state. Questo rende possibile l’upgrade-senza-perdita-di-dati per job long-running, ed è una rarità tra gli streaming engine.
La semantica event-time è di prima classe. I watermark di Flink (la lezione 44 li copre in profondità) sono parte dell’API core. Le operazioni di windowing (tumbling, sliding, session) sono primitive ben comprese. Gli eventi che arrivano in ritardo hanno una gestione esplicita: side-output stream, allowed lateness, custom trigger. La potenza espressiva è più vicina a un linguaggio che a un framework.
Il costo è la complessità operativa. Un deployment Flink ha un JobManager (coordinatore), TaskManager (worker), e un state backend, tutti che devono essere in esecuzione e configurati correttamente. Il JobManager è un single point of failure a meno che non lo si faccia girare in modalità HA (ZooKeeper o leader election Kubernetes). Il tuning di RocksDB conta su scala. I checkpoint verso S3 vanno dimensionati. Aggiornare le versioni di Flink richiede attenzione perché la compatibilità dei savepoint non è garantita tra major version.
I team che scelgono Flink di solito lo fanno perché hanno bisogno delle sue capacità: event-time processing complesso, exactly-once con sink esterni, state grandi (gigabyte per operatore), o latenza nei millisecondi. I team che scelgono Flink e non hanno bisogno delle sue capacità tendono a passare un sacco di tempo operativo a imparare a tenerlo in salute. Il caso “potresti non aver bisogno di Flink” è reale.
Kafka Streams: la libreria che gira nella tua app
Kafka Streams è strutturalmente diverso dagli altri due: non è un cluster da deployare. È una libreria Java che importi, accanto al resto del codice della tua applicazione, che fa girare pipeline di streaming dentro un normale processo Java. Il JAR è parte della tua applicazione; il deployment è il deployment della tua applicazione; se già fai girare microservizi Java su Kubernetes, sai già come far girare Kafka Streams.
Il modello è strettamente legato a Kafka. Ogni applicazione Kafka Streams legge da topic Kafka, scrive su topic Kafka, e memorizza state in topic Kafka (i topic compattati fungono da storage di backing durevole, con una cache RocksDB locale per le performance di lettura). Non c’è uno state backend separato, nessuna submission del job separata, nessun cluster. L’applicazione è l’engine di streaming, e Kafka è tutto quello che gli sta intorno.
L’API ha due livelli. La Processor API di livello più basso ti dà controllo sul processing dei singoli record, sugli state store, e sulla punctuation (callback guidate da timer). La Streams DSL di livello più alto ti dà un set fluido di operazioni: map, filter, groupByKey, aggregate, join, windowedBy. La DSL copre la maggior parte dei carichi di lavoro in modo pulito, e la Processor API è la via di fuga quando la DSL non basta.
Lo scaling segue il modello di partition di Kafka. Un’applicazione Kafka Streams che legge un topic con dodici partition può girare fino a dodici istanze in parallelo; la libreria coordina l’assegnazione delle partition tramite il meccanismo dei consumer-group che Kafka ha già. Lo state è colocato con la partition: ogni istanza possiede un sottoinsieme delle partition e i corrispondenti state store. I rebalance spostano lo state tra istanze, il che costa banda di rete ma è automatico.
I trade-off sono il prezzo della semplicità. La gestione dello state è buona ma non flessibile come quella di Flink. La semantica event-time è presente ma meno completa. Non c’è un frontend SQL nella libreria core (ksqlDB esiste, ma come prodotto Confluent separato sopra). La latenza è bassa (paragonabile a Flink) ma il limite superiore sulla dimensione dello state è RAM-più-RocksDB su una singola istanza, più piccolo del modello di state distribuito di Flink.
I team che scelgono Kafka Streams di solito hanno una codebase Java o Scala, fanno già girare microservizi su Kafka, e vogliono aggiungere logica di streaming a servizi esistenti senza tirare su una nuova piattaforma. La fit è eccellente per quel caso. Per i team senza uno stack JVM, la scelta è scomoda (i port Python e Go sono non ufficiali e incompleti), e questo li spinge verso Flink o Spark.
Spark Structured Streaming: micro-batch su Spark
Spark Structured Streaming sta nel mezzo. È un’API di streaming sopra l’engine batch di Spark, che processa i record in piccoli batch (tipicamente 100-500ms) invece che uno alla volta. La stessa API DataFrame funziona per batch e streaming: se già usi Spark per ETL batch, il tuo codice di streaming sembra quasi identico, gira sullo stesso cluster, usa le stesse librerie.
L’architettura è quella standard di Spark: un driver che coordina executor, con il runtime di streaming che aggiunge un trigger che scatta a ogni intervallo di batch. Ogni trigger legge nuovi record dalla sorgente (Kafka, Kinesis, file), esegue la query DataFrame, e scrive i risultati. Lo state è tenuto in memoria, opzionalmente supportato da HDFS o S3 tramite il checkpointing di Spark. I watermark e il windowing sono supportati. Il set di operazioni supportate è più piccolo di quello di Flink, più grande di quello di cui la maggior parte dei team ha bisogno.
La latenza è la differenza più visibile da Flink e Kafka Streams. Il micro-batch ha un pavimento strutturale: anche con batch da 100ms, la latenza end-to-end è tipicamente di diverse centinaia di millisecondi fino a un secondo, non i bassi millisecondi a singola cifra che Flink può raggiungere. Per dashboard, alerting, ed ETL questo va bene. Per requisiti sotto i 100ms no. La modalità Continuous Processing (Spark 2.3+) tenta vero streaming sull’engine Spark; nel 2026 è ancora meno matura del native streaming di Flink e la maggior parte dei team resta sul micro-batch.
La complessità operativa è in mezzo. Un cluster Spark è una cosa nota nel 2026: la maggior parte dei data team ne ha uno o ha accesso tramite Databricks, EMR, Dataproc, o Synapse. Aggiungere job di streaming a un deployment esistente è per lo più scrivere il codice e puntarlo al cluster. Rispetto a Flink, più liscio perché Spark ha avuto più occhi addosso da più tempo; rispetto a Kafka Streams, più pesante perché c’è un cluster separato da tenere in vita.
I team che scelgono Structured Streaming di solito hanno Spark per il batch e vogliono consolidare su un singolo engine. L’API unificata è valore reale: il codice può essere condiviso tra pipeline batch e streaming. Il Modulo 9 del corso PySpark copre Structured Streaming end to end; la decisione architetturale in questa lezione è quando puntare su questo invece che su Flink o Kafka Streams.
Il confronto
Mettendo i tre uno accanto all’altro sulle dimensioni che contano per la scelta.
Latenza. Flink e Kafka Streams sono entrambi nel range dei bassi millisecondi per i carichi di lavoro tipici. Spark Structured Streaming è da diverse centinaia di millisecondi a un secondo in modalità micro-batch. Per la maggior parte dei carichi analitici e operativi, tutti e tre sono abbastanza veloci. Per requisiti sotto i 100ms, Flink o Kafka Streams.
Gestione dello state. Flink vince: state supportato da RocksDB, savepoint, supporto per state grandi, exactly-once tra operatori. Kafka Streams secondo: store RocksDB per task, backing durevole in topic compattati, scala più piccola. Spark terzo: state in-memory con tolleranza ai guasti basata su checkpoint, modello più semplice, scala più piccola.
Complessità operativa. Kafka Streams vince (è solo una libreria). Spark secondo (un cluster esistente noto). Flink terzo (un cluster separato con la sua curva di apprendimento). L’ordine è esattamente opposto al ranking della gestione dello state: gli engine che ti danno di più lo pagano con il footprint operativo.
Fit con l’ecosistema. Spark per le shop SQL-e-batch, soprattutto chiunque sia su Databricks o faccia girare PySpark. Kafka Streams per le shop di microservizi Java/Scala su Kafka. Flink per il caso “ci serve real real-time”, il caso regulated-finance “exactly-once tra sistemi”, il caso gaming/IoT/adtech dove sia state che latenza contano.
Supporto linguaggi. Spark ha pieno supporto Python e Scala, parziale per Java. Flink ha pieno supporto Java/Scala, decente per Python (PyFlink), niente Go o altri linguaggi. Kafka Streams è solo Java/Scala; i port non ufficiali per Python e Go non sono production-grade.
Frontend SQL. Flink ha Flink SQL, che è competente e in miglioramento. Spark ha Spark SQL, che è maturo ed eccellente. Kafka Streams ha ksqlDB (tecnicamente un prodotto separato, ma la storia SQL de facto).
flowchart LR
subgraph Sources[Sources]
K1[(Kafka)]
K2[(Kinesis)]
F1[(Files / S3)]
end
subgraph Engines[Engines]
FL[Flink<br/>standalone cluster]
KS[Kafka Streams<br/>library in app]
SS[Spark Structured Streaming<br/>on Spark cluster]
end
subgraph Sinks[Sinks]
OK[(Kafka topics)]
DB[(Databases)]
WH[(Warehouses)]
DL[(Data lake)]
end
K1 --> FL
K1 --> KS
K1 --> SS
K2 --> FL
K2 --> SS
F1 --> SS
FL --> OK
FL --> DB
FL --> WH
KS --> OK
KS --> DB
SS --> OK
SS --> WH
SS --> DL
Diagramma da creare: un side-by-side rifinito dei tre engine, con le tipiche sorgenti di input a sinistra e i sink di output a destra. Il punto visivo è che il lato sorgente si sovrappone quasi completamente (uno qualsiasi dei tre può leggere da Kafka) ma il lato sink e la forma di deployment differiscono. Flink è un cluster standalone con ampio supporto di sink. Kafka Streams è una libreria dentro un’app, che scrive principalmente di nuovo a Kafka. Spark Structured Streaming è su un cluster Spark con forte supporto per warehouse e data lake.
Quando ognuno è la risposta giusta
Tre domande di solito chiariscono la scelta.
Cosa fa girare già il tuo team? Se hai Spark, Structured Streaming è la via di minor resistenza. Se hai Kafka e uno stack Java/Scala, Kafka Streams. Se nessuno dei due, la domanda si sposta sul carico di lavoro.
Quali sono i tuoi requisiti di latenza? Sotto i 100ms end-to-end, Flink o Kafka Streams. Un secondo o due, uno qualsiasi dei tre.
Quanto state devi gestire? Gigabyte per chiave, Flink. Qualche centinaio di megabyte per partition senza transazioni cross-operator, Kafka Streams o Spark vanno bene.
La realtà del 2026 è che la maggior parte dei team ne sceglie uno e ci va avanti per anni. Flink ha il caso più forte per sistemi greenfield streaming-heavy. Kafka Streams per sistemi di microservizi Java esistenti dove lo streaming è una capacità tra molte. Spark Structured Streaming per le shop di analytics-e-data-engineering dove lo stesso team scrive pipeline batch e di streaming.
Le modalità di fallimento da scelta sbagliata differiscono. Scegliere Flink quando Kafka Streams sarebbe bastato ti compra un team di piattaforma di streaming di cui non avevi bisogno. Scegliere Kafka Streams quando Flink sarebbe stato giusto ti dà una pipeline che sbatte contro un muro di gestione dello state un anno dopo. Scegliere Spark Structured Streaming quando ti serviva latenza sotto il secondo ti dà una pipeline funzionante che manca il suo SLO. Nessuno catastrofico; tutti mesi di lavoro per sistemare.
Riferimenti incrociati
- Il Modulo 9 del corso PySpark copre Structured Streaming in profondità: API, watermarking e windowing, integrazione Kafka, sink writeStream per Delta e Iceberg, pattern operativi di produzione.
- La lezione 44 copre event time, watermark, e windowing come concetto cross-engine. Il vocabolario è condiviso anche se le implementazioni differiscono.
- La lezione 45 copre il processing exactly-once, dove i sink transazionali di Flink, il producer transazionale di Kafka Streams, e le scritture idempotenti di Spark si giocano in modi diversi.
- La lezione 47 copre Change Data Capture, dove la scelta dello stream processor interagisce con la scelta del tool CDC (Debezium più spesso).
Citazioni e letture aggiuntive
- Documentazione Apache Flink,
https://flink.apache.org/(consultata 2026-05-01). Il riferimento canonico. La sezione “Concepts”, soprattutto le parti su state e tempo, ripaga una lettura attenta. - Documentazione Kafka Streams,
https://kafka.apache.org/documentation/streams/(consultata 2026-05-01). Parte del progetto Apache Kafka; concisa e ben strutturata. - Spark Structured Streaming Programming Guide,
https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html(consultata 2026-05-01). Il riferimento standard; le sezioni su output mode e watermarking sono essenziali. - Tyler Akidau, Slava Chernyak, Reuven Lax, “Streaming Systems” (O’Reilly, 2018). Il riferimento concettuale cross-engine; il modello che descrive si mappa pulitamente su Flink e ragionevolmente su Spark e Kafka Streams.
- “Stream Processing with Apache Flink” (Fabian Hueske, Vasiliki Kalavri, O’Reilly, 2019). Il libro standard su Flink.
- “Kafka Streams in Action” (Bill Bejeck, Manning, 2a edizione, 2024). Il libro standard su Kafka Streams.
- “Learning Spark” (Jules S. Damji et al, O’Reilly, 2a edizione, 2020). Il libro standard su Spark; i capitoli su Structured Streaming coprono le basi che il Modulo 9 del corso PySpark espande.