Five questions, the same data
The canteen manager has five questions about the same 90 lakh orders: how many orders above ₹0, above ₹18, above ₹20, above ₹25, above ₹30. Riya writes them as five MapReduce jobs: 199 seconds. Kabir installs Apache Spark on the very same cluster, tells it to keep the data in memory after the first pass, and asks the same five questions: 35 seconds. Same HDFS, same YARN, same three workers.
So Spark is the new Hadoop? Should we throw HDFS and YARN away?
No. Look where Spark ran: its executors were YARN containers, and it read the file from HDFS. Spark replaced MapReduce, the processing engine. Storage and resource management stayed. Most "Hadoop clusters" today are HDFS + YARN running Spark, Hive on Tez, and a few old MapReduce jobs.
And the speed-up isn't magic. Spark builds the whole job as a graph, keeps intermediate data in memory instead of writing it to HDFS between steps, and reuses the same executors for every step. The more passes, the bigger the win.
Today we run the same job three ways on our cluster (MapReduce, Spark RDDs, Spark DataFrames), read Spark's plan to find the map, the combiner and the shuffle we already know, and measure where caching helps.
Apache Spark, and how it relates to Hadoop
Apache Spark is a distributed data processing engine that represents a computation as a directed acyclic graph (DAG) of transformations over partitioned datasets (RDDs, and the higher-level DataFrames and Datasets). Transformations are lazy: nothing runs until an action (such as collect or write) asks for a result. The DAG is cut into stages at shuffle boundaries; each stage runs as parallel tasks, one per partition, inside long-lived executor processes coordinated by a driver. Intermediate results can be kept in memory (caching/persistence), and lost partitions are recomputed from their lineage. For DataFrames and SQL, the Catalyst optimizer rewrites the plan (column pruning, predicate pushdown, partial aggregation) and the Tungsten engine executes it with compact binary memory layouts and generated code. Spark has no storage or cluster manager of its own: it commonly runs on Hadoop YARN and reads HDFS, or runs on Kubernetes or its standalone manager with cloud object storage.
Spark is a faster engine you can plug into a Hadoop cluster instead of MapReduce. You describe the whole job once; Spark plans it, runs it in executors that stay alive, and keeps data in memory between steps instead of writing it to disk after every step.
Words you'll meet in this lesson
From our lab (Spark 4.0.4 on our YARN cluster, 3 executors of 512–768 MB, 1 core each)
| Same answer | Spark RDD (map + reduceByKey) and DataFrame (groupBy) gave exactly the numbers of ItemRevenue.java in Lesson 8.1: chai 50170 orders, ₹752,550 … RDD job 7.4 s, DataFrame job 11.3 s |
|---|---|
| Start-up | spark-submit wall time 46 s: most of it is starting the YARN application and 3 executors, paid once per application |
| Five passes, no cache | 19.8, 15.6, 13.5, 13.6, 12.8 s = 75.3 s (each pass re-reads 140 MB from HDFS, but executors stay alive) |
| Five passes, cached | 21.4 s, then 4.2, 3.7, 3.3, 2.7 s = 35.3 s |
| Five MapReduce jobs | 39, 39, 39, 41, 41 s = 199 s (new ApplicationMaster and containers, HDFS read and write every time) |
Spark vs MapReduce at three levels
Beginner A kitchen that keeps the ingredients out
- MapReduce is a cook who puts every ingredient back in the cold store after each step, then fetches it again for the next step.
- Spark keeps what it will need again on the counter (memory), plans the whole recipe before starting, and keeps the same helpers (executors) for the whole meal.
- Both cook in the same kitchen: the store room is HDFS and the head chef handing out stoves is YARN.
Intermediate The same ideas, new names
- map/filter are the same functions. reduceByKey is map output + combiner + shuffle + reducer in one call; Spark combines on the map side automatically (the plan shows
partial_count). - The shuffle is called an Exchange in the plan;
hashpartitioning(item, 200)is the HashPartitioner from Lesson 9.2 with 200 partitions. - An input split becomes a partition (our 4.7 MB file: 2 partitions); a container runs an executor, which runs many tasks, one after another, without starting a new JVM.
- The MapReduce ApplicationMaster role is played by Spark's driver (with a small AM on YARN to request executors).
Advanced Where the speed comes from, and where it doesn't
- No HDFS between steps: a multi-stage job is one application; shuffle files stay on local disk and stage outputs are not written to HDFS with replication 3.
- Long-lived executors: no JVM and container start-up per task or per job; our 5 MapReduce jobs paid that 5 times.
- Caching + lineage: reuse data across passes; if an executor dies, only lost partitions are recomputed.
- Catalyst/Tungsten: read only needed columns (ReadSchema item, amount), aggregate in compact binary form, generate code.
- Limits: memory is finite (spills to disk), a single one-pass scan of a huge file is disk-bound for both engines, and a badly partitioned shuffle is slow in any engine.
Exam tip: For "Compare Hadoop MapReduce and Spark", say first that Spark is an engine that usually runs on Hadoop (YARN, HDFS), then compare: disk vs memory between steps, one job per step vs one DAG, JVM per task vs long-lived executors, re-run task vs lineage recomputation, Java API vs SQL/Python/Scala/R + streaming + ML. Give one number: 5 passes took 199 s with MapReduce and 35.3 s with cached Spark on the same cluster.
MapReduce and Spark side by side
| Aspect | Hadoop MapReduce | Apache Spark |
|---|---|---|
| Unit of work | One job = one map + one reduce phase | One application = a DAG of many stages |
| Between steps | Write to HDFS (replication 3), read again | Memory or local shuffle files |
| Processes | New JVM per task attempt (YarnChild) | Executors live for the whole application |
| Iterative work | Slow: every pass is a job (our 5 passes: 199 s) | Fast with cache (35.3 s) |
| Fault tolerance | Re-run the failed task from HDFS input | Recompute lost partitions from lineage |
| APIs | Java (Streaming for other languages) | SQL, Python, Scala, Java, R; DataFrames |
| Beyond batch | No | Structured Streaming, MLlib, GraphFrames |
| Memory need | Low; streams through data | Higher; spills to disk when short |
| Still used for | Old jobs, DistCp, HAR, simple huge scans | Most new batch, SQL, ML and streaming |
Architecture at a glance: one cluster, two engines
Left: a MapReduce job (Lesson 8.6). Right: a Spark application on the same YARN and HDFS. The yellow boxes are what each engine writes to HDFS between steps.
Animated example: a fraud model that needs ten passes
- 09:00: the fraud team's model needs ten passes over the same 30 days of transactions. Costs are in units from our lab's ratio (MapReduce 40 per pass, cached Spark about 3.5).
- MapReduce: every pass is a new job that starts containers, reads HDFS, computes and writes three copies of its output.
- Afternoon: ten passes cost about 400 units, mostly spent reading and writing, not computing.
- Spark: the first pass reads HDFS and caches the data in the executors (our lab: 21.4 s against about 40 s for one MapReduce pass).
- Passes 2–10 come from memory at about 3.5 units each; the executors never restart.
- Result: about 75 units instead of 400, roughly 5× faster, on the same cluster. (Illustrative scenario built on our measured ratio.)
Spark does not need YARN. It also runs on Kubernetes and on its own standalone manager, usually reading cloud object storage (S3, GCS, ADLS) instead of HDFS. On Hadoop clusters and on EMR, Dataproc and Cloudera it normally runs on YARN, exactly as in our lab.
The same job three ways, on our cluster
We unpacked spark-4.0.4-bin-hadoop3 in the namenode container and pointed it at our Hadoop configuration. No Spark daemons were started: YARN starts everything per application.
Submit to YARN
export SPARK_HOME=/tmp/opt/spark-4.0.4-bin-hadoop3 HADOOP_CONF_DIR=/opt/hadoop/etc/hadoop $SPARK_HOME/bin/spark-submit --master yarn --num-executors 3 --executor-memory 512m \ --executor-cores 1 --driver-memory 512m item_revenue_spark.py /user/hadoop/data/canteen_orders.csvspark-submit wall time 46 s (includes starting the YARN application and 3 executors)--master yarnmakes the client ask our ResourceManager for an application, exactly likehadoop jar. The driver runs here (client mode); the AM and 3 executors run in containers on the workers.The RDD way: MapReduce thinking
The same mapper and reducer as ItemRevenue.java, in four lines, with the same answers. Two partitions = two input splits of the 4.7 MB file..map(lambda line: line.split(",")) .filter(lambda f: len(f) == 3 and f[0] != "order_id") .map(lambda f: (f[1], (1, int(f[2])))) # map: (item, (1, amount)) .reduceByKey(lambda a, b: (a[0] + b[0], a[1] + b[1])) # combine + shuffle + reduceRDD chai 50170 752550 RDD dosa 49948 2247660 … RDD job took 7.4 s, partitions: 2The DataFrame way: SQL thinking
You say what, Catalyst decides how. It took longer here becausedf = spark.read.csv(path, header=True, inferSchema=True) df.groupBy("item").agg(F.count("*").alias("orders"), F.sum("amount").alias("revenue")).orderBy("item")| chai| 50170| 752550| | dosa| 49948|2247660| DataFrame job took 11.3 sinferSchemareads the file once just to guess types; with a declared schema it reads once.Read the plan: find MapReduce inside
Bottom to top: read only two columns, partially aggregate inside each map task (the combiner of Lesson 9.1), shuffle by hash of item (Lesson 9.2), finish the aggregate, then a second shuffle to sort globally.Sort [item ASC] +- Exchange rangepartitioning(item, 200) +- HashAggregate(keys=[item], functions=[count(1), sum(amount)]) ← reduce +- Exchange hashpartitioning(item, 200) ← shuffle +- HashAggregate(keys=[item], functions=[partial_count(1), partial_sum(amount)]) ← combiner +- FileScan csv [item, amount] ReadSchema: struct<item:string,amount:int> ← map + column pruningFive passes: no cache vs cache
Without cache Spark re-reads HDFS every pass, but executors stay alive, so it is already far faster than starting five jobs. Withno cache: 19.8, 15.6, 13.5, 13.6, 12.8 s = 75.3 s cache : 21.4, 4.2, 3.7, 3.3, 2.7 s = 35.3 spersist(MEMORY_AND_DISK)pass 1 costs a bit more (it fills the cache); later passes skip HDFS completely.The MapReduce way: five jobs
Each job starts an ApplicationMaster and new map and reduce JVMs, reads 140 MB from HDFS and writes its result with replication 3. Nothing is reused between jobs.for k in 1 2 3 4 5; do hadoop jar mr.jar ItemRevenue orders_x30.csv /out/iter$k; doneMapReduce pass 1: 39 s … pass 5: 41 s MapReduce: 5 passes took 199 s
Our numbers come from three workers sharing one laptop. Absolute times will differ on real hardware; the shape will not: the more passes over the same data, the bigger Spark's advantage. For a single pass over a huge file, both engines are limited by disks and the gap shrinks.
Spark on our YARN cluster
Six steps of our real runs: submit, the RDD job, the plan, five uncached passes, five cached passes, and five MapReduce jobs.
- spark-submit asks our ResourceManager for an application; 46 s wall time including start-up.
- The RDD job gives exactly the Lesson 8.1 numbers in 7.4 s.
- The DataFrame plan: scan two columns, partial aggregate, shuffle, final aggregate.
- Five passes without cache: 75.3 s; each pass re-reads HDFS.
- Five passes with cache: 35.3 s; passes 2–5 take 2.7–4.2 s.
- Five MapReduce jobs over the same file: 199 s.
- The driver asks YARN for an application master and three executor containers, which stay up for the whole application.
- Two partitions become two map tasks inside the executors; their combined output is shuffled to a reduce task.
- Catalyst prunes columns and aggregates before the shuffle, the same idea as a combiner.
- Without cache each pass re-reads the HDFS blocks, but no new JVMs are started.
- With cache, pass 1 stores the partitions in executor memory and later passes skip HDFS.
- MapReduce runs five separate jobs: five times the start-up, the HDFS read and the HDFS write.
Spark on Hadoop at work
Versions as of October 2026: Spark 4.2 (July 2026), 4.1 and 4.0 are current lines and 3.5 is the long-term line with security fixes until November 2027. Managed platforms often lag: Amazon EMR 7.14 ships Spark 3.5.8, Google Dataproc 2.3.37 ships Spark 3.5.3 and Cloudera Base 7.3.2 ships Spark 3.5 (sources below).
1. Fraud and risk models in fintech
Payment and lending companies train models on months of transactions with many passes over the same data; MLlib or Spark-prepared features with cached DataFrames replaced chains of MapReduce jobs.
2. Rewriting nightly ETL in telecom
Operators that once ran call-record summaries as dozens of chained MapReduce jobs rewrite them as one Spark SQL application, removing the HDFS writes between steps.
3. Near-real-time dashboards in e-commerce
During a festival sale, Spark Structured Streaming reads orders from Kafka in micro-batches and updates sales per city every minute: something MapReduce was never built for.
4. Interactive analysis in healthcare research
Researchers query de-identified hospital records with Spark SQL from notebooks; queries that took minutes as MapReduce return in seconds when tables are cached and stored as Parquet (Lesson 10.2).
5. Both engines side by side in government data platforms
Large public-sector clusters keep old, stable MapReduce jobs (and tools like DistCp) running while all new work is written in Spark on the same YARN queues; migration happens job by job.
Simplified; organisations differ in tools and policies. Sources: endoflife.date/apache-spark, Amazon EMR 7.x versions, Dataproc 2.3 versions, Cloudera Base 7.3.2.
A model of k passes, fitted to our runs
The per-pass times below are our measurements. The model predicts any number of passes; check that k = 5 gives back the lab totals (199 s, 75.3 s, 35.3 s). It leaves out Spark's one-time application start-up (about 46 s in our lab), which MapReduce pays inside every pass.
# Per-pass times measured on our lab (5 passes over 9 M orders, 140 MB)
MR_PASS = 199 / 5 # every pass is a new MapReduce job: start + read + write
NOCACHE_FIRST, NOCACHE_NEXT = 19.8, (15.6 + 13.5 + 13.6 + 12.8) / 4
CACHE_FIRST, CACHE_NEXT = 21.4, (4.2 + 3.7 + 3.3 + 2.7) / 4
def mapreduce(k):
return k * MR_PASS
def spark(k, cache):
if cache:
return CACHE_FIRST + (k - 1) * CACHE_NEXT
return NOCACHE_FIRST + (k - 1) * NOCACHE_NEXT
for k in [1, 5, 10, 20]:
mr, nc, c = mapreduce(k), spark(k, False), spark(k, True)
print(f"{k:>2} passes: MapReduce {mr:6.1f} s | Spark {nc:6.1f} s | Spark+cache {c:5.1f} s | {mr / c:4.1f}x")MR_PASS = 199 / 5 # every pass is a new MapReduce job: start + read + writeFive ItemRevenue jobs took 39, 39, 39, 41 and 41 s: about 40 s each, every time.CACHE_FIRST, CACHE_NEXT = 21.4, (4.2 + 3.7 + 3.3 + 2.7) / 4Pass 1 pays for reading and filling the cache; the next passes cost about 3.5 s.return CACHE_FIRST + (k - 1) * CACHE_NEXTThe cached line grows slowly, so the advantage grows with every extra pass.
Run it on your PC: Spark on the Module 8 cluster
Spark needs no daemons of its own here: unpack it on the namenode container, point HADOOP_CONF_DIR at the Hadoop configuration, and spark-submit --master yarn asks our ResourceManager for an ApplicationMaster and executors, just like hadoop jar. Executors of 512–768 MB fit our 2 GB NodeManagers. The outputs below are real; Spark's INFO/WARN log lines and the YARN status report are filtered out.
Item revenue: RDD and DataFrame
# item_revenue_spark.py: the same job as ItemRevenue.java (Lesson 8.1), in PySpark, twice:
# with the RDD API (map / reduceByKey, MapReduce thinking) and with DataFrames (SQL thinking).
import sys, time
from pyspark.sql import SparkSession, functions as F
spark = SparkSession.builder.appName("item revenue (spark)").getOrCreate()
path = sys.argv[1]
t = time.time()
rdd = (spark.sparkContext.textFile(path)
.map(lambda line: line.split(","))
.filter(lambda f: len(f) == 3 and f[0] != "order_id")
.map(lambda f: (f[1], (1, int(f[2])))) # map: (item, (1, amount))
.reduceByKey(lambda a, b: (a[0] + b[0], a[1] + b[1]))) # shuffle + reduce (with map-side combine)
for item, (n, total) in sorted(rdd.collect()):
print(f"RDD {item:9} {n:>8} {total:>10}")
print(f"RDD job took {time.time() - t:.1f} s, partitions: {rdd.getNumPartitions()}")
t = time.time()
df = spark.read.csv(path, header=True, inferSchema=True)
out = df.groupBy("item").agg(F.count("*").alias("orders"), F.sum("amount").alias("revenue")).orderBy("item")
out.show()
print(f"DataFrame job took {time.time() - t:.1f} s")
out.explain()
spark.stop()
# One-time setup on the namenode container of the Module 8 lab (no Spark daemons needed):
# curl -O https://dlcdn.apache.org/spark/spark-4.0.4/spark-4.0.4-bin-hadoop3.tgz
# mkdir -p /tmp/opt && tar xzf spark-4.0.4-bin-hadoop3.tgz -C /tmp/opt
# export SPARK_HOME=/tmp/opt/spark-4.0.4-bin-hadoop3 HADOOP_CONF_DIR=/opt/hadoop/etc/hadoop
# $SPARK_HOME/bin/spark-submit --master yarn --num-executors 3 --executor-memory 512m \
# --executor-cores 1 --driver-memory 512m item_revenue_spark.py /user/hadoop/data/canteen_orders.csvSame numbers as ItemRevenue.java (Lesson 8.1). The DataFrame is slower here because inferSchema reads the file once more to guess the column types.
Five passes, without and with cache
# iterative_spark.py: five passes over the same 140 MB (9 million orders).
# Pass k counts orders per item with amount >= threshold k. With cache(), only pass 1 reads HDFS.
import sys, time
from pyspark import StorageLevel
from pyspark.sql import SparkSession
spark = SparkSession.builder.appName("five passes (spark)").getOrCreate()
cache = sys.argv[2] == "cache"
orders = (spark.sparkContext.textFile(sys.argv[1])
.map(lambda l: l.split(","))
.filter(lambda f: f[0] != "order_id")
.map(lambda f: (f[1], int(f[2]))))
if cache:
orders = orders.persist(StorageLevel.MEMORY_AND_DISK)
total = time.time()
for k, threshold in enumerate([0, 18, 20, 25, 30], 1):
t = time.time()
counts = orders.filter(lambda p, th=threshold: p[1] >= th).map(lambda p: (p[0], 1)).reduceByKey(lambda a, b: a + b).collect()
print(f"pass {k}: amount >= {threshold:2}: {sum(c for _, c in counts):>9,} orders, {time.time() - t:5.1f} s")
print(f"cache={cache}: 5 passes took {time.time() - total:.1f} s")
spark.stop()
# One-time setup on the namenode container of the Module 8 lab (no Spark daemons needed):
# curl -O https://dlcdn.apache.org/spark/spark-4.0.4/spark-4.0.4-bin-hadoop3.tgz
# mkdir -p /tmp/opt && tar xzf spark-4.0.4-bin-hadoop3.tgz -C /tmp/opt
# export SPARK_HOME=/tmp/opt/spark-4.0.4-bin-hadoop3 HADOOP_CONF_DIR=/opt/hadoop/etc/hadoop
# for c in nocache cache; do
# $SPARK_HOME/bin/spark-submit --master yarn --num-executors 3 --executor-memory 768m \
# --executor-cores 1 --driver-memory 512m iterative_spark.py /user/hadoop/data/orders_x30.csv $c
# doneorders_x30.csv is the 140 MB, 9 million order file from Lesson 8.1. Timings vary from run to run; the pattern does not.
The same five passes as MapReduce jobs
# iterative_mr.sh: the MapReduce way - every pass is a new job that reads HDFS again
cd /tmp/mr; total=$(date +%s)
for k in 1 2 3 4 5; do
s=$(date +%s); hdfs dfs -rm -r -f -skipTrash /out/iter$k > /dev/null 2>&1
hadoop jar mr.jar ItemRevenue /user/hadoop/data/orders_x30.csv /out/iter$k > /dev/null 2>&1
echo "MapReduce pass $k: $(( $(date +%s) - s )) s"
done
echo "MapReduce: 5 passes took $(( $(date +%s) - total )) s"
# Run inside the namenode container: bash iterative_mr.shEach pass runs ItemRevenue over the whole file as a new job: new ApplicationMaster, new map and reduce JVMs, a full HDFS read and an HDFS write.
Lazy transformations, one action
Spark records transformations and runs them only when an action asks for a result. Watch the plan grow without any data moving, then the action run it all at once.
Worked examples: real problems, solved and traced in memory
Four decisions engineers face when choosing between MapReduce and Spark, worked with our measured numbers.
Ten passes for a fraud model
Problem statement
A UPI fraud team needs 10 passes over the same data. Using our lab's per-pass times, how long would MapReduce and cached Spark take, and how does Spark's one-time 46 s start-up change the answer?
MR 39.8 s/pass · cache 21.4 s + 3.475 s/pass · start-up 46 s
MapReduce 398 s, Spark+cache 52.7 s, with start-up 98.7 s (4.0x)
Approach
- MapReduce: passes × 39.8 s.
- Spark: first pass + 9 cheap passes.
- Add Spark's start-up once and compare again.
Solution: run it live
What happens in memory, step by step
Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.
Will the data fit in the cache?
Problem statement
A telecom company wants to cache 2 TB of call records (CSV) for a 10-pass analysis. It has 100 executors of 16 GB. Spark's unified memory is (heap − 300 MB) × 0.6, shared by cache and execution. Stored as a columnar cache, the data is roughly the size of Parquet (our Lesson 10.2 ratio: 4.4 MB Parquet for 19.4 MB CSV). Does it fit?
2 TB CSV · 100 × 16 GB executors · ratio 4.4/19.4
memory 942 GB, data cached ~464 GB, fits: True
Approach
- Usable memory per executor = (16,384 − 300) × 0.6 MB.
- Total = per executor × 100.
- Cached size ≈ 2 TB × Parquet ratio.
Solution: run it live
What happens in memory, step by step
Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.
Which engine for which job?
Problem statement
An e-commerce platform has three workloads: (a) a nightly one-pass copy of 50 TB of logs into an archive on a memory-poor old cluster, (b) a sales dashboard refreshed every minute from Kafka, (c) a recommendation model with 30 passes. Pick an engine for each.
3 workloads
a: one-pass 50 TB copy -> MapReduce/DistCp is fine b: dashboard every minute -> Spark Structured Streaming c: 30-pass model -> Spark + cache
Approach
- One pass, no reuse, little memory: MapReduce (or DistCp) is fine.
- Streaming: Spark Structured Streaming.
- Many passes: Spark with cache.
Solution: run it live
What happens in memory, step by step
Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.
Is rewriting 400 MapReduce jobs worth it?
Problem statement
An insurer runs 400 MapReduce jobs nightly, using 6,000 container-hours a night. Rewriting a job in Spark SQL takes 2 developer-days (₹8,000 per day). Assume the rewritten jobs use 55% fewer container-hours, and a container-hour costs ₹4 (illustrative). How long until the rewrite pays back?
400 jobs · 2 days × ₹8,000 · 6,000 container-hours/night · −55% · ₹4/hour
rewrite Rs 6,400,000, saves Rs 13,200 a night, payback 485 nights
Approach
- Cost = jobs × days × daily rate.
- Saving per night = 6,000 × 0.55 × ₹4.
- Payback = cost ÷ saving per night.
Solution: run it live
What happens in memory, step by step
Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.
Check yourself
Quick revision
Lesson 13.2 in one screen
- Spark is a DAG engine with lazy transformations, actions, long-lived executors, a driver, caching and lineage; Catalyst and Tungsten optimize DataFrames and SQL.
- Spark usually runs on Hadoop: YARN containers for executors, HDFS for data. It replaced MapReduce, not HDFS or YARN.
- Mapping: map/filter = map, reduceByKey = combiner + shuffle + reduce, Exchange = shuffle, partition = split, executor = container.
- Lab: same answers as Lesson 8.1; RDD 7.4 s, DataFrame 11.3 s; spark-submit 46 s including start-up.
- Lab, 5 passes over 9 M orders: MapReduce 199 s; Spark 75.3 s; Spark + cache 35.3 s (passes 2–5: 2.7–4.2 s).
- Spark wins most on iterative, interactive and streaming work; a single huge scan is disk-bound for both.
- Oct 2026: Spark 4.2/4.1/4.0 current, 3.5 LTS to Nov 2027; EMR 7.14, Dataproc 2.3 and Cloudera 7.3.2 ship Spark 3.5.
Interview and viva questions
Is Spark a replacement for Hadoop?
Spark replaces Hadoop MapReduce as the processing engine, not Hadoop as a whole. Spark has no storage or resource manager of its own, so on most Hadoop clusters it runs on YARN and reads HDFS. It can also run on Kubernetes or its standalone manager with cloud object storage, which is how many new platforms use it.
Why is Spark faster than MapReduce?
Spark plans a whole multi-step job as one DAG, keeps intermediate data in memory or on local disk instead of writing it to HDFS between steps, reuses long-lived executors instead of starting a JVM per task, can cache datasets across passes, and optimizes DataFrame/SQL plans with Catalyst and Tungsten. In our lab, five passes took 199 s with MapReduce and 35.3 s with cached Spark.
Is MapReduce still used?
Yes, but mostly for existing jobs and tools built on it, such as DistCp and Hadoop Archive. New batch, SQL, machine learning and streaming work is usually written in Spark (or Hive on Tez, Flink, Trino), often running on the same YARN cluster as the old MapReduce jobs.
What is the difference between an RDD and a DataFrame?
An RDD is a distributed collection of objects transformed with functions you write; Spark cannot see inside those functions. A DataFrame is a table with a schema, written with SQL or column expressions, which the Catalyst optimizer can rewrite (for example reading only needed columns and aggregating before the shuffle). DataFrames are the recommended API for most work.
Does Spark need more memory than MapReduce?
Usually yes. Executors are long-lived and use memory for caching and for shuffles and joins; when memory runs short Spark spills to disk, and badly sized executors can fail with out-of-memory errors. MapReduce streams data through short-lived tasks and needs less memory, at the cost of disk I/O between steps.
Practice problems
Exercises using the real MapReduce and Spark numbers from our cluster.
Speed-ups
Compute how many times faster cached Spark was than MapReduce, and than uncached Spark, for 5 passes.
199 s · 75.3 s · 35.3 s
vs MapReduce 5.6x, vs uncached Spark 2.1x
Show solution and explanation
print(f"vs MapReduce {199 / 35.3:.1f}x, vs uncached Spark {75.3 / 35.3:.1f}x")How it works: About 5.6× and 2.1×. Part of the first gap is the HDFS write and job start-up in every MapReduce pass, the rest is caching.
Cost of the first pass
What share of the cached run's 35.3 s was spent in pass 1?
21.4 s of 35.3 s
61%
Show solution and explanation
print(f"{21.4 / 35.3:.0%}")How it works: About 61%: reading HDFS and filling the cache dominate a short run.
Same answer?
Check that Spark's chai revenue equals the MapReduce result from Lesson 8.1 and that the price works out.
50170 orders, 752550
True
Show solution and explanation
print(752550 == 50170 * 15)How it works: True: every chai costs ₹15 in this dataset, and both engines agree.
When does caching pay?
The cached run's first pass costs 1.6 s more than the uncached one, but every later pass saves about 10.4 s. From how many passes is caching worth it?
21.4 vs 19.8 · 3.475 vs 13.875
1 passes: cache 21.4 no cache 19.8 2 passes: cache 24.9 no cache 33.7 3 passes: cache 28.3 no cache 47.5
Show solution and explanation
for k in range(1, 4):
print(k, "passes: cache", round(21.4 + (k - 1) * 3.475, 1), "no cache", round(19.8 + (k - 1) * 13.875, 1))How it works: From 2 passes on. With a single pass, caching only costs time.
Word count, Spark style
Simulate flatMap → map → reduceByKey on three lines.
3 lines of feedback
[('chai', 2), ('cold', 1), ('dosa', 1), ('hot', 1), ('late', 1), ('was', 3)]Show solution and explanation
lines = ["chai was hot", "dosa was cold", "chai was late"]
words = [w for line in lines for w in line.split()] # flatMap
pairs = [(w, 1) for w in words] # map
counts = {}
for w, n in pairs: # reduceByKey
counts[w] = counts.get(w, 0) + n
print(sorted(counts.items()))How it works: The same three steps as Lesson 8.6's WordCount.java, written as one chain in Spark: lines.flatMap(split).map(lambda w: (w, 1)).reduceByKey(add).
Partitions from blocks
textFile makes about one partition per HDFS block. How many partitions do 140 MB and 4.7 MB files get with 128 MB blocks?
140,759,640 and 4,692,009 bytes
140759640 -> 2 partitions 4692009 -> 2 partitions
Show solution and explanation
import math
for size in [140_759_640, 4_692_009]:
print(size, "->", max(2, math.ceil(size / (128 * 1024 * 1024))), "partitions")How it works: Both get 2 (textFile asks for at least 2 partitions by default), matching "partitions: 2" in our RDD run.
Cluster time saved per day
A bank runs 120 iterative jobs a day, 10 passes each. Using our per-pass numbers, how many cluster-hours a day does cached Spark save over MapReduce?
120 jobs × 10 passes
10.0 hours of job time saved per day
Show solution and explanation
mr = 10 * 39.8
spark = 21.4 + 9 * 3.475 + 46
print(f"{120 * (mr - spark) / 3600:.1f} hours of job time saved per day")How it works: About 10 hours of job time a day on our lab's scale (illustrative); on real clusters, multiply by the containers each job holds.
Recompute from lineage
Three cached partitions; executor 2 dies and loses partition 1. Recompute only that partition from its lineage and check the total is unchanged.
3 partitions, lineage = filter ≥ 18 then double
recomputed 1 partition; total same: True
Show solution and explanation
source = [[15, 18, 20], [25, 30, 45], [18, 80, 15]]
lineage = lambda part: [x * 2 for x in part if x >= 18]
cache = [lineage(p) for p in source]
before = sum(map(sum, cache))
cache[1] = None # executor lost
recomputed = 0
for i, p in enumerate(cache):
if p is None:
cache[i] = lineage(source[i]); recomputed += 1
print("recomputed", recomputed, "partition; total same:", sum(map(sum, cache)) == before)How it works: Only the lost partition is rebuilt, from its source block and its recipe. MapReduce instead re-runs the failed task from its HDFS input.
Real-world scenario questions (asked in company interviews)
Hadoop vs Spark is asked in almost every big data interview, often as a trap: the best answers start by saying Spark usually runs on Hadoop.
"Hadoop or Spark: which is better?"
An opening question in a data engineering interview.
The interviewer is checking: that you know they are not alternatives.
Show a strong answer
- Hadoop is storage (HDFS) + resources (YARN) + an engine (MapReduce); Spark is an engine, commonly running on YARN and HDFS.
- As engines, Spark is faster for multi-step, iterative and interactive work: memory between steps, long-lived executors, Catalyst.
- Example: on one cluster, five passes took 199 s as MapReduce jobs and 35 s with cached Spark.
"Explain this Spark plan in MapReduce terms"
The interviewer shows FileScan → HashAggregate(partial) → Exchange → HashAggregate.
The interviewer is checking: real understanding of shuffles.
Show a strong answer
- FileScan with a narrow ReadSchema is the map input with column pruning.
- The partial aggregate is a combiner inside each map task; Exchange hashpartitioning is the shuffle with a hash partitioner.
- The final aggregate is the reducer; a stage boundary sits at each Exchange.
A Spark job that was fast in testing fails with executor OOM in production
The team cached a large DataFrame and joins it with another.
The interviewer is checking: memory sizing and options.
Show a strong answer
- Check executor memory vs cached size (Storage tab in the Spark UI), skewed keys, and shuffle partition counts.
- Use MEMORY_AND_DISK, cache only what is reused, cache columnar DataFrames rather than object RDDs, raise memory or partitions.
- Fix skew (salting, AQE skew join) and broadcast small tables (Lesson 9.6).
"Should we rewrite all our MapReduce jobs in Spark?"
A company has 400 stable MapReduce jobs on a Hadoop cluster.
The interviewer is checking: pragmatic migration thinking.
Show a strong answer
- Not all at once: rank jobs by cluster-hours and business value; rewrite the heaviest and the multi-step chains first.
- Run old and new side by side on the same YARN queues and compare outputs row by row before switching.
- Keep jobs that are one-pass, stable and cheap; write all new work in Spark SQL/DataFrames.