Arhitectura datelor și a sistemelor, de la zero Lecția 35 / 80

Spark și batch-ul modern

Înlocuitorul in-memory pentru Hadoop, lecțiile pe care le-a păstrat și stack-ul batch modern din 2026.

Lecția 34 s-a încheiat cu o listă de lucruri pe care Hadoop le-a făcut bine și de lucruri pe care le-a făcut prost. Cel mai consecvent dintre lucrurile greșite era state-ul intermediar greu pe disc: fiecare shuffle lovea discul, fiecare iterație a unui job iterativ plătea din nou taxa de disc, iar workload-urile de machine learning și grafuri rulau de ordine de mărime mai lent decât aveau dreptul. Înlocuitorul pentru MapReduce a venit din AMPLab-ul UC Berkeley în 2010, a fost generalizat într-un articol din 2012, a devenit proiect Apache top-level în 2013, iar până în 2018 mâncase aproape întreaga piață de procesare batch pe care o deținea Hadoop. Lecția asta parcurge Spark, ecosistemul din jurul lui, motoarele care concurează cu el pentru diverse nișe, și arborele de decizie pentru când să apelezi la care.

Cadrul lecției e comparativ. Spark e una dintre mai multe unelte batch la care un data engineer din 2026 ar putea apela, iar întrebarea interesantă rareori e „e Spark bun?”, ci „e Spark unealta potrivită pentru această formă specifică de muncă?”. Răspunsul depinde de mărimea datelor, de bugetul de latență, de platforma existentă a echipei și de dacă SQL pe un warehouse gestionat acoperă deja cazul de utilizare.

De ce a câștigat Spark

Matei Zaharia și grupul AMPLab au introdus Spark ca să repare slăbiciunile specifice ale MapReduce, păstrând părțile care funcționau. Articolul original („Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing”, NSDI 2012) merită citit; ideile centrale sunt scurte.

State intermediar in-memory. Un job Spark reprezintă datele ca o serie de seturi imutabile, partiționate, numite RDD (Resilient Distributed Datasets). Operațiile construiesc un nou RDD dintr-unul vechi. Crucial, RDD-urile intermediare pot fi cache-uite în memoria din cluster și reutilizate de etape ulterioare fără drum dus-întors prin disc. Pentru un job iterativ precum antrenarea unei regresii logistice pe aceleași date de antrenament timp de cincizeci de epoci, MapReduce a plătit costul discului de cincizeci de ori; Spark îl plătește o singură dată. Accelerările raportate în articolul din 2012 erau de zece până la o sută de ori pentru workload-uri iterative și au rezistat în producție.

Execuție conștientă de DAG. MapReduce rula o pereche map-reduce la un moment dat, materializând output-ul pe HDFS între joburi. Un query care avea nevoie de trei pași map-reduce era trei joburi separate cu trei scrieri pe HDFS. Spark construiește un graf direcționat aciclic al tuturor operațiilor dintr-un singur job, optimizează peste tot graful, și materializează doar rezultatul final. Modelul de cost alege granițe de shuffle mai bune, iar runtime-ul poate face pipeline pe operațiile care nu necesită un shuffle între ele.

API-uri mai bune. Primul API Spark era unul funcțional nativ Scala, care era deja un pas înainte față de Hadoop brut. Al doilea a fost DataFrames (2015), modelat după R și pandas, cu un optimizator de query (Catalyst) dedesubt care compila operațiile DataFrame în planuri de execuție eficiente. Al treilea a fost Spark SQL, care îți permitea să scrii un select împotriva unui DataFrame ca și cum ar fi un tabel de bază de date. Până în 2020 majoritatea codului Spark era DataFrames sau SQL, cu RDD-urile brute rezervate pentru cazurile rare în care API-urile de nivel mai înalt nu se potriveau. PySpark a făcut aceleași API-uri disponibile din Python, ceea ce e ce scriau efectiv majoritatea data engineer-ilor.

Poliglot de la prima zi. Scala (limbajul nativ), Java, Python (PySpark) și R aveau toate API-uri first-class. SQL a venit mai târziu dar a devenit dominant. Același motor le rula pe toate, ceea ce însemna că o echipă mixtă de ingineri Scala-și-Python putea împărți un codebase fără ca limbajul să fie o graniță de integrare.

