Lecția 42 a acoperit Kafka, log-ul în care stau înregistrările. Înregistrările care stau într-un log nu fac nimic de la sine. Ceva trebuie să le citească, să le transforme, să le agrege, să le facă join și să scrie rezultatele undeva. Acel ceva este un stream processor, iar în 2026 există trei opțiuni care contează pentru majoritatea echipelor: Apache Flink, Kafka Streams și Spark Structured Streaming. Alegerea dintre ele este a doua cea mai mare decizie arhitecturală într-un sistem de streaming, după alegerea log-ului. Lecția asta parcurge comparația.
Versiunea scurtă, înainte de detalii. Flink este greul de categorie: un motor de stream processing standalone cu cele mai bogate API-uri pentru event-time și stare, cea mai mică latență și cea mai abruptă curbă operațională. Kafka Streams este biblioteca: un JAR Java pe care îl integrezi în aplicația ta, cel mai simplu deployment, strâns legat de Kafka. Spark Structured Streaming este compromisul: streaming în micro-batch peste Spark, răspunsul corect când deja rulezi Spark pentru batch și vrei cod unificat.
Cele trei nu sunt interschimbabile. Alegerea greșită este recuperabilă, dar dureroasă, iar costul migrării este suficient de mare încât alegerea merită luată în serios.
Apache Flink: greul de categorie nativ stream
Flink a început la Universitatea Tehnică din Berlin ca proiectul Stratosphere prin 2010, a devenit proiect Apache în 2014 și este implementarea de referință pentru stream processing din jur de 2017. Stream processing aici înseamnă în sensul literal: Flink procesează înregistrările una câte una pe măsură ce sosesc, fără batching la nivelul motorului.
Modelul de stare este ceea ce face Flink să iasă în evidență. Fiecare operator își menține propria stare cu cheie, partiționată cu aceeași cheie ca stream-ul de intrare. Starea este susținută de RocksDB pe disc local pentru stare mare și de memorie gestionată pentru stare mică, cu checkpoint-uri periodice către stocare durabilă (S3, HDFS, GCS) pentru recuperare. Savepoint-urile, checkpoint-uri declanșate de utilizator cu versionare, îți permit să oprești un job, să faci upgrade la cod și să reiei din același punct cu aceeași stare. Asta face posibil upgrade-fără-pierdere-de-date pentru job-urile de lungă durată și este rar printre motoarele de streaming.
Semanticile event-time sunt cetățeni de prim rang. Watermark-urile Flink (lecția 44 le acoperă în profunzime) sunt parte din API-ul de bază. Operațiunile de windowing (tumbling, sliding, session) sunt primitive bine înțelese. Evenimentele care sosesc târziu au tratare explicită: stream-uri de side-output, allowed lateness, trigger-e personalizate. Puterea expresivă este mai aproape de un limbaj decât de un framework.
Costul este complexitatea operațională. Un deployment Flink are un JobManager (coordonator), TaskManagers (workeri) și un state backend, toate trebuind să fie pornite și configurate corect. JobManager-ul este un single point of failure dacă nu îl rulezi în mod HA (ZooKeeper sau leader election Kubernetes). Tuning-ul RocksDB contează la scară. Checkpoint-urile către S3 trebuie dimensionate. Upgrade-ul versiunilor de Flink necesită grijă pentru că compatibilitatea savepoint-urilor nu este garantată între versiuni majore.
Echipele care aleg Flink o fac de obicei pentru că au nevoie de capabilitățile lui: procesare event-time complexă, exactly-once cu sink-uri externe, stare mare (gigabytes per operator) sau latență de milisecundă. Echipele care aleg Flink și nu au nevoie de capabilitățile lui tind să petreacă mult timp operațional învățând să-l țină sănătos. Cazul „s-ar putea să nu ai nevoie de Flink” este real.
Kafka Streams: biblioteca care rulează în aplicația ta
Kafka Streams este structural diferit de celelalte două: nu este un cluster pe care îl deployezi. Este o bibliotecă Java pe care o imporți, alături de restul codului tău de aplicație, care rulează pipeline-uri de streaming în interiorul unui proces Java obișnuit. JAR-ul este parte din aplicația ta; deployment-ul este deployment-ul aplicației tale; dacă deja rulezi microservicii Java pe Kubernetes, deja știi cum să rulezi Kafka Streams.
Modelul este strâns legat de Kafka. Fiecare aplicație Kafka Streams citește din topic-uri Kafka, scrie în topic-uri Kafka și stochează starea în topic-uri Kafka (topic-urile compactate servesc drept stocare durabilă, cu un cache RocksDB local pentru performanța la citire). Nu există un state backend separat, nu există submitere separată de job, nu există cluster. Aplicația este motorul de streaming, iar Kafka este tot ce e în jur.
API-ul are două niveluri. Processor API-ul de nivel mai jos îți dă control asupra procesării individuale a înregistrărilor, asupra state stores și asupra punctuation (callback-uri declanșate de timer). Streams DSL-ul de nivel mai înalt îți dă un set fluent de operațiuni: map, filter, groupByKey, aggregate, join, windowedBy. DSL-ul acoperă curat majoritatea workload-urilor, iar Processor API-ul este escape hatch-ul când DSL-ul nu mai ajunge.
Scalarea se face prin modelul de partiții al Kafka. O aplicație Kafka Streams care citește un topic cu douăsprezece partiții poate rula pe până la douăsprezece instanțe în paralel; biblioteca coordonează asignarea partițiilor prin mecanismul de consumer-group pe care Kafka îl are deja. Starea este colocată cu partiția: fiecare instanță deține un subset din partiții și state stores corespunzătoare. Rebalanțările mută starea între instanțe, ceea ce costă lățime de bandă de rețea, dar este automat.
Compromisurile sunt prețul simplicității. Managementul stării este bun, dar nu la fel de flexibil ca al Flink. Semanticile event-time sunt prezente, dar mai puțin complete. Nu există un frontend SQL în biblioteca de bază (ksqlDB există, dar ca produs Confluent separat deasupra). Latența este mică (comparabilă cu Flink), dar limita superioară a dimensiunii stării este RAM-plus-RocksDB pe o singură instanță, mai mică decât modelul de stare distribuită al Flink.
Echipele care aleg Kafka Streams au de obicei un codebase Java sau Scala, rulează deja microservicii pe Kafka și vor să adauge logică de streaming la servicii existente fără să ridice o platformă nouă. Potrivirea este excelentă pentru asta. Pentru echipele fără un stack JVM, alegerea este incomodă (port-urile Python și Go sunt neoficiale și incomplete), iar asta le împinge spre Flink sau Spark.
Spark Structured Streaming: micro-batch pe Spark
Spark Structured Streaming stă la mijloc. Este un API de streaming peste motorul de batch Spark, procesând înregistrările în loturi mici (de obicei 100 până la 500 ms) în loc de unul câte unul. Același API DataFrame funcționează pentru batch și streaming: dacă deja folosești Spark pentru ETL în batch, codul tău de streaming arată aproape identic, rulează pe același cluster, folosește aceleași biblioteci.
Arhitectura este cea standard Spark: un driver care coordonează executors, cu runtime-ul de streaming adăugând un trigger care se declanșează la fiecare interval de batch. Fiecare trigger citește înregistrări noi de la sursă (Kafka, Kinesis, fișiere), rulează interogarea DataFrame și scrie rezultatele. Starea este ținută în memorie, opțional susținută de HDFS sau S3 prin Spark checkpointing. Watermark-urile și windowing-ul sunt suportate. Setul de operațiuni suportate este mai mic decât al Flink, mai mare decât ce au nevoie majoritatea echipelor.
Latența este cea mai vizibilă diferență față de Flink și Kafka Streams. Micro-batch-ul are un prag structural: chiar și cu loturi de 100 ms, latența end-to-end este de obicei câteva sute de milisecunde până la o secundă, nu cele câteva milisecunde de o singură cifră pe care le poate atinge Flink. Pentru dashboard-uri, alerting și ETL, asta e ok. Pentru cerințe sub 100 ms, nu este. Modul Continuous Processing (Spark 2.3+) încearcă streaming adevărat pe motorul Spark; în 2026 este încă mai puțin matur decât streaming-ul nativ al Flink, iar majoritatea echipelor rămân pe micro-batch.
Complexitatea operațională este la mijloc. Un cluster Spark este un lucru cunoscut în 2026: majoritatea echipelor de date au unul sau au acces prin Databricks, EMR, Dataproc sau Synapse. Adăugarea de job-uri de streaming la un deployment existent înseamnă în principal scrierea codului și îndreptarea lui către cluster. Comparativ cu Flink, mai lin pentru că Spark a avut mai mulți ochi pe el mai mult timp; comparativ cu Kafka Streams, mai greu pentru că există un cluster separat de ținut viu.
Echipele care aleg Structured Streaming au de obicei Spark pentru batch și vor să consolideze pe un singur motor. API-ul unificat este o valoare reală: codul poate fi partajat între pipeline-urile de batch și cele de streaming. Modulul 9 al cursului de PySpark acoperă Structured Streaming end to end; decizia arhitecturală din lecția asta este când să alegi Structured Streaming în loc de Flink sau Kafka Streams.
Comparația
Punând cele trei una lângă alta pe dimensiunile care contează pentru alegere.
Latență. Flink și Kafka Streams sunt amândouă în zona de milisecunde scăzute pentru workload-urile tipice. Spark Structured Streaming este la câteva sute de milisecunde până la o secundă în mod micro-batch. Pentru majoritatea workload-urilor analitice și operaționale, toate trei sunt suficient de rapide. Pentru cerințe sub 100 ms, Flink sau Kafka Streams.
Managementul stării. Flink câștigă: stare susținută de RocksDB, savepoint-uri, suport pentru stare mare, exactly-once peste operatori. Kafka Streams al doilea: stocări RocksDB per task, susținere durabilă în topic-uri compactate, scară mai mică. Spark al treilea: stare în memorie cu fault tolerance bazat pe checkpoint, model mai simplu, scară mai mică.
Complexitatea operațională. Kafka Streams câștigă (doar o bibliotecă). Spark al doilea (un cluster cunoscut existent). Flink al treilea (un cluster separat cu propria curbă de învățare). Ordinea este exact opusă clasamentului de management al stării: motoarele care îți dau mai mult plătesc pentru asta cu amprentă operațională.
Potrivirea în ecosistem. Spark pentru shop-urile SQL-și-batch, în special oricine pe Databricks sau care rulează PySpark. Kafka Streams pentru shop-urile de microservicii Java/Scala pe Kafka. Flink pentru cazul „avem nevoie de real real-time”, cazul finanțelor reglementate „exactly-once peste sisteme”, cazul gaming/IoT/adtech unde și starea, și latența contează.
Suportul pentru limbaje. Spark are suport complet pentru Python și Scala, parțial pentru Java. Flink are suport complet pentru Java/Scala, decent pentru Python (PyFlink), niciunul pentru Go sau alte limbaje. Kafka Streams este doar Java/Scala; port-urile neoficiale pentru Python și Go nu sunt de calitate de producție.
Frontend-uri SQL. Flink are Flink SQL, care este competent și se îmbunătățește. Spark are Spark SQL, care este matur și excelent. Kafka Streams are ksqlDB (tehnic un produs separat, dar povestea 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
Diagramă de creat: o vizualizare șlefuită cot la cot a celor trei motoare, cu sursele tipice de intrare în stânga și sink-urile de ieșire în dreapta. Punctul vizual este că partea de surse se suprapune aproape complet (oricare dintre cele trei poate citi din Kafka), dar partea de sink-uri și forma de deployment diferă. Flink este un cluster standalone cu suport larg pentru sink-uri. Kafka Streams este o bibliotecă în interiorul unei aplicații, scriind în primul rând înapoi în Kafka. Spark Structured Streaming este pe un cluster Spark cu suport puternic pentru warehouse și data lake.
Când fiecare este răspunsul potrivit
Trei întrebări clarifică de obicei alegerea.
Ce rulează deja echipa ta? Dacă ai Spark, Structured Streaming este calea cea mai puțin rezistentă. Dacă ai Kafka și un stack Java/Scala, Kafka Streams. Dacă niciunul, întrebarea se mută la workload.
Care sunt cerințele tale de latență? End-to-end sub 100 ms, Flink sau Kafka Streams. O secundă sau două, oricare dintre cele trei.
Câtă stare trebuie să gestionezi? Gigabytes per cheie, Flink. Câteva sute de megabytes per partiție fără tranzacții cross-operator, Kafka Streams sau Spark sunt ok.
Realitatea din 2026 este că majoritatea echipelor aleg unul și rulează cu el ani de zile. Flink are cazul cel mai puternic pentru sisteme greenfield grele de streaming. Kafka Streams pentru sisteme existente de microservicii Java unde streaming-ul este o capabilitate printre multe altele. Spark Structured Streaming pentru shop-urile de analiză și data engineering unde aceeași echipă scrie pipeline-uri de batch și de streaming.
Modurile de eșec ale alegerii greșite diferă. Alegerea Flink când Kafka Streams ar fi fost suficient îți cumpără o echipă de platformă de streaming de care nu aveai nevoie. Alegerea Kafka Streams când Flink ar fi fost potrivit îți dă un pipeline care lovește un zid de management al stării după un an. Alegerea Spark Structured Streaming când aveai nevoie de latență sub-secundă îți dă un pipeline funcțional care își ratează SLO-ul. Niciunul catastrofic; toate luni de muncă pentru reparat.
Referințe încrucișate
- Modulul 9 al cursului de PySpark acoperă Structured Streaming în profunzime: API, watermarking și windowing, integrare Kafka, sink-uri writeStream pentru Delta și Iceberg, pattern-uri operaționale de producție.
- Lecția 44 acoperă event time, watermark-uri și windowing ca un concept cross-engine. Vocabularul este partajat chiar dacă implementările diferă.
- Lecția 45 acoperă procesarea exactly-once, unde sink-urile tranzacționale ale Flink, producer-ul tranzacțional al Kafka Streams și scrierile idempotente ale Spark se manifestă diferit.
- Lecția 47 acoperă Change Data Capture, unde alegerea stream processor-ului interacționează cu alegerea uneltei CDC (Debezium cel mai des).
Citări și lectură suplimentară
- Apache Flink documentation,
https://flink.apache.org/(consultat 2026-05-01). Referința canonică. Secțiunea „Concepts”, în special părțile despre stare și timp, răsplătește o citire atentă. - Kafka Streams documentation,
https://kafka.apache.org/documentation/streams/(consultat 2026-05-01). Parte din proiectul Apache Kafka; concis și bine structurat. - Spark Structured Streaming Programming Guide,
https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html(consultat 2026-05-01). Referința standard; secțiunile despre output modes și watermarking sunt esențiale. - Tyler Akidau, Slava Chernyak, Reuven Lax, “Streaming Systems” (O’Reilly, 2018). Referința conceptuală cross-engine; modelul pe care îl descrie se mapează curat pe Flink și rezonabil pe Spark și Kafka Streams.
- “Stream Processing with Apache Flink” (Fabian Hueske, Vasiliki Kalavri, O’Reilly, 2019). Cartea standard despre Flink.
- “Kafka Streams in Action” (Bill Bejeck, Manning, 2nd edition, 2024). Cartea standard despre Kafka Streams.
- “Learning Spark” (Jules S. Damji et al, O’Reilly, 2nd edition, 2020). Cartea standard despre Spark; capitolele despre Structured Streaming acoperă bazele pe care Modulul 9 al cursului de PySpark le extinde.