Hadoop Streaming with Python

Hadoop Streaming lets any program that reads lines and prints lines be a mapper or a reducer. This lesson writes a Python mapper and reducer for revenue per menu category, tests them with an ordinary Linux pipe, runs them on the cluster with the same result as the Java job, and then breaks the mapper to show where a Python error really ends up.

Module 9 · MapReduce in depth⏱ 26 minIntermediateReal Python job on YARNSame totals as JavaRuns in your browser (Python)8 practice problems
1 The story

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.

map task (on a worker) Java task(YarnChild) python3mapper.py stdin: 1,S1413,…,dosa,45 stdout: meal⇥45 one line in, one line out shuffle and sort sorted by key breakfast⇥30 · breakfast⇥25 drink⇥15 · drink⇥15 … reduce task Java task(YarnChild) python3reducer.py stdin: sorted lines stdout: meal⇥80219⇥4670075 your script spots each new key Real run: 5 lakh orders, 3 maps, 24.5 s. Same totals as the Java job, written as two short Python scripts. myphatshala.in
Kabir

So Hadoop runs my Python script instead of a Java Mapper?

Arjun Sir

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.

Riya

And the reducer gets all values of a key in a list, like in Java?

Arjun Sir

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.

2 Definition

Hadoop Streaming

Formal definition (write this in exams)

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.

In simple words

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

stdin / stdoutThe input and output pipes of a process; Streaming uses them instead of Java method calls.
key⇥valueA text line split at the first tab: before it is the key, after it the value.
-mapper / -reducerThe commands to run, e.g. "python3 revenue_mapper.py".
-filesShips local files to every task's working folder (scripts, lookup files).
Key changeThe moment a reducer script sees a different key from the line before.
PipeMapRedThe Java class that runs your process and moves lines through the pipes.
Subprocess exit codeA non-zero exit from your script fails the task attempt.
Local pipe testcat 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 test1,000 lines through mapper | sort | reducer: breakfast 179, drink 377, meal 162, snack 282 orders
Cluster runstudent_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
Resultbreakfast 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 typoJob 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

Pick your level. New to this? Start at Beginner and climb.

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.py does the same thing; sort plays 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 -files placed your scripts and menu.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⇥value lines, 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 0 gives a map-only job (filtering, format conversion); -combiner takes a script too.
  • -D stream.num.map.output.key.fields=2 makes the first two tab-separated fields the key, so you can sort on a composite key; with KeyFieldBasedPartitioner you 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".

3 Types and ways to use it

Streaming options you will actually use

OptionWhat it doesExample
-input / -outputHDFS input path(s) and the output folder (must not exist)-input /user/hadoop/data/student_orders.csv -output /out/stream
-mapper / -reducerThe command to run for each task-mapper "python3 revenue_mapper.py"
-combinerA 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)
-filesShips local files into each task's working folder-files revenue_mapper.py,revenue_reducer.py,menu.csv
-numReduceTasksNumber 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
-partitionerA 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 API

Hadoop Streaming

Any language, text lines through pipes. Quick to write and test; slower per record; you parse and format everything yourself.

any language

mrjob / Pydoop

Python libraries that generate the Streaming command (mrjob) or talk to Hadoop more directly (Pydoop). Less boilerplate.

Python helpers

PySpark

The usual choice for new Python data work: DataFrames, SQL, in-memory stages. Runs on YARN too.

today

Counters 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.

4 Step by step

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”.

  1. The mapper: one order in, one line out

    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}")
    The menu lookup makes this a map-side join (Lesson 9.6) for free: menu.csv lands in the task's working folder because of -files.
  2. The reducer: watch for the key to change

    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}")
    Two easy-to-miss lines: the final print after the loop (otherwise the last category is lost) and the if current is not None guard (an empty partition must print nothing).
  3. Test with a Linux pipe first

    head -1000 student_orders.csv | python3 revenue_mapper.py | sort | python3 revenue_reducer.py
    breakfast 179 4940 drink 377 6060 meal 162 9145 snack 282 5362
    179 + 377 + 162 + 282 = 1,000 orders. sort stands in for the shuffle. If this works, the cluster run almost always works too.
  4. Run it on the cluster

    mapred 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)=5710
    Three map tasks each started a python3 process; the same counters as a Java job appear because the Java task around your script still does all the bookkeeping.
  5. Read the output and cross-check

    hdfs dfs -cat /out/stream/part-00000
    breakfast 90110 2476620 drink 190033 3052315 meal 80219 4670075 snack 139638 2652992
    Streaming output files are called part-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.
  6. Break the mapper and find the error

    # 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 stderr
    Error: 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):
    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.

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.

5 See it happen

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.

Streaming job 6 steps
What you see hadoop@namenode : /tmp/mr9 $ mapred streaming -files revenue_mapper.py, revenue_reducer.py,menu.csv …number of splits:3 map 0% reduce 0%(3 maps, each with a python3 process) map 100% reduce 0%Map output records=500000 map 100% reduce 100%Reduce input groups=4completed successfully $ hdfs dfs -cat /out/stream/part-00000breakfast 90110 2476620drink 190033 3052315meal 80219 4670075snack 139638 2652992 PipeMapRed.waitOutputThreads(): subprocess failed with code 1$ yarn logs … -log_files stderrNameError: name 'itme' is not defined myphatshala.in
Behind the screen worker: one task container Java taskPipeMapRed python3your script shuffle and sort (Java, as in Lesson 8.4)key = text before the first tab ./revenue_mapper.py./menu.csv-files puts the scripts and menu.csv in each task's working folder stdin: 1,S1413,…,dosa,45stdout: meal⇥45each input line is written to the script; each printed line is read back “meal⇥45” is split at the tab: key “meal”, value “45”, then sorted stdin: drink⇥15, drink⇥20 …stdout: drink⇥190033⇥…reduce side: sorted lines in; the script prints when the key changes part-00000 in HDFSthe Java task writes the script's output lines to the job output exit code 1NameError → stderrthe Java task sees the exit code, fails the attempt; stderr goes to the YARN log myphatshala.in
6 Real-world use