Combinația de viteză, expresivitate și acoperire de limbaje a fost suficientă să detroneze MapReduce ca implicit în câțiva ani. Clusterele Hadoop nu au dispărut peste noapte; au primit Spark instalat alături de MapReduce, apoi Spark a preluat încet ca motor, apoi HDFS a fost înlocuit de S3, și într-o zi nu mai era niciun Hadoop în clădire.

Ecosistemul Spark în 2026

Spark în 2026 e un stack de componente deasupra aceluiași motor de execuție.

Spark SQL și DataFrames. API-ul dominant. Aproape tot codul batch nou e scris aici. Optimizatorul Catalyst transformă planuri logice în planuri fizice, iar motorul de execuție Tungsten generează bytecode eficient pentru operatori. Pentru majoritatea echipelor, „a scrie un job Spark” înseamnă a scrie SQL sau operații DataFrame înlănțuite și a avea încredere în optimizator.

Structured Streaming. API-ul de streaming al Spark, care împarte același model DataFrame și motor. Pitch-ul e „același cod care procesează un batch din datele de ieri poate procesa stream-ul de azi”, și Spark se apropie remarcabil de a livra asta. Modulul 6 acoperă streaming-ul în detaliu; deocamdată faptul relevant e că Spark e unul dintre motoarele care concurează pentru acel workload.

MLlib. Biblioteca clasică de machine learning deasupra Spark: implementări distribuite de regresie, clustering, recomandare, gradient-boosted trees. MLlib conta când datele de antrenament nu încăpeau pe o singură mașină, iar deep learning-ul nu era abordarea dominantă. În 2026 majoritatea antrenamentului ML s-a mutat la framework-uri dedicate (PyTorch, JAX, scikit-learn pe o singură mașină mare, sau platforme specializate). MLlib încă se livrează și încă funcționează; rar e prima alegere.

GraphX. API-ul de procesare pe grafuri. Există. Aproape nimeni nu-l folosește. Workload-urile pe grafuri în 2026 rulează în mare parte pe motoare dedicate (Neo4j, JanusGraph, NebulaGraph, extensiile pentru grafuri din Postgres sau DuckDB) sau pe Spark cu o bibliotecă de grafuri atașată. GraphX e o notă de subsol.

Rostul listării componentelor e că „Spark” în materialele de marketing acoperă o amprentă largă, iar singura piesă pe care majoritatea echipelor o folosesc efectiv intens e Spark SQL plus DataFrames. Restul e disponibil și ocazional relevant.

Databricks

Spark a rămas un proiect Apache, dar compania scoasă din AMPLab pentru a-l comercializa (Databricks, fondată 2013) a devenit dominantă într-un mod pe care niciun susținător comercial al unui proiect Apache nu l-a mai egalat de la primii ani ai Cloudera. Databricks rulează cel mai mare serviciu Spark gestionat, vinde un produs notebook-și-cluster care a devenit efectiv platforma-de-date-de-record pentru multe echipe enterprise de date și a extins platforma cu adăugiri proprietare pe care proiectul Spark open-source nu le are. Delta Lake (lecția 37) e cea mai consecvențială dintre acestea, iar Unity Catalog (guvernare), Photon (un motor de query vectorizat C++ pentru Spark SQL) și produsele AI/BI sunt toate doar Databricks.

În 2026, Databricks e listată public (IPO finalizat la sfârșitul lui 2024), e al doilea cel mai mare furnizor de platforme de date după venituri în spatele Snowflake, și e răspunsul implicit pentru „vrem o platformă de date bazată pe Spark fără să operăm noi Spark” în enterprise. Spark-urile gestionate concurente (AWS EMR, Google Dataproc, Azure Synapse Spark) sunt reale, dar nu au detronat Databricks pentru companiile care s-au angajat la un stack centrat pe Spark.

Concurenții în 2026

Spark nu e singurul motor batch și e tot mai puțin cel potrivit pentru câteva forme comune de muncă. Peisajul competitiv e partea cu adevărat interesantă a lecției.

