Le lezioni precedenti del Modulo 6 hanno introdotto i mattoni dello streaming. Kafka come log durevole. Flink e Spark Structured Streaming come motori che computano sui dati in movimento. Watermark ed event time come modello per “quando posso committare un risultato”. Questa lezione fa di nuovo zoom out e pone la domanda architetturale che il Modulo 5 e il Modulo 6 insieme impongono a ogni team dati: quando hai sia pipeline batch sia pipeline streaming, come si incastrano?
Ci sono due risposte canoniche, entrambe con nomi che suonano molto più altisonanti di quello che sono. La Lambda architecture fa girare una pipeline batch e una pipeline streaming fianco a fianco e le unisce al momento della query. La Kappa architecture fa girare solo una pipeline streaming e fa il replay del log storico quando ha bisogno della vista batch. I nomi vengono dalle persone che le hanno proposte. La decisione è più vecchia dei nomi.
Nel 2026 la risposta pratica è per lo più Kappa, con qualche caveat. Il lavoro interessante è capire perché Lambda è esistita, perché Kappa l’ha sostituita, e quel ristretto insieme di casi in cui Lambda guadagna ancora il suo posto.
Il mondo che ha prodotto Lambda
Nathan Marz ha proposto la Lambda architecture in una serie di blog post e talk a partire dal 2011, formalizzata nel suo libro del 2015 “Big Data” scritto con James Warren. Il contesto conta. Nel 2011 lo strumento batch del momento era Hadoop con MapReduce. Gli strumenti di streaming erano Storm (che Marz aveva costruito a BackType e Twitter) e una manciata di concorrenti meno maturi. Kafka esisteva ma era nuovo. Flink era un progetto di ricerca. Spark era un paper di AMPLab.
In quel mondo, batch e streaming erano due bestie diverse in due sensi, entrambi a spingere verso il farli girare separati.
Il primo senso era tecnico. Gli strumenti batch processavano dataset storici limitati, ottimizzati per il throughput, e producevano risultati esatti. Gli strumenti di streaming processavano dati illimitati, ottimizzati per la latenza, e producevano risultati approssimati: conteggi grossolani, distinct approssimati, sketch che barattavano accuratezza per velocità. I due motori avevano API diverse, modelli di fault-tolerance diversi, e profili di performance diversi. Chiedere a un motore di streaming del 2011 di fare anche il caso storico batch era chiedergli di fare qualcosa per cui non era stato costruito.
Il secondo senso era operativo. I job di streaming erano considerati fragili. Potevano perdere dati in caso di failure, soffrire per gli eventi in ritardo senza un percorso di recovery ovvio, e allontanarsi dalla verità su uptime lunghi. La pipeline batch era la fonte di verità; la pipeline streaming era un’approssimazione che ti dava qualcosa da guardare mentre aspettavi che il run batch finisse. Se la pipeline streaming si rompeva, il run batch l’avrebbe corretta la mattina dopo.
Lambda ha formalizzato quella separazione come architettura.
Com’è fatta Lambda
Tre layer, tutti che leggono lo stesso log di input di eventi immutabili.
Batch layer. Conserva l’intero dataset storico e lo riprocessa periodicamente (tipicamente di notte) per produrre viste accurate e complete. L’output è chiamato batch view. Se un run batch impiega sei ore, la batch view è vecchia fino a ventiquattro ore in qualunque momento. Va bene, perché la batch view è la fonte di verità: vecchia ma corretta.
Speed layer. Legge lo stesso log di input in tempo reale e produce una real-time view che copre solo gli eventi arrivati dall’ultimo run batch. La real-time view è approssimata (può usare sketch, sampling, o altri compromessi velocità-per-accuratezza) ed è di breve durata: appena il run batch successivo si completa, la porzione corrispondente della real-time view viene scartata.
Serving layer. Riceve query, legge da entrambe le viste, e unisce i risultati. Una query che chiede “totale eventi negli ultimi sette giorni” legge la batch view per i giorni che il batch ha coperto e la real-time view per tutto ciò che è successo dall’ultimo run batch. L’utente vede una singola risposta che è per lo più accurata (la parte batch) e parzialmente approssimata (la coda real-time).
flowchart LR
LOG[(Event log)]
BATCH[Batch layer<br/>full history<br/>exact, slow]
SPEED[Speed layer<br/>recent only<br/>approximate, fast]
BV[(Batch view)]
RTV[(Real-time view)]
SERVE[Serving layer]
Q[Query]
LOG --> BATCH --> BV
LOG --> SPEED --> RTV
BV --> SERVE
RTV --> SERVE
Q --> SERVE
Nel mondo dal 2011 al 2015 questo era un design difendibile. Ottenevi risposte a bassa latenza dallo speed layer, risposte esatte dal batch layer, e il merge al momento della query ti dava il meglio dei due mondi. L’architettura sopravviveva con grazia ai failure dei motori: il batch layer avrebbe recuperato la volta successiva che girava, e le approssimazioni dello speed layer erano economiche da buttare via.
La lamentela su Lambda
La lamentela, espressa a gran voce da Jay Kreps nel 2014 e da molti altri da allora, è che Lambda ti costringe a mantenere due codebase che computano la stessa cosa.
La pipeline batch ha un job Spark o MapReduce che aggrega sette giorni di eventi in un conteggio. La pipeline speed ha un job Storm o Flink che fa la stessa aggregazione in tempo reale. La logica è la stessa; l’implementazione è duplicata, in linguaggi diversi, contro API diverse, con harness di test diversi, deployata su infrastrutture diverse, spesso di proprietà di team diversi.
Ne seguono tre dolori operativi.
Le bug fix vanno fatte due volte. Sistemi un bug nel job batch, poi porti la fix nel job streaming. Le due codebase divergono sotto pressione di scadenza quando solo il percorso urgente viene patchato. Alla fine la batch view e la real-time view iniziano a non essere d’accordo in modi sottili, e capire quale delle due è giusta diventa un esercizio di forensica.
Gli schemi divergono. Un nuovo campo viene aggiunto agli eventi di input. Il job batch lo prende; il job streaming no, perché la topologia Storm è stata toccata l’ultima volta otto mesi fa e nessuno si è ricordato di aggiornarla. La vista unita inizia a mostrare valori inconsistenti per il nuovo campo a seconda che il punto dato cada nella parte batch o nella parte speed della finestra.
L’overhead operativo raddoppia. Due pipeline significano due cluster, due percorsi di deployment, due rotazioni on-call, due set di dashboard, due set di alert. Per un team piccolo questa è una frazione reale della capacità totale di engineering.
Il costo si paga tutti i giorni. Il beneficio (bassa latenza più accuratezza esatta) è reale, ma il costo era abbastanza alto che la gente ha iniziato a chiedersi se fosse strettamente necessario.
Kappa: una sola pipeline
Jay Kreps, allora a LinkedIn e autore originale di Kafka, ha proposto la Kappa architecture in un post del 2014 intitolato “Questioning the Lambda Architecture”. L’argomento era semplice: se il tuo stream processor è abbastanza buono da gestire il caso storico via replay, la pipeline batch non ti serve per niente.
Il design di Kappa è una sola pipeline.
flowchart LR
LOG[(Event log<br/>Kafka, durable, replayable)]
STREAM[Stream processor<br/>Flink or Spark Streaming]
VIEW[(Materialised view)]
Q[Query]
LOG --> STREAM --> VIEW --> Q
Lo stream processor legge da un log durevole e replayabile (Kafka, Kinesis, Pulsar). Per il lavoro real-time legge la coda del log e aggiorna la vista materializzata man mano che arrivano nuovi eventi. Per ricostruire la vista da zero (perché la logica è cambiata, o la vista si è corrotta, o hai scoperto un bug), avvii una nuova istanza dello stream processor che legge dall’inizio del log, la lasci recuperare, e sposti il traffico sulla nuova vista quando supera quella vecchia. Lo stesso codice processa entrambi i casi. C’è una sola codebase, un solo set di garanzie, un solo schema, una sola rotazione on-call.
Le precondizioni tecniche perché Kappa funzioni non erano soddisfatte nel 2011. Erano soddisfatte entro il 2018 e lo sono molto nel 2026.
Il log deve essere abbastanza durevole. Kafka con una retention sensata (lezione 42) tiene gli eventi abbastanza a lungo da fare il replay di tutta la storia. Per i dati che non entrano nella retention di Kafka, gli eventi vivono anche in una tabella lakehouse (lezione 37) da cui lo stream processor può fare il replay.
Lo stream processor deve gestire il bulk replay. Flink e Spark Structured Streaming, nel 2026, possono macinare dati storici a un throughput paragonabile al batch quando gli si danno le risorse. Lo stesso motore che processa cento eventi al secondo dalla coda live può processarne cento milioni al secondo durante il replay.
Gli state store devono essere ricostruibili. I job di streaming stateful (lezione 43) tengono il loro stato in RocksDB o store embedded simili supportati da checkpoint. Un replay da zero ricostruisce lo stato da zero man mano che procede. Non c’è uno step separato di “warm up della cache”.
In un setup Kappa, lo stesso job Flink che mantiene un’aggregazione real-time è anche il job che, se punti una nuova istanza all’inizio del log, produrrà l’aggregazione storica da zero. Una sola codebase. Un solo set di garanzie. Una sola cosa da operare.
Perché Kappa ha vinto, in gran parte
Nel 2026 la maggior parte delle nuove architetture streaming-and-batch va di default su Kappa. Tre ragioni.
Gli stream processor sono diventati abbastanza potenti. Flink con stato RocksDB, sink exactly-once, e watermark in event time fa quello che Storm non poteva fare nel 2011. Spark Structured Streaming ti dà lo stesso motore per il caso streaming e per quello batch, con il codice streaming che è un superset stretto del codice batch. L’obiezione tecnica che ha motivato Lambda non regge più.
I lakehouse forniscono lo storage di backing. Un setup Kappa ha bisogno di una storia durevole e replayabile. Per le firehose ad alto volume che eccedono la retention di Kafka, quella storia vive in tabelle Iceberg o Delta o Hudi (lezione 37), che gli stream processor possono leggere come sorgente. La domanda “dove tengo la storia lunga” ha una risposta pulita.
Il costo della doppia codebase è sempre stato troppo alto. Anche i team che amavano la separazione delle preoccupazioni di Lambda finivano per pagare la tassa di sincronizzazione a ogni release. Una volta che Kappa è diventata tecnicamente fattibile, la semplificazione operativa è stata difficile da contestare.
Il case study alla fine di questo modulo (lezione 48, Uber) è una storia in pratica a forma di Kappa: ingestion streaming-first, storage lakehouse, query batch e real-time servite dallo stesso layer fisico. Il pattern si ripete in tutte le piattaforme dati più grandi del settore.
Quando Lambda ha ancora un suo posto
Il default del 2026 è Kappa. Lambda non è estinta, ma i casi in cui guadagna la sua complessità sono più ristretti di quanto fossero.
Le risposte batch e stream sono genuinamente diverse. I distinct approssimati con HyperLogLog nello streaming layer sono estremamente economici con errore limitato. I distinct esatti nel batch layer costano soldi veri ma ti danno la verità. Se il tuo prodotto vuole entrambi (una dashboard veloce e approssimata più un report lento e auditato) e lo sketch streaming non è solo una versione meno precisa dell’aggregato batch ma una computazione diversa, Lambda ti permette di tenerli come pipeline separate che producono cose diverse invece che la stessa cosa due volte.
Latenza e accuratezza vivono in prodotti diversi. Una dashboard di trading floor vuole freschezza di trenta millisecondi su volumi e prezzi. La riconciliazione di fine giornata vuole numeri esatti, auditati, lenti, firmati dal compliance. I due consumer non condividono un percorso di query, e farli girare come due pipeline che si trovano a leggere lo stesso log di input a volte è più pulito che far girare una pipeline che deve soddisfare entrambi i contratti.
Sistemi legacy in cui il batch layer precede lo streaming. Un team ha fatto girare un Spark batch notturno per dieci anni contro lo stesso log di input e aggiunge una pipeline streaming per i casi real-time che sono apparsi da allora. Sostituire la pipeline batch è una migrazione di più trimestri con rischio reale. La scelta pragmatica è lasciarla al suo posto e far girare lo streaming a fianco. Quella è Lambda per accumulo invece che per design, e va bene, finché qualcuno tiene le due consistenti.
Team abbastanza grandi da avere staff per entrambe le pipeline. A scala sufficiente, team streaming e batch dedicati con prodotti diversi e contratti di latenza diversi possono operare due codebase senza che il dolore di sincronizzazione domini, perché le due codebase non stanno in realtà computando la stessa cosa. Stanno computando cose diverse che si trovano a condividere un nome.
Fuori da questi casi, l’overhead di Lambda è raramente giustificato.
Il consiglio del 2026
Vai di default su Kappa. Usa un solo stream processor sia per la coda live sia per il replay storico. Sostienilo con Kafka per il replay a breve termine e una tabella lakehouse per il replay a lungo termine. Esprimi le tue aggregazioni una volta sola. Falle girare su una sola codebase, deployata una volta, di proprietà di un solo team.
Se ti trovi a tendere la mano verso Lambda, articola perché. La risposta streaming è genuinamente una computazione diversa dalla risposta batch, non solo una versione più veloce e meno precisa? I due consumer sono abbastanza diversi che una sola codebase che serve entrambi è un fit architetturale peggiore di due codebase che servono ciascuno? Hai il team per operare due pipeline, o stai aggiungendo una seconda pipeline a un team che è già tirato sulla prima?
Se la risposta a queste domande è sì, Lambda va bene. Se la risposta è “abbiamo sempre fatto così” o “la pipeline streaming non sembra ancora affidabile”, investi nella pipeline streaming finché non lo è, invece di costruire una seconda pipeline il cui costo di manutenzione sopravviverà al deficit di fiducia.
L’inquadramento del Modulo 5 di “gli stessi pattern a ogni scala” si applica anche qui. Un team piccolo che fa girare un singolo job Flink contro un singolo topic Kafka e un lakehouse di cinque tabelle sta facendo Kappa. Una piattaforma a scala di petabyte che fa girare centinaia di job Flink contro migliaia di topic e decine di migliaia di tabelle lakehouse sta facendo la stessa cosa, solo più grande. La forma architetturale è identica. La lezione 48 percorre una di queste forme alla scala di Uber e mostra le stesse primitive che fanno lo stesso lavoro.
Citazioni e letture di approfondimento
- Nathan Marz e James Warren, “Big Data: Principles and Best Practices of Scalable Real-Time Data Systems” (Manning, 2015). Il libro che ha formalizzato la Lambda architecture, con la motivazione originale e il design layer per layer.
- Jay Kreps, “Questioning the Lambda Architecture”, O’Reilly Radar, 2014,
https://www.oreilly.com/radar/questioning-the-lambda-architecture/(consultato 2026-05-01). Il post che ha introdotto Kappa come alternativa e ha sostenuto la causa di una sola pipeline. - “Designing Data-Intensive Applications” (Martin Kleppmann, O’Reilly, 2017), capitoli 11 e 12. Il riferimento standard per lo stream processing e la relazione tra batch e streaming.
- Documentazione di Apache Flink,
https://flink.apache.org/(consultato 2026-05-01). Il motore di streaming che rende Kappa pratica su scala, con la gestione dello stato e la semantica exactly-once che corrispondono ai bisogni architetturali.