Streaming at work

mapper.pyreducer.py

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.

Existing script→mapred streaming→Cluster-scale run
grep / awk

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.

Logs in HDFS→-mapper grep→Matching lines

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.

Records + model file→map: predict()→Scores
StreamSpark

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().

Streaming job→same logic→PySpark job

Simplified; organisations differ in tools and policies.

7 Run the code

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)
map output : ['meal\t45', 'snack\t18', 'snack\t18', 'drink\t15', 'meal\t80', 'breakfast\t25'] sorted : ['breakfast\t25', 'drink\t15', 'meal\t45', 'meal\t80', 'snack\t18', 'snack\t18'] reduce : breakfast 1 25 reduce : drink 1 15 reduce : meal 2 125 reduce : snack 2 36
  • 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.py
meal 45 snack 18 snack 18

Output 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.py
breakfast 179 4940 drink 377 6060 meal 162 9145 snack 282 5362

Real 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.sh
streaming jar: /opt/hadoop/share/hadoop/tools/lib/hadoop-streaming-3.5.0.jar == 1. test the scripts locally first, with a Linux pipe (sort = the shuffle) breakfast 179 4940 drink 377 6060 meal 162 9145 snack 282 5362 == 2. run on the cluster took 24512 ms JobSubmitter:203 - number of splits:3 Job:1765 - Job job_1792643020992_0045 completed successfully Launched map tasks=3 Map output records=500000 Reduce input groups=4 CPU time spent (ms)=5710 breakfast 90110 2476620 drink 190033 3052315 meal 80219 4670075 snack 139638 2652992 == 3. a bug in the mapper (a typo in a variable name) Error: java.lang.RuntimeException: PipeMapRed.waitOutputThreads(): subprocess failed with code 1 Job:1767 - 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):

Real 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.

8 Line by line

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.

Example 1BeginnerDesign · scripts

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?

Input
5 order lines
Expected output
{'08': 3, '13': 2}

Approach

  1. Mapper: cut the hour (characters 11–13 of the time) and print hour⇥1.
  2. Shuffle: sort the lines.
  3. 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.

Example 2IntermediateMap-only · filter

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?

Input
-numReduceTasks 0 · 3 splits
Expected output
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

  1. A filter needs no reducer: -numReduceTasks 0.
  2. Each map task writes its own output file.
  3. 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.

Example 3AdvancedPerformance · pipes

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?

Input
2 µs per crossing · 3 crossings per record
Expected output
    500,000 records -> 3 s of extra CPU
500,000,000 records -> 3,000 s of extra CPU

Approach

  1. Crossings = records × 3.
  2. CPU = crossings × 2 µs.
  3. 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.

9 Quiz and revision

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.

P1Easy

Check the local test

The local pipe test on 1,000 lines printed 179, 377, 162 and 282 orders. Check that they add up.

Input
4 counts
Expected output
1000
Show solution and explanation
print(179 + 377 + 162 + 282)
1000

How it works: 1,000: every input line produced exactly one mapper line.

P2Easy

Split a line like Hadoop

Split each mapper line at the FIRST tab into key and value, the way Streaming does.

Input
3 lines
Expected output
'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))
'meal' '45' 'drink' '15\textra' 'notab' ''

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.

P3Easy

Average order value per category

From the cluster output, compute the average amount per order for each category.

Input
4 output lines
Expected output
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}")
breakfast Rs 27.48 drink Rs 16.06 meal Rs 58.22 snack Rs 19.00

How it works: Meals average about ₹58 an order, drinks about ₹16: dosa and thali vs chai and coffee.

P4Medium

Reducer with a minimum and maximum

Extend the key-change reducer to also print the smallest and largest amount per key.

Input
sorted lines
Expected output
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))
drink 3 15 20 meal 2 45 80

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.

P5Medium

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.

Input
3 lines
Expected output
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")
reporter:counter:Quality,BAD_LINES,1 reporter:counter:Quality,BAD_LINES,1

How it works: Two lines, so the job counter Quality / BAD_LINES would show 2 in the job output.

P6Medium

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.

Input
combiner output fed to the reducer
Expected output
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")
reducer would read value: '3\t50' -> 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.

P7Business problem

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?

Input
40 → 12 minutes · 20 nodes · ₹50/node-hour
Expected output
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")
Rs 467 saved per night, pays back in 514 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.

P8Advanced

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.

Input
5 lines, 2 reducers
Expected output
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 = s
reducer 0: S2 first order 2026-09-02 09:00 poha reducer 1: S1 first order 2026-09-02 08:10 chai

How 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.

Concepts · basicsFresher

“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.
Debugging · logsExperienced

“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.
Design · trade-offsExperienced

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.
Correctness · reducerFresher

“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.