DuckDB. O bază de date analitică in-process, columnar, single-machine, scrisă în C++. Pitch-ul DuckDB e că o fracțiune enormă din workload-urile „trebuie să zdrobesc niște fișiere Parquet” nu au nevoie de un cluster: un singur server modern cu o sută de gigabytes de RAM poate mesteca sute de gigabytes de Parquet pe disc în secunde, fără cluster, fără scheduler, fără amprentă operațională. DuckDB rulează ca bibliotecă în Python, R, Node sau un CLI; îl imporți, îl îndrepți spre S3 și scrii SQL. Pentru seturi de date până la circa un terabyte, DuckDB e adesea mai rapid decât Spark pe același query, pentru că nu există shuffle, nu există JVM, nu există coordonare driver-executor, doar un proces care citește date columnar cu un motor de execuție vectorizat. DuckDB a mâncat o fracțiune semnificativă din workload-ul „analiză mică spre medie” care obișnuia să ruleze pe Spark implicit.

Polars. O bibliotecă DataFrame scrisă în Rust, cu un API similar Pandas. Aceeași nișă ca DuckDB dar cu o suprafață DataFrame în loc de SQL, și un profil de performanță similar: vectorizat, columnar, single-machine, rapid. Polars și DuckDB au devenit substituenți apropiați pentru multe cazuri de utilizare, iar alegerea între ele e adesea condusă de dacă echipa preferă SQL sau DataFrames ca suprafață.

BigQuery, Snowflake, Redshift, Databricks SQL. Warehouse-urile găzduite. După cum a acoperit lecția 33, tiparul ELT e să încarci datele brute în warehouse și să le transformi cu SQL. Pentru workload-urile care se potrivesc în acest tipar (majoritatea BI și analiză), motorul warehouse gestionează procesarea batch intern, iar Spark nu e deloc în peisaj. Utilizatorul scrie un select, iar motorul warehouse (Dremel în interiorul BigQuery, motoarele proprietare în interiorul Snowflake și Redshift, Photon în interiorul Databricks SQL) face munca batch distribuită invizibil.

Trino și Presto. Motoare de query SQL distribuite, dezvoltate inițial la Facebook (Presto, 2013) și forkuite în Trino (2020). Ambele sunt proiectate să interogheze date care stau în object storage și warehouse-uri fără să le copieze. Trino e alegerea dominantă open-source pentru „SQL peste multe surse”, cu Starburst ca susținător comercial. Se suprapune cu Spark SQL pentru query-uri analitice ad-hoc și cu warehouse-urile pentru citiri federate. Rar e prima alegere pentru ETL greu, dar adesea răspunsul potrivit pentru query-uri interactive ale analiștilor peste un lake.

Dask și Ray. Biblioteci de calcul distribuit native Python. Dask e cel mai aproape de Spark în spirit: un API DataFrame și un task scheduler, ambele în Python, care scalează la clustere. Ray e mai general (un framework de task-uri distribuite) și a devenit backend-ul implicit pentru antrenament și serving ML distribuit. Ambele au o nișă în care Python e limbajul principal și workload-ul nu se potrivește bine cu Spark sau SQL pe warehouse, dar niciuna nu a detronat Spark pentru batch cu scop general.

Când să apelezi la Spark în 2026

Un arbore scurt de decizie.

flowchart TD
    Q[Batch work to do] --> SizeQ{Data size}
    SizeQ -->|"Fits in RAM<br/>on one machine"| Single[DuckDB or Polars]
    SizeQ -->|"100GB to a few TB"| MidQ{Already in a<br/>warehouse?}
    SizeQ -->|"Many TB to PB"| Spark[Spark or Databricks]
    MidQ -->|Yes| WH[Warehouse SQL<br/>BigQuery, Snowflake, Redshift]
    MidQ -->|No, on object storage| LakeQ{Need streaming<br/>or complex ETL?}
    LakeQ -->|No, just SQL| Trino[Trino or DuckDB on S3]
    LakeQ -->|Yes| Spark
    Spark --> StreamQ{Sub-second<br/>latency?}
    StreamQ -->|Yes| Flink[Flink, see Module 6]
    StreamQ -->|No| SparkOK[Spark Structured Streaming<br/>or batch]

Spark își câștigă locul atunci când una sau mai multe dintre următoarele e adevărată.

Datele sunt cu adevărat mari. Terabytes până la petabytes per job. Motoarele single-machine încep să se chinuie, creditele de warehouse devin scumpe, iar shuffle-ul la scară de cluster pe care Spark îl face bine devine bottleneck-ul. Ăsta e workload-ul pentru care a fost proiectat Spark și cel pentru care e încă implicit.

Jobul amestecă batch și streaming pe aceleași date. Structured Streaming îți permite ca aceeași cale de cod să servească ambele moduri, cu un câștig semnificativ de eficiență față de rularea a două stack-uri separate. Dacă platforma ta are nevoie de ambele și nu vrei să operezi Flink pentru partea de streaming, Spark e un răspuns bun.

Ești deja pe Databricks sau ai un cluster Spark. Inerția e un departajator legitim. Dacă platforma e pe loc, echipa o cunoaște și workload-ul se potrivește, „folosește ce ai” e decizia corectă.

Transformarea implică lucruri pe care SQL nu le exprimă bine. Folosirea grea de UDF-uri, integrarea cu biblioteci ML Python, operații complexe pe grafuri sau array-uri: API-ul DataFrame al Spark e mai expresiv decât SQL-ul de warehouse pentru aceste cazuri.

Când să NU apelezi la Spark.

Datele încap pe o singură mașină. Un singur server cu 64 de core-uri și 512 GB procesează sute de gigabytes de Parquet mai rapid decât un cluster Spark, cu un binar și un operator (tu). DuckDB sau Polars e răspunsul corect aici. Pragul a urcat în fiecare an pe măsură ce RAM-ul devine mai ieftin și DuckDB devine mai rapid, iar multe workload-uri „aveam nevoie de Spark” din 2018 sunt acum workload-uri single-machine.

Munca se poate exprima ca SQL pe un warehouse gestionat. Dacă datele sunt deja în Snowflake sau BigQuery, iar transformarea e SQL, motorul warehouse e mai rapid, mai ieftin și dramatic mai simplu de operat decât pornirea unui job Spark care să citească, să transforme și să scrie înapoi. Lecția 33 a acoperit de ce ăsta e tiparul dominant.

E necesară latență real-time, sub-secundă. Spark Structured Streaming și-a îmbunătățit substanțial performanța la latență mică față de modelul original micro-batch, dar Flink e încă alegerea dominantă pentru procesare cu adevărat eveniment-cu-eveniment cu bugete de milisecunde. Modulul 6 parcurge acea decizie în detaliu.

Dacă alegi Spark pentru platforma ta și vrei să înveți motorul, site-ul ăsta are un curs PySpark dedicat la /programming/courses/pyspark/ care intră mai în profunzime în experiența efectivă de scriere a joburilor: dimensionarea cluster-ului, tuning-ul partițiilor, comportamentul shuffle-ului, strategiile de join pe care le alege optimizatorul și modurile în care joburile Spark de producție merg prost. Lecția asta acoperă decizia arhitecturală; cursul PySpark acoperă meseria de zi cu zi.

Încheierea lecției

Spark stă în mijlocul peisajului batch modern. Nu mai e implicitul pentru workload-uri mici (DuckDB) și nu mai e implicitul pentru workload-uri în formă de warehouse (Snowflake sau BigQuery), dar e încă implicitul pentru batch cu adevărat mare și pentru batch-și-streaming unificate pe același motor. Cadrul pe care restul Modulului 5 îl va folosi e că Spark e unul dintre mai multe motoare specializate, fiecare câștigându-și locul împotriva alternativelor. Lecția 36 se întoarce la întrebarea cum sunt așezate datele pe stocarea de sub toate aceste motoare: formatele de fișier, formatele de tabel și războaiele formatelor de tabel deschise care au produs Delta, Iceberg și Hudi.

Citări și lecturi suplimentare

  • Matei Zaharia et al., “Resilient Distributed Datasets: A Fault-Tolerant Abstraction for In-Memory Cluster Computing”, NSDI 2012, https://www.usenix.org/conference/nsdi12/technical-sessions/presentation/zaharia (consultat 2026-05-01).
  • Apache Spark documentation, https://spark.apache.org/docs/latest/ (consultat 2026-05-01).
  • Databricks documentation, https://docs.databricks.com/ (consultat 2026-05-01).
  • DuckDB documentation, https://duckdb.org/docs/ (consultat 2026-05-01). Merită citită pagina „Why DuckDB” pentru deciziile de design.
  • Polars user guide, https://docs.pola.rs/ (consultat 2026-05-01).
  • Trino documentation, https://trino.io/docs/current/ (consultat 2026-05-01).
  • “Spark: The Definitive Guide” (Bill Chambers și Matei Zaharia, O’Reilly, 2018). Ușor depășit, dar încă cea mai clară referință Spark cap-coadă.
Caută