Riya writes MapReduce without Java
The canteen committee wants revenue per menu category: drinks, snacks, breakfast, meals. Kabir knows Python well but has never written a line of Java. Riya has the Java version from Lesson 9.4 working, but Kabir asks: “Can't I just write two small Python scripts and let Hadoop run them on the cluster?” He can. Hadoop ships a tool for exactly this, and it works with any program that reads lines and prints lines.
So Hadoop runs my Python script instead of a Java Mapper?
Alongside it. Each map task is still a Java process, but it starts your script as a child process, writes every input line to the script's standard input, and reads whatever the script prints. A line like meal⇥45 becomes the key meal and the value 45. That is Hadoop Streaming.
And the reducer gets all values of a key in a list, like in Java?
No, that's the one surprise. The reducer script gets plain lines, sorted by key, one after another. Your script has to notice when the key changes. Today we write both scripts, test them with an ordinary Linux pipe, run them on the cluster, and then break one on purpose to learn where Python errors hide.
Hadoop Streaming
Hadoop Streaming is a utility shipped with Hadoop (hadoop-streaming-*.jar, run with mapred streaming or hadoop jar) that lets any executable or script that reads standard input and writes standard output act as the mapper, combiner or reducer of a MapReduce job. Each map task launches the mapper program as a subprocess and pipes its input records to it as lines of text; each output line is split at the first tab character into a key and a value (the whole line is the key if there is no tab). After the shuffle and sort, each reduce task pipes the key-value lines of its partition, sorted by key, to the reducer program, which must detect the boundaries between keys itself. The program and any files it needs are shipped to the worker nodes with the -files option; its interpreter must already be installed on every NodeManager.
Write the map and the reduce as two small scripts that read lines and print key⇥value lines. Hadoop runs them on every node, sorts between them, and does everything else exactly as for a Java job.
Words you'll meet in this lesson
"python3 revenue_mapper.py".cat data | mapper | sort | reducer: the whole job on one machine.From our lab (Hadoop 3.5.0, HDFS + YARN, 3 workers)
| Streaming jar | /opt/hadoop/share/hadoop/tools/lib/hadoop-streaming-3.5.0.jar, run as mapred streaming |
|---|---|
| Local test | 1,000 lines through mapper | sort | reducer: breakfast 179, drink 377, meal 162, snack 282 orders |
| Cluster run | student_orders.csv (5 lakh orders): 3 splits, 3 maps, 500,000 map output records, 4 reduce groups, 24.5 s, total CPU 5,710 ms |
| Result | breakfast 90,110 orders ₹24,76,620 · drink 1,90,033 ₹30,52,315 · meal 80,219 ₹46,70,075 · snack 1,39,638 ₹26,52,992: identical to the Java counters job in Lesson 9.4 |
| A typo | Job FAILED with “PipeMapRed.waitOutputThreads(): subprocess failed with code 1”; the real cause, “NameError: name 'itme' is not defined”, was only in the task's stderr log |
Streaming at three levels
Beginner Two scripts and a pipe
- The mapper reads order lines and prints one line per order:
category⇥amount. - Hadoop collects those lines from all maps, sorts them by category and hands each reducer its share.
- The reducer reads the sorted lines, keeps a running total, and prints the total whenever the category changes, and once more at the end.
- On your laptop,
head -1000 orders.csv | python3 mapper.py | sort | python3 reducer.pydoes the same thing;sortplays the shuffle.
Intermediate What Hadoop does with your script
- Each task JVM (
YarnChild) starts your command as a child process in the task's working folder, where-filesplaced your scripts andmenu.csv. - Map input: with the default
TextInputFormat, only the line (the value) is written to stdin, not the byte offset. - Your output lines are split at the first tab into key and value, partitioned with the HashPartitioner and sorted, just like Java map output (Lesson 8.4).
- The reducer's stdin is
key⇥valuelines, grouped only in the sense that equal keys are next to each other. There is no Iterable of values. - If your process exits with a non-zero code, the attempt fails and is retried, up to 4 times, like any task (Lesson 8.6).
Advanced Options, limits and alternatives
-numReduceTasks 0gives a map-only job (filtering, format conversion);-combinertakes a script too.-D stream.num.map.output.key.fields=2makes the first two tab-separated fields the key, so you can sort on a composite key; withKeyFieldBasedPartitioneryou can partition on part of it (a Streaming-style secondary sort, Lesson 9.7).- Everything travels as text and every record crosses two pipes, so Streaming usually uses more CPU than the same job in Java. For the heavy work, Java (or Spark) wins.
- Libraries such as mrjob and Pydoop wrap Streaming for Python. Today most Python work on Hadoop data uses PySpark instead, but Streaming is still the quickest way to run an existing script, or a shell command like
grep, across a cluster.
Exam tip: For “What is Hadoop Streaming?”, say: a utility that lets any program reading stdin and writing stdout act as mapper or reducer; lines are split into key and value at the first tab; the reducer receives lines sorted by key and must detect key changes itself; files are shipped with -files. Then write the one-line command: mapred streaming -files m.py,r.py -input in -output out -mapper "python3 m.py" -reducer "python3 r.py".
Streaming options you will actually use
| Option | What it does | Example |
|---|---|---|
| -input / -output | HDFS input path(s) and the output folder (must not exist) | -input /user/hadoop/data/student_orders.csv -output /out/stream |
| -mapper / -reducer | The command to run for each task | -mapper "python3 revenue_mapper.py" |
| -combiner | A command run on map output before the shuffle | -combiner "python3 revenue_reducer.py" (only if the output format matches the input, see Lesson 9.1) |
| -files | Ships local files into each task's working folder | -files revenue_mapper.py,revenue_reducer.py,menu.csv |
| -numReduceTasks | Number of reducers; 0 means map-only | -numReduceTasks 0 |
| -D (generic, first) | Job settings, placed before the streaming options | -D mapreduce.job.name=revenue -D stream.num.map.output.key.fields=2 |
| -partitioner | A Java partitioner class | -partitioner org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner |
Streaming vs Java MapReduce vs PySpark
Java MapReduce
Typed keys and values, Writables, counters and comparators. Most control, most code. Lessons 8.1 to 9.7.
full APIHadoop Streaming
Any language, text lines through pipes. Quick to write and test; slower per record; you parse and format everything yourself.
any languagemrjob / Pydoop
Python libraries that generate the Streaming command (mrjob) or talk to Hadoop more directly (Pydoop). Less boilerplate.
Python helpersPySpark
The usual choice for new Python data work: DataFrames, SQL, in-memory stages. Runs on YARN too.
todayCounters work in Streaming too: a script that prints reporter:counter:Quality,BAD_LINES,1 to stderr increments a counter, and reporter:status:message updates the task status. Never print debugging text to stdout: it would become output records.
Revenue per category in Python, on the cluster
Run inside the namenode container of the Module 8 lab, with student_orders.csv (Lesson 9.1) in HDFS. The full script is streaming_lab.sh in “Run it on your PC”.
The mapper: one order in, one line out
The menu lookup makes this a map-side join (Lesson 9.6) for free:menu = {} with open("menu.csv") as f: # shipped with -files for line in f: item, category, price, counter = line.strip().split(",") menu[item] = category for line in sys.stdin: order_id, student, time, item, amount = line.strip().split(",") print(f"{menu.get(item, 'unknown')}\t{amount}")menu.csvlands in the task's working folder because of-files.The reducer: watch for the key to change
Two easy-to-miss lines: the finalcurrent, orders, total = None, 0, 0 for line in sys.stdin: category, amount = line.rstrip("\n").split("\t") if category != current: if current is not None: print(f"{current}\t{orders}\t{total}") current, orders, total = category, 0, 0 orders += 1 total += int(amount) if current is not None: print(f"{current}\t{orders}\t{total}")printafter the loop (otherwise the last category is lost) and theif current is not Noneguard (an empty partition must print nothing).Test with a Linux pipe first
179 + 377 + 162 + 282 = 1,000 orders.head -1000 student_orders.csv | python3 revenue_mapper.py | sort | python3 revenue_reducer.pybreakfast 179 4940 drink 377 6060 meal 162 9145 snack 282 5362sortstands in for the shuffle. If this works, the cluster run almost always works too.Run it on the cluster
Three map tasks each started amapred streaming -files revenue_mapper.py,revenue_reducer.py,menu.csv \ -input /user/hadoop/data/student_orders.csv -output /out/stream \ -mapper "python3 revenue_mapper.py" -reducer "python3 revenue_reducer.py"took 24512 ms number of splits:3 Job job_1792643020992_0045 completed successfully Launched map tasks=3 Map output records=500000 Reduce input groups=4 CPU time spent (ms)=5710python3process; the same counters as a Java job appear because the Java task around your script still does all the bookkeeping.Read the output and cross-check
Streaming output files are calledhdfs dfs -cat /out/stream/part-00000breakfast 90110 2476620 drink 190033 3052315 meal 80219 4670075 snack 139638 2652992part-00000(the older API's naming). Every number equals the Java counters job of Lesson 9.4: BREAKFAST 90,110 and ₹24,76,620, and so on.Break the mapper and find the error
The job log only says that the subprocess exited with code 1. Python's traceback went to the task's stderr, which YARN keeps in the container logs. Retrying cannot fix a typo: the failed attempts are retried (up to 4 by default) and fail the same way, so the job gives up.# a typo: itme instead of item mapred streaming -files broken_mapper.py,revenue_reducer.py,menu.csv … -mapper "python3 broken_mapper.py" … yarn logs -applicationId <app id> -log_files stderrError: java.lang.RuntimeException: PipeMapRed.waitOutputThreads(): subprocess failed with code 1 Job job_1792643020992_0046 failed with state FAILED due to: Task failed task_1792643020992_0046_m_000002 -- the Python error is in the task's stderr: NameError: name 'itme' is not defined Traceback (most recent call last):
Python (and every library your script imports) must be installed on every NodeManager, in the same version. A script that works on the edge node can fail on a worker with ModuleNotFoundError. Ship pure-Python helpers with -files or -archives, or use a container image that has them.
Lines through the pipes
Six steps of one Streaming job: ship the files, map through a Python process, sort, reduce through another Python process, and what a failure looks like.
- The command ships two scripts and the menu, and the client finds 3 splits.
- Three map tasks start; each launches its own Python mapper.
- Maps are done: 5 lakh category lines were printed by the scripts.
- The reducer script saw 4 categories; the job succeeds.
- Four output lines, identical to the Java job's totals.
- With a typo, the job only reports a failed subprocess; the Python error is in the task log.
- The NodeManager localizes the -files into each container's working folder.
- The Java map task writes every input line to Python's stdin and reads its stdout.
- Each output line is split at the first tab into key and value, then partitioned and sorted.
- The reduce task pipes sorted key⇥value lines to the reducer script, which prints one line per key.
- The reducer's printed lines become the job output in HDFS.
- If the script crashes, only its exit code reaches the job; its error text is in the container's stderr.
Streaming at work
1. Analysts who know Python, not Java
Data teams reuse existing Python parsing code to aggregate logs or clickstreams on Hadoop without learning the Java API.
2. Shell tools across the whole cluster
A map-only Streaming job with -mapper "grep ERROR" and -numReduceTasks 0 searches terabytes of logs in parallel, one block per task.
3. Running a model on every record
Teams ship a trained scikit-learn model with -files or -archives and score millions of records in a map-only job, before Spark made this routine.
4. Migration to PySpark
Older Streaming pipelines are commonly rewritten in PySpark: the map logic becomes a DataFrame transformation and the key-change reducer becomes a groupBy().agg().
Simplified; organisations differ in tools and policies.
Simulate a Streaming job: mapper, sort, reducer
The mapper and reducer below are the lab scripts turned into functions that take and return lines. sorted() plays the shuffle, exactly as sort does in the local pipe test.
menu = {"chai": "drink", "coffee": "drink", "samosa": "snack", "vada pav": "snack",
"poha": "breakfast", "idli": "breakfast", "dosa": "meal", "thali": "meal"}
orders = ["1,S1413,2026-09-14 15:30,dosa,45", "2,S0973,2026-09-26 20:11,samosa,18",
"3,S0007,2026-09-01 13:37,samosa,18", "4,S0001,2026-09-02 08:10,chai,15",
"5,S0002,2026-09-02 13:05,thali,80", "6,S0001,2026-09-03 08:20,poha,25"]
def mapper(lines): # stdin lines in, "key\tvalue" lines out
out = []
for line in lines:
oid, student, time, item, amount = line.split(",")
out.append(f"{menu.get(item, 'unknown')}\t{amount}")
return out
def reducer(lines): # SORTED lines in; no list of values per key
out, current, orders, total = [], None, 0, 0
for line in lines:
key, value = line.split("\t")
if key != current:
if current is not None:
out.append(f"{current}\t{orders}\t{total}")
current, orders, total = key, 0, 0
orders += 1
total += int(value)
if current is not None:
out.append(f"{current}\t{orders}\t{total}")
return out
mapped = mapper(orders)
print("map output :", mapped)
shuffled = sorted(mapped) # the shuffle and sort
print("sorted :", shuffled)
for line in reducer(shuffled):
print("reduce :", line)out.append(f"{menu.get(item, 'unknown')}\t{amount}")Everything is text. The tab separates the key (category) from the value (amount), exactly what Hadoop splits on.if key != current:The reducer only ever sees one line at a time, so it must notice where one key ends and the next begins.if current is not None:After the loop the last key is still open: forget this and the last category silently disappears.
Run it on your PC: Streaming on the Module 8 lab
Use the Module 8 lab cluster (Lesson 8.1). Copy the two scripts, menu.csv and student_orders.csv (from make_student_orders.py, Lesson 9.1) into /tmp/mr9 in the namenode container. The workers already have python3, because the apache/hadoop image includes it; on a real cluster check every NodeManager.
The mapper
#!/usr/bin/env python3
# revenue_mapper.py: Hadoop Streaming mapper. Reads order lines on stdin, writes "category<TAB>amount".
import sys
menu = {}
with open("menu.csv") as f: # shipped with -files, appears in the task's working folder
for line in f:
item, category, price, counter = line.strip().split(",")
menu[item] = category
for line in sys.stdin:
order_id, student, time, item, amount = line.strip().split(",")
print(f"{menu.get(item, 'unknown')}\t{amount}")
# Test alone: head -3 student_orders.csv | python3 revenue_mapper.pyOutput of the first three lines of student_orders.csv (dosa, samosa, samosa). Each line is category, a tab, and the amount.
The reducer
#!/usr/bin/env python3
# revenue_reducer.py: Hadoop Streaming reducer. Lines arrive SORTED by key; we detect where a key changes.
import sys
current, orders, total = None, 0, 0
for line in sys.stdin:
category, amount = line.rstrip("\n").split("\t")
if category != current:
if current is not None:
print(f"{current}\t{orders}\t{total}")
current, orders, total = category, 0, 0
orders += 1
total += int(amount)
if current is not None:
print(f"{current}\t{orders}\t{total}")
# Test the whole job locally:
# head -1000 student_orders.csv | python3 revenue_mapper.py | sort | python3 revenue_reducer.pyReal output of the local pipe test on the first 1,000 orders.
Local test, cluster run, and a broken mapper
# streaming_lab.sh: the same category-revenue job written in Python and run with Hadoop Streaming
cd /tmp/mr9
JAR=$(ls /opt/hadoop/share/hadoop/tools/lib/hadoop-streaming-*.jar); echo "streaming jar: $JAR"
O=/user/hadoop/data/student_orders.csv
echo "== 1. test the scripts locally first, with a Linux pipe (sort = the shuffle)"
head -1000 student_orders.csv | python3 revenue_mapper.py | sort | python3 revenue_reducer.py
echo "== 2. run on the cluster"
hdfs dfs -rm -r -f -skipTrash /out/stream > /dev/null 2>&1
start=$(date +%s%N)
mapred streaming -files revenue_mapper.py,revenue_reducer.py,menu.csv \
-input $O -output /out/stream \
-mapper "python3 revenue_mapper.py" -reducer "python3 revenue_reducer.py" > /tmp/stream.log 2>&1
echo "took $(( ($(date +%s%N) - start) / 1000000 )) ms"
grep -E "number of splits|completed successfully|Launched map tasks|Map output records|Reduce input groups|CPU time spent" /tmp/stream.log | sed -E 's/^[0-9-]+ [0-9:]+ INFO //; s/^\s*/ /'
hdfs dfs -cat /out/stream/part-00000
echo "== 3. a bug in the mapper (a typo in a variable name)"
sed 's/print(f"{menu.get(item/print(f"{menu.get(itme/' revenue_mapper.py > broken_mapper.py
hdfs dfs -rm -r -f -skipTrash /out/stream_bad > /dev/null 2>&1
mapred streaming -files broken_mapper.py,revenue_reducer.py,menu.csv -input $O -output /out/stream_bad \
-mapper "python3 broken_mapper.py" -reducer "python3 revenue_reducer.py" > /tmp/stream_bad.log 2>&1
grep -E "subprocess failed|failed with state" /tmp/stream_bad.log | sed -E 's/^[0-9-]+ [0-9:]+ (INFO|ERROR) //' | sort -u | head -3 | cut -c1-160
app=$(grep -o "application_[0-9_]*" /tmp/stream_bad.log | head -1); sleep 5
echo "-- the Python error is in the task's stderr:"
yarn logs -applicationId $app -log_files stderr 2>/dev/null | grep -E "NameError|Traceback" | sort -u | head -2
# In the mr-lab folder:
# docker cp revenue_mapper.py revenue_reducer.py menu.csv streaming_lab.sh lab8-namenode-1:/tmp/mr9/
# docker exec lab8-namenode-1 bash /tmp/mr9/streaming_lab.shReal output. The totals match the Java OrderCounters job of Lesson 9.4 to the rupee. In step 3 the job log only says the subprocess failed; the NameError is found in the task's stderr with yarn logs.
The key-change reducer, line by line
Four sorted lines with two keys. Watch current and total: the first key is printed only when the second key arrives, the last one only after the loop.
Worked examples: real problems, solved and traced in memory
Three Streaming problems: designing the scripts, a map-only job, and estimating the cost of text pipes.
Count orders per hour with Streaming
Problem statement
Write the mapper and reducer logic to count orders per hour of the day from lines like 4,S0001,2026-09-02 08:10,chai,15. What does the mapper print, and what does the reducer do?
5 order lines
{'08': 3, '13': 2}Approach
- Mapper: cut the hour (characters 11–13 of the time) and print hour⇥1.
- Shuffle: sort the lines.
- Reducer: add the ones while the hour stays the same.
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.
A map-only Streaming job: find the thali orders
Problem statement
You need every thali order as a separate file set, no aggregation. Which options make a map-only job, and what are the output files called?
-numReduceTasks 0 · 3 splits
2 matching lines: ['2,S2,t,thali,80', '3,S3,t,thali,80'] output files with 3 splits: ['part-00000', 'part-00001', 'part-00002']
Approach
- A filter needs no reducer: -numReduceTasks 0.
- Each map task writes its own output file.
- The mapper prints only matching lines.
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.
What do the pipes cost?
Problem statement
Our Streaming mapper printed 500,000 lines of about 10 bytes. Each record crosses two pipes on the map side (line in, line out) and one on the reduce side. If a pipe crossing costs about 2 µs of CPU per record (illustrative), how much CPU do the pipes add for 5 lakh records, and for 50 crore?
2 µs per crossing · 3 crossings per record
500,000 records -> 3 s of extra CPU 500,000,000 records -> 3,000 s of extra CPU
Approach
- Crossings = records × 3.
- CPU = crossings × 2 µs.
- Compare small and large data.
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 9.8 in one screen
- Hadoop Streaming: any program reading stdin and writing stdout can be mapper, combiner or reducer.
- Output lines are split at the first tab into key and value; the reducer gets lines sorted by key and must detect key changes (and print the last key after the loop).
- Command:
mapred streaming -files m.py,r.py,menu.csv -input … -output … -mapper "python3 m.py" -reducer "python3 r.py". - Lab: 5 lakh orders, 3 maps, 24.5 s; totals identical to the Java job (drink 1,90,033 orders, ₹30,52,315).
- Test locally:
cat data | mapper | sort | reducer. - Errors: job log says “PipeMapRed … subprocess failed with code 1”; the Python traceback is in
yarn logs … -log_files stderr. -numReduceTasks 0= map-only;stream.num.map.output.key.fields= composite keys. Python must be on every node. New Python work usually uses PySpark.
Interview and viva questions
What is Hadoop Streaming?
A utility that comes with Hadoop which lets you write the mapper and reducer of a MapReduce job in any language. Each task runs your program as a subprocess, writes input records to its standard input as lines, and reads its standard output as key and value lines separated by a tab. The shuffle, sort, scheduling and fault tolerance are the same as for Java jobs.
How do you run a Python MapReduce job on Hadoop?
Write a mapper and a reducer script that read sys.stdin and print key TAB value lines, test them with cat file | python3 mapper.py | sort | python3 reducer.py, then run mapred streaming -files mapper.py,reducer.py -input <hdfs input> -output <hdfs output> -mapper "python3 mapper.py" -reducer "python3 reducer.py". Python must be installed on every NodeManager.
How is a Streaming reducer different from a Java reducer?
A Java reducer is called once per key with an Iterable of all its values. A Streaming reducer is one process that reads all the lines of its partition, sorted by key, so the script must remember the current key, accumulate values, and output a result whenever the key changes and once more at the end of input.
How do I debug a failed Hadoop Streaming job?
The job output usually only shows “PipeMapRed.waitOutputThreads(): subprocess failed with code 1”. Read the failed task's stderr with yarn logs -applicationId <id> -log_files stderr (or the JobHistory UI) to see the real error, such as a Python traceback. Test the scripts first with a local Unix pipe, and never print debug text to stdout.
Is Hadoop Streaming slower than Java MapReduce?
Usually somewhat, because every record is converted to text and passed through pipes to and from a separate process, and the scripting language itself may be slower. For many jobs the difference is small compared with the I/O; for CPU-heavy jobs Java or Spark is faster.
Practice problems
Streaming exercises with the real output of our lab run.
Check the local test
The local pipe test on 1,000 lines printed 179, 377, 162 and 282 orders. Check that they add up.
4 counts
1000
Show solution and explanation
print(179 + 377 + 162 + 282)How it works: 1,000: every input line produced exactly one mapper line.
Split a line like Hadoop
Split each mapper line at the FIRST tab into key and value, the way Streaming does.
3 lines
'meal' '45' 'drink' '15\textra' 'notab' ''
Show solution and explanation
for line in ["meal\t45", "drink\t15\textra", "notab"]:
key, _, value = line.partition("\t")
print(repr(key), repr(value))How it works: Only the first tab counts; the rest stays in the value. A line with no tab becomes a key with an empty value.
Average order value per category
From the cluster output, compute the average amount per order for each category.
4 output lines
breakfast Rs 27.48 drink Rs 16.06 meal Rs 58.22 snack Rs 19.00
Show solution and explanation
out = {"breakfast": (90110, 2476620), "drink": (190033, 3052315), "meal": (80219, 4670075), "snack": (139638, 2652992)}
for c, (n, total) in out.items():
print(f"{c:9} Rs {total / n:.2f}")How it works: Meals average about ₹58 an order, drinks about ₹16: dosa and thali vs chai and coffee.
Reducer with a minimum and maximum
Extend the key-change reducer to also print the smallest and largest amount per key.
sorted lines
drink 3 15 20 meal 2 45 80
Show solution and explanation
lines = sorted(["drink\t15", "drink\t20", "meal\t45", "meal\t80", "drink\t15"])
current, vals = None, []
for line in lines + [None]: # None marks the end of input
key, value = line.split("\t") if line else (None, None)
if key != current and current is not None:
print(current, len(vals), min(vals), max(vals))
vals = []
current = key
if value is not None:
vals.append(int(value))How it works: A sentinel at the end replaces the separate “print the last key” step. Both patterns are fine; forgetting both is the bug.
Counters from Python
Write the stderr line a Streaming script prints to increment a counter BAD_LINES in group Quality, and count how many it would print for this input.
3 lines
reporter:counter:Quality,BAD_LINES,1 reporter:counter:Quality,BAD_LINES,1
Show solution and explanation
lines = ["1,S1,t,chai,15", "broken line", "3,S3,t,dosa"]
for line in lines:
if len(line.split(",")) != 5:
print("reporter:counter:Quality,BAD_LINES,1") # in a real script: sys.stderr.write(... + "\n")How it works: Two lines, so the job counter Quality / BAD_LINES would show 2 in the job output.
Combiner-safe output
Our reducer prints category⇥orders⇥total, but the mapper prints category⇥amount. Why can't the reducer be used as -combiner unchanged? Show what the reducer would see after such a combiner.
combiner output fed to the reducer
reducer would read value: '3\t50' -> int() fails
Show solution and explanation
combined = ["drink\t3\t50"] # what the reducer script would print as a combiner
key, value = combined[0].split("\t", 1)
print("reducer would read value:", repr(value), "-> int() fails")How it works: A combiner's output must look like the mapper's output (Lesson 9.1). Here the value becomes "3\t50", which int() cannot parse.
Rewrite or keep?
A nightly Streaming job takes 40 minutes of a 20-node cluster. A PySpark rewrite (2 developer-weeks at ₹1.2 lakh each) would take 12 minutes. Node-hours cost ₹50. How many nights until the rewrite pays for itself?
40 → 12 minutes · 20 nodes · ₹50/node-hour
Rs 467 saved per night, pays back in 514 nights
Show solution and explanation
saved_per_night = (40 - 12) / 60 * 20 * 50
print(f"Rs {saved_per_night:.0f} saved per night, pays back in {2 * 120000 / saved_per_night:.0f} nights")How it works: About ₹467 a night and roughly 514 nights (illustrative). Speed alone rarely justifies a rewrite; maintainability and team skills usually decide.
Secondary sort with two key fields
With stream.num.map.output.key.fields=2 and a partitioner on field 1 only, each reducer sees one student's orders sorted by time. Simulate it: partition on the student, sort on (student, time), and print each student's first order.
5 lines, 2 reducers
reducer 0: S2 first order 2026-09-02 09:00 poha reducer 1: S1 first order 2026-09-02 08:10 chai
Show solution and explanation
lines = ["S2\t2026-09-03 08:05\tchai", "S1\t2026-09-02 13:05\tthali", "S1\t2026-09-02 08:10\tchai",
"S2\t2026-09-02 09:00\tpoha", "S1\t2026-09-03 08:40\tpoha"]
parts = {0: [], 1: []}
for l in lines:
parts[int(l.split("\t")[0][1:]) % 2].append(l) # partition on field 1 only
for p in parts:
current = None
for l in sorted(parts[p]): # sort on fields 1 and 2
s, t, item = l.split("\t")
if s != current:
print(f"reducer {p}: {s} first order {t} {item}")
current = sHow it works: Each student lands in one reducer, already in time order, so the first line per student is the first order: Lesson 9.7's secondary sort without Java comparators.
Real-world scenario questions (asked in company interviews)
Streaming comes up when interviewers check whether you know MapReduce beyond Java, and in debugging questions.
“Can you write MapReduce in Python?”
A common follow-up after the word count question.
The interviewer is checking: knowing Streaming and how its reducer differs.
Show a strong answer
- Yes, with Hadoop Streaming: the mapper and reducer are scripts reading stdin and printing key⇥value lines.
- The reducer gets lines sorted by key, not a list per key, so it tracks the current key and emits on change (and at the end).
- Run with mapred streaming -files … -mapper "python3 m.py" -reducer "python3 r.py"; Python must be on every node. Today PySpark is usually preferred.
“subprocess failed with code 1” and nothing else
A Streaming job that worked yesterday fails on every map attempt.
The interviewer is checking: where to look and likely causes.
Show a strong answer
- Read the failed attempt's stderr: yarn logs -applicationId … -log_files stderr, or the JobHistory UI. That shows the Python traceback.
- Typical causes: a malformed input line (unpacking error), a missing module or different Python on some nodes, a file not shipped with -files.
- Reproduce locally with the failing split's data through the same pipe; make the script skip and count bad lines instead of crashing.
Streaming, Java or PySpark for a new job?
A team of Python developers must aggregate 5 TB of logs nightly on an existing Hadoop cluster.
The interviewer is checking: balanced reasoning, not a reflex.
Show a strong answer
- Streaming: fastest to start with existing scripts; text pipes add CPU; limited control (no custom comparators without Java).
- Java MapReduce: best per-record efficiency and control, but a skills gap for this team.
- PySpark on YARN: Python skills, in-memory stages, SQL/DataFrames; usually the right default for new work if Spark is available.
“The last category is missing from the output”
A Streaming job prints totals for drink, meal and breakfast, but never snack.
The interviewer is checking: the key-change reducer pattern.
Show a strong answer
- The reducer prints a key only when the next key arrives; the final key has no successor.
- Fix: after the loop, print the current key's result if current is not None (or use a sentinel).
- Test with a local pipe whose last sorted key is known, and with empty input.