Bine ai venit la prima lecție din cursul de PySpark. Dacă ai lucrat cu date de ceva vreme, ai văzut același lucru întâmplându-se la o jumătate de duzină de colegi: deschid un CSV în Pandas, kernel-ul mestecă 16 GB de RAM, ventilatorul laptopului începe să sune ca un avion mic, kernel-ul moare, iar ei postează pe Slack întrebând dacă nu cumva e momentul să „folosească Spark”. Acel moment, cel în care Pandas își pierde răbdarea și e nevoie de un ciocan mai mare, e despre ce e cursul ăsta. Șaizeci de lecții, începând cu teorie și terminând cu tine reglând job-uri reale de producție pe object storage. Până la final vei ști ce e Spark, când să apelezi la el, când să nu apelezi la el, și cum să scrii PySpark care nu te face de râs la code review.
Înainte să atingem însă vreun pic de cod, trebuie să vorbim despre expresia care a pus Spark pe hartă în primul rând: big data. Termenul a fost atât de bine schingiuit de departamentele de marketing din 2012 încoace încât majoritatea inginerilor cu care lucrez au un mic reflex de tresărire când îl aud. E corect. Cele mai multe lucruri vândute drept „big data” se dovedesc a fi un CSV de 4 GB pe care cineva a uitat să-i pună un dtype=. Dar ideea de bază e reală, problemele de inginerie sunt reale, iar motivul pentru care Spark există e real. Așa că hai să ne definim termenii cu adevărat.
Ce înseamnă cu adevărat „mare”
Big data înseamnă date care nu încap, sau nu se procesează, pe un singur calculator într-un timp rezonabil. Atât. Nu există un număr magic de gigabyți sau rânduri peste care datele devin Mari cu „M” mare. E o relație între trei lucruri: cât de multe date ai, ce încerci să faci cu ele și ce hardware ai la dispoziție.
Un fișier Parquet de 200 GB nu e big data pe o instanță EC2 cu 96 de nuclee, 768 GB de RAM și un SSD NVMe rapid. Poți trage tot fișierul în memorie și gata. Același fișier de 200 GB este big data pe un MacBook cu 16 GB de RAM, unde nu poți nici măcar să-l deschizi fără să faci paging pe disc și să blochezi tot. Aceleași date, alt „mare”.
Definiția clasică, cea pe care managerul tău probabil a învățat-o dintr-un slide McKinsey pe la 2014, sunt cei patru V: Volume, Velocity, Variety și Veracity. Ar trebui să știi ce sunt fiindcă cineva ți-i va cita la un interviu, dar ar trebui să știi și că doi dintre ei sunt reali și doi sunt acolo mai mult ca să umple un slide.
Volume înseamnă cât de mult. Acesta contează. Cantitatea de date pe care o procesezi schimbă cu adevărat ce unelte poți folosi. Cinci gigabyți sunt o problemă de Pandas. Cinci terabyți sunt o problemă de Spark. Cinci petabyți sunt o problemă de „trebuie să discutăm despre data warehouse-ul tău”. Volume e V-ul care te trezește la 3 dimineața când un job rămâne fără spațiu pe disc.
Velocity e cât de repede sosesc. Și acesta contează, mai ales în 2026 când streaming-ul a devenit implicit, nu excepția. Un job care procesează un dump zilnic static e o formă de problemă; un job care trebuie să țină pasul cu 100.000 de evenimente pe secundă dintr-un topic Kafka e o formă fundamental diferită. Spark are Structured Streaming, îl vom acoperi în modulul 9, și există tocmai pentru că batch-ul singur a încetat să mai fie suficient acum vreo zece ani.
Variety e cât de diferite sunt formatele: JSON aici, Parquet acolo, un export SQL dincolo, un feed XML pentru că furnizorul de plăți al cuiva e blocat în 2009. E real, dar nu e chiar o dificultate tehnică așa cum sunt Volume și Velocity. E mai mult o problemă de „trebuie să scrii mai multe parsere”, iar orice motor distribuit rezonabil te lasă să atașezi un connector sau să scrii un UDF. Variety e pe listă fiindcă face un slide aliterativ frumos.
Veracity e cât de demne de încredere sunt datele. Cu tot respectul, asta nu e o proprietate a datelor; e o proprietate a pipeline-ului de date. Dacă sistemul tău CRM produce gunoi, nicio cantitate de Spark, Snowflake sau geometrie sacră nu va repara asta. Calitatea datelor e o problemă reală, dar e o problemă de proces și tooling, nu o problemă de „am nevoie de un motor distribuit”. Faptul că acest V există se datorează în principal cuiva care avea nevoie de un al patrulea V ca să rotunjească regula de trei într-o regulă de patru.
Așa că versiunea sinceră: Volume și Velocity sunt motivul pentru care apelezi la calcul distribuit. Variety și Veracity sunt motivul pentru care ai un job după ce ai apelat la el.
Pragurile, aproximativ
Iată regulile empirice pe care le folosesc, și pe care majoritatea data engineer-ilor în care am încredere ți le vor cita înapoi dacă-i întrebi la prânz. Niciuna dintre ele nu e o lege a fizicii; se schimbă cu hardware-ul, cu prețul cloud-ului, cu cum arată datele tale, și cu cât de răbdător ești. Dar sunt o primă tăietură utilă.
Până la ~10 GB: Pandas e ok. Polars e mai rapid și folosește mai puțină memorie, și probabil ar trebui să-l folosești în schimb, dar Pandas funcționează. DuckDB e ok. SQLite e ok. Un Postgres pe un singur nod e ok. Niciuna dintre acestea nu e o problemă de Spark. Dacă cineva apelează la un motor distribuit ca să proceseze un CSV de 4 GB, e pe cale să petreacă trei zile scriind infrastructură pentru ceva ce un df = pd.read_csv(...) ar fi rezolvat în zece minute.
~10 GB până la ~100 GB: mijlocul incomod. Un laptop modern cu 32 sau 64 GB de RAM poate gestiona tehnic asta în Pandas dacă ești atent la dtype-uri și nu te deranjează ca mașina ta să fie inutilizabilă cât timp rulează. Polars o gestionează grațios. DuckDB o gestionează strălucit, e construit special pentru exact acest interval. Un VM de cloud robust o gestionează fără să transpire. Poți folosi Spark aici și va funcționa, dar plătești taxa pe sistemele distribuite (timp de pornire a clusterului, overhead de shuffle, debugging mai complicat) pentru un beneficiu nu prea mare. E intervalul în care răspunsul corect e tot mai des „DuckDB pe un nod gras” și unde marketing-ul Spark s-a vândut istoric peste valoare.
~100 GB până la ~1 TB: aici Spark începe să-și câștige pâinea. Încă poți îndesa asta pe o singură mașină robustă dacă ai buget pentru ea, iar unelte ca DuckDB vor ține pasul până la un punct, dar paralelismul pe mai multe mașini începe să fie cu adevărat mai rapid, mai ales pentru join-urile pe tabele late și agregările care apar în warehouse-uri reale. Spark e un default perfect rezonabil în acest interval. La fel e Snowflake. La fel e BigQuery. Alege în funcție de ce operează deja echipa ta.
~1 TB și mai sus: o singură mașină e fără speranță sau, cel puțin, catastrofal de scumpă. Un motor distribuit e singurul răspuns sănătos. Spark, Trino, Snowflake, BigQuery, Athena, Synapse: produse diferite, aceeași formă fundamentală: împarte datele pe multe mașini, procesează în paralel, combină rezultatele. Acesta e terenul de acasă al cursului ăsta.
~100 TB și mai sus: ești acum în teritoriul în care problemele interesante nu sunt „pot rula query-ul ăsta”, ci „pot rula query-ul ăsta fără să dau firma în faliment”. Optimizarea costurilor, layout-ul datelor, partition pruning, predicate pushdown, broadcast join-uri, tot materialul din a doua jumătate a cursului, devine job-ul propriu-zis. Oricine poate rula un SELECT * pe un petabyte. Să-l rulezi pentru 4€ în loc de 4.000€ e abilitatea.
De ce paralelismul e greu
Răspunsul naiv la „datele mele nu încap pe o singură mașină” e „folosește mai multe mașini”. Răspunsul tehnic e că asta e aproximativ corect, dar enorm mai complicat decât pare. Motivul pentru care motoarele de calcul distribuit există ca un domeniu propriu de inginerie e că toate cele de mai jos trebuie să funcționeze, și trebuie să funcționeze sub eșec, pentru ca abstracția să fie utilă.
Împărțirea datelor. Trebuie să decizi, pentru orice job dat, cum să tai input-ul în bucăți suficient de mici încât să încapă pe worker-i individuali. Sună trivial până îți amintești că datele trăiesc în S3, sau HDFS, sau Kafka, sau toate trei deodată, iar bucățile trebuie să se alinieze cu modul în care e organizat sistemul de stocare însuși. Spark cheltuiește o fracțiune semnificativă din codul său sursă pe figura de a împărți input-urile în „partiții”.
Coordonarea muncii. Odată ce ai o sută de worker-i, cineva trebuie să le spună fiecăruia ce să facă, în ce ordine, și ce să facă atunci când unul dintre ei durează de zece ori mai mult decât ceilalți (temutul „straggler”). Acel cineva e un scheduler, iar a scrie unul care funcționează la scară e o mică carieră. Procesul driver al Spark e exact asta.
Shuffle-ul. Cele mai multe calcule netriviale, join-uri, group-by-uri, sortări, necesită ca datele să fie mutate între mașini astfel încât toate înregistrările cu aceeași cheie să ajungă pe același worker. Asta se numește shuffle și e cea mai costisitoare operație într-un motor distribuit. Vom petrece multe lecții mai târziu în curs pe cum să eviți shuffle-urile, să minimizezi shuffle-urile și să supraviețuiești shuffle-urilor pe care nu le poți evita.
Combinarea rezultatelor. După ce toți acei worker-i și-au făcut bucata, cineva trebuie să asambleze răspunsul final. Uneori e trivial (concatenarea partițiilor). Alteori nu (o sortare globală pe o sută de mașini e un algoritm cu adevărat interesant).
Gestionarea eșecurilor. La scară, eșecul nu e un caz limită; e o condiție de stare staționară. Cu 1.000 de worker-i rulând timp de o oră, unii dintre ei vor eșua: discuri proaste, hopuri de rețea, instanțe spot recuperate de cloud, OOM-uri de la o cheie distorsionată, ce vrei tu. Framework-ul trebuie să detecteze eșecurile, să reia munca pierdută și să continue fără să repornească tot job-ul. Acesta e cel mai mare motiv unic pentru care nu scrii job-uri distribuite de la zero în Python. E și motivul pentru care un job Spark care „ar trebui să dureze 10 minute” durează uneori 40: undeva un worker a murit și trei stage-uri au fost reluate.
Un motor de calcul distribuit e, în esență, o bucată de software care ascunde toate cele de mai sus în spatele unui API care arată ca și cum ai lucra cu un singur DataFrame mare. Întreaga propunere a Spark e că scrii cod ca și cum ai avea o singură mașină cu RAM infinită, iar motorul gestionează în liniște împărțirea, planificarea, shuffle-ul, combinarea și recuperarea pentru tine. Când funcționează, e magic. Când nu, petreci o săptămână citind log-uri de stage-uri și cuvintele „shuffle spill” intră în vocabularul tău zilnic.
Ce urmează
Următoarele cinci lecții sunt tot teorie, încă fără cod, asta începe în lecția 7 cu primul nostru SparkSession. Lecția 2 se uită la MapReduce și Hadoop, sistemul care a dovedit că procesarea distribuită poate fi făcută abordabilă pentru programatori normali, și modelul pe care Spark l-a moștenit și l-a îmbunătățit. Lecția 3 introduce Spark însuși: lucrarea Berkeley din 2010, abstracția de dataset în memorie, și ce înseamnă cu adevărat „de 100 de ori mai rapid decât Hadoop” odată ce citești textul mărunt. Lecția 4 e arhitectura: driver, executors, cluster manager și cum trece efectiv un job prin ele. Lecția 5 sunt abstracțiile de dataset: RDD-uri, DataFrame-uri, Dataset-uri, și pe care ar trebui să o folosești cu adevărat în 2026 (spoiler: DataFrame-uri). Lecția 6 închide modulul de teorie cu PySpark vs Scala vs SQL: trei feluri de a vorbi cu același motor, și când fiecare e alegerea corectă.
Apoi în modulul 2 instalăm Spark, scriem primul nostru job și nu mai privim niciodată cei patru V.
Pentru lecturi suplimentare, referința canonică pentru tot ce e în cursul ăsta e documentația Apache Spark. Pune-o la favorite acum; ne vom întoarce la ea.