Word count end to end

Every MapReduce program has the same parts: a mapper, a reducer, a driver, a jar and one command. This lesson takes word count from an empty Java file to a sorted result in HDFS on a real cluster, follows what YARN does at each step, reads the errors you will meet first, and then kills a running task to watch Hadoop recover.

Module 8 · MapReduce fundamentals⏱ 30 minBeginnerReal job, killed and recoveredEvery command and error explainedRuns in your browser (Python)8 practice problems
1 The story

Two lakh complaints and compliments

The canteen put a feedback box on its app. After one semester there are 2,00,000 short lines like “the idli was fresh” and “why is the chai was cold”. The canteen manager asks Riya one simple question: which words come up most? Riya has learned map, splits, the mapper, the shuffle and the reducer one by one. Today Arjun Sir wants her to do the whole thing herself, from an empty .java file to a result in HDFS, and then he plans to break it on purpose.

1. writeWordCount.java 2. buildjavac + jar → wc.jar 3. submithadoop jar … 4. YARN runs itAM · maps · reduce 5. resultpart-r-00000 kill -9 a map task → exit code 137, retried on worker3 was 200000 the 166921 … Real lab: 1,033,238 words counted in one job; a killed task changed nothing in the answer. myphatshala.in
Riya

Sir, word count again? We did it on paper in the first lesson.

Arjun Sir

On paper you did the thinking. Today you do the engineering: the exact Java classes, the jar, the command, what happens inside YARN when you press Enter, where the answer lands, and what the error messages mean when something is wrong. Word count is the “hello world” of Hadoop because every job you will ever write has exactly these parts.

Kabir

And the best part: halfway through a bigger run we will kill one of its map tasks with kill -9. Hadoop should notice, run it again on another worker, and still give the exact same counts.

2 Definition

A MapReduce job, end to end

Formal definition (write this in exams)

A MapReduce application consists of a Mapper class, a Reducer class and a driver (a main method) that configures a Job: its input and output paths, the classes to use, and the output key and value types. It is compiled against the Hadoop libraries, packaged as a jar and launched with hadoop jar. On submission the client checks the output specification (the output directory must not exist), computes the input splits, copies the jar, configuration and split information to a staging directory in HDFS and submits an application to the YARN ResourceManager. YARN starts the job's MRAppMaster in a container; it requests containers for the map and reduce tasks, monitors them, reruns failed attempts (up to mapreduce.map.maxattempts = 4 by default), and commits the output. Job counters and task history are kept by the JobHistory Server after the job ends.

In simple words

You write three small pieces of Java, pack them into a jar and hand the jar to Hadoop with one command. Hadoop uploads it, YARN starts a manager for your job, the manager runs your map and reduce tasks on the workers (again if one fails), and the answer appears as files in HDFS.

Words you'll meet in this lesson

DriverThe main method that builds and submits the Job.
Job jarYour compiled classes packed with jar cf; sent to every task.
hadoop jarThe command that runs a driver with Hadoop's classpath and config.
Staging directory/tmp/hadoop-yarn/staging/…/.staging/job_…: jar, job.xml, split info.
Application IDapplication_1792601400460_0030: YARN's name for the job.
Uber modeTiny jobs run all tasks inside the AM's JVM. Off by default.
Task attemptOne try of a task; a failed attempt is retried as _1, _2…
Exit code 137128 + 9: the task JVM was killed with SIGKILL.

From our lab (Hadoop 3.5.0, HDFS + YARN, 3 workers)

Buildwc.jar with 3 classes: WordCount, WordCount$TokenizerMapper, WordCount$IntSumReducer
Runfeedback.txt, 5,093,962 bytes, 2,00,000 lines → 1 split, 1 map, 1 reduce; 1,033,238 (word, 1) pairs, 31 distinct words, 350 bytes of output; uber mode false
Top wordswas 200000 · the 166921 · loved 33570 · dosa 33566 · chai 33431 · samosa 33431. “vada pav” is counted as vada 33040 and pav 33040
ErrorsSame output folder again: FileAlreadyExistsException; misspelt input: InvalidInputException: Input path does not exist. Both fail before any task starts
Killed task20× the file (102 MB, 3 splits): map 0's JVM killed on worker2 → “exit code 137”, attempt _1 succeeded on worker3; Failed map tasks=1, job succeeded, chai 668620 = 20 × 33431

The whole job at three levels

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

Beginner A recipe, a parcel and a kitchen

  • The Java file is a recipe: what to do with one line (map) and with one word's pile of ones (reduce).
  • The jar is the recipe sealed in a parcel so it can be sent to every kitchen (worker).
  • hadoop jar hands the parcel to the canteen manager (YARN), who appoints a supervisor for this order (the ApplicationMaster).
  • If one cook faints, the supervisor gives that cook's part to another cook. The final dish is the same.

Intermediate What each command does

  • javac -cp $(hadoop classpath) compiles against Hadoop's jars; jar cf wc.jar … packs the .class files, including the nested $TokenizerMapper and $IntSumReducer.
  • hadoop jar wc.jar WordCount in out runs main. GenericOptionsParser removes options like -D mapreduce.job.reduces=2 so rest holds only the two paths.
  • job.waitForCompletion(true) submits and then prints progress and counters until the job ends; the exit code tells scripts whether it worked.
  • Output: part-r-00000 (one per reducer) and _SUCCESS. Read it with hdfs dfs -cat, or sort it: | sort -k2,2nr | head.

Advanced What happens inside YARN

  • The client writes the jar, job.xml, job.split and job.splitmetainfo to the staging directory, then asks the ResourceManager for an application.
  • The RM starts the MRAppMaster in a container on some NodeManager. The AM reads the splits and requests one container per map (with the split's hosts as preferred locations) and the reducers.
  • Each task runs in a YarnChild JVM that downloads the jar from HDFS (localization). Tasks report progress to the AM over the umbilical protocol; a dead JVM ends its container with a non-zero exit code and the AM schedules a new attempt, preferably on another node.
  • When all reducers have committed, the FileOutputCommitter moves their output into place and writes _SUCCESS; the AM hands the history file to the JobHistory Server and exits.

Exam tip: For “Explain the execution flow of a MapReduce job on YARN”, list in order: client submits (output check, splits, staging) → ResourceManager allocates a container for the MRAppMaster → AM requests task containers → NodeManagers launch map tasks → shuffle → reduce tasks → output commit and _SUCCESS → history to the JobHistory Server. Add one line on failure: attempts are retried up to 4 times.

3 Types and ways to use it

Two Java APIs, many ways to run

Hadoop ships two MapReduce Java APIs. Old books and Stack Overflow answers mix them, which is a classic source of compile errors.

New API (use this)Old API
Packageorg.apache.hadoop.mapreduceorg.apache.hadoop.mapred
Mapperclass Mapper<K1,V1,K2,V2>, you extend itinterface Mapper + MapReduceBase
Writing outputcontext.write(k, v)output.collect(k, v) + Reporter
Reduce valuesIterable<V> (for-each loop)Iterator<V>
Job setupJob.getInstance(conf, "word count")JobConf + JobClient.runJob
Setup/cleanupsetup(), cleanup(), run()configure(), close()

Ways to launch a word count

Your own jar

hadoop jar wc.jar WordCount in out. What we do today; you control every line.

custom

Built-in example

hadoop jar $HADOOP_HOME/share/hadoop/mapreduce/hadoop-mapreduce-examples-*.jar wordcount in out. Good for testing a new cluster (Lesson 4.3).

smoke test

Hadoop Streaming

Mapper and reducer as Python scripts reading stdin (Lesson 9.8).

any language

Local mode

-D mapreduce.framework.name=local runs everything in one JVM: fast to debug, no YARN (Lesson 4.2).

debugging

Always pass options through GenericOptionsParser (or implement Tool and use ToolRunner). Then you can change reducers, memory or queue from the command line with -D key=value without recompiling.

4 Step by step

From an empty file to a result in HDFS

Run on the Module 8 lab cluster (Lesson 8.1) with wordcount_lab.sh and kill_task.sh from “Run it on your PC”.

  1. Write the mapper and reducer

    // Mapper<input key, input value, output key, output value>
    public void map(LongWritable offset, Text line, Context context) … {
      StringTokenizer it = new StringTokenizer(line.toString().toLowerCase(), " \t.,!?;:\"'()");
      while (it.hasMoreTokens()) { word.set(it.nextToken()); context.write(word, ONE); }
    }
    // Reducer<Text, IntWritable, Text, IntWritable>
    public void reduce(Text word, Iterable<IntWritable> ones, Context context) … {
      int sum = 0;
      for (IntWritable one : ones) sum += one.get();
      result.set(sum); context.write(word, result);
    }
    The four generic types must line up: the mapper's output types (Text, IntWritable) are the reducer's input types. word and result are reused objects: Hadoop serializes them on every write, so reuse is safe and saves millions of allocations.
  2. Write the driver

    String[] rest = new GenericOptionsParser(conf, args).getRemainingArgs();
    Job job = Job.getInstance(conf, "word count");
    job.setJarByClass(WordCount.class);
    job.setMapperClass(TokenizerMapper.class);
    job.setReducerClass(IntSumReducer.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    FileInputFormat.addInputPath(job, new Path(rest[0]));
    FileOutputFormat.setOutputPath(job, new Path(rest[1]));
    System.exit(job.waitForCompletion(true) ? 0 : 1);
    setJarByClass tells Hadoop which jar to ship to the workers. Input format defaults to TextInputFormat (key = byte offset, value = line), output to TextOutputFormat (key TAB value).
  3. Compile and package

    javac -cp $(hadoop classpath) -d wc_classes WordCount.java
    jar cf wc.jar -C wc_classes .
    jar tf wc.jar | grep class
    WordCount$IntSumReducer.class WordCount$TokenizerMapper.class WordCount.class
    Nested classes compile to separate files with a $ in the name. They must be static: Hadoop creates them by reflection with no outer object.
  4. Submit and watch

    hadoop jar wc.jar WordCount /user/hadoop/data/feedback.txt /out/wc
    JobSubmitter:203 - number of splits:1 YarnClientImpl:356 - Submitted application application_1792601400460_0030 Job:1681 - The url to track the job: http://resourcemanager:8088/proxy/application_1792601400460_0030/ Job:1747 - Job job_1792601400460_0030 running in uber mode : false Job:1754 - map 0% reduce 0% Job:1754 - map 100% reduce 0% Job:1754 - map 100% reduce 100% Job:1765 - Job job_1792601400460_0030 completed successfully Map input records=200000 Map output records=1033238 Reduce input groups=31 Reduce output records=31
    One split (5 MB fits in one block). The tracking URL points at the ResourceManager UI on port 8088; after the job ends the same details live on the JobHistory Server (port 19888). 1,033,238 words became 1,033,238 pairs, all shuffled to one reducer, which found 31 different words.
  5. Read the result

    hdfs dfs -cat /out/wc/part-r-00000 | head -5
    hdfs dfs -cat /out/wc/part-r-00000 | sort -k2,2nr | head -8
    amazing 17687 chai 33431 cold 8452 costly 8410 crispy 17585 was 200000 the 166921 loved 33570 dosa 33566 chai 33431 samosa 33431 improve 33398 please 33398
    The file is sorted by word (the shuffle sorts keys), not by count; sorting by count is a second step. “was” appears once in every line: 200,000. Good words (tasty, crispy…) appear about twice as often as bad ones (cold, stale…).
  6. Make it fail, twice, before it starts

    hadoop jar wc.jar WordCount /user/hadoop/data/feedback.txt /out/wc
    hadoop jar wc.jar WordCount /user/hadoop/data/feedbak.txt /out/wc2
    Exception in thread "main" org.apache.hadoop.mapred.FileAlreadyExistsException: Output directory hdfs://namenode/out/wc already exists Exception in thread "main" org.apache.hadoop.mapreduce.lib.input.InvalidInputException: Input path does not exist: hdfs://namenode/user/hadoop/data/feedbak.txt
    Both are caught by the client before anything is sent to YARN: the output check and the split calculation happen at submission. Cheap, early failures are a feature.
  7. Kill a running map task

    docker exec lab8-worker2-1 kill -9 <pid of the map task's YarnChild JVM>
    bash attempts.sh job_1792601400460_0033
    Task Id : attempt_1792601400460_0033_m_000000_0, Status : FAILED Container exited with a non-zero exit code 137. Job job_1792601400460_0033 completed successfully Failed map tasks=1 Launched map tasks=5 attempt_…_m_000000_0 FAILED worker2 7.9 s attempt_…_m_000000_1 SUCCEEDED worker3 21.7 s attempt_…_m_000000_2 KILLED worker2 4.4 s Speculation: attempt_…_m_000000_1 succeeded first! chai 668620 was 4000000
    The AM saw the container die (137 = 128 + signal 9) and launched attempt _1 on worker3. It also started a speculative _2, which lost the race and was killed. The answer is exactly 20 × the small one (chai 20 × 33431 = 668620): a failed attempt's partial output is thrown away, never mixed in.

Don't “fix” FileAlreadyExistsException by deleting output folders automatically inside the driver unless you are sure. In production, a rerun silently replacing yesterday's results is worse than a job that refuses to start. Use dated output paths like /out/wc/dt=2026-10-21.

5 See it happen

The job's life, including a crash

Six steps of the 20× run: submit, ApplicationMaster, maps, a killed task, the retry and the reduce.

Word count on YARN 6 steps
What you see hadoop@namenode : /tmp/mr $ hadoop jar wc.jar WordCount feedback_x20.txt …number of splits:3Submitted application_…_0033 Running job: job_…_0033running in uber mode : falsemap 0% reduce 0% map 0% reduce 0%(3 maps reading 3 blocks …) attempt_…_m_000000_0,Status : FAILEDContainer exited with a non-zeroexit code 137. map 26% reduce 0%map 67% reduce 0%map 100% reduce 0%map 100% reduce 95% completed successfullyFailed map tasks=1Launched map tasks=5chai 668620was 4000000 myphatshala.in
Behind the screen client ResourceManager HDFS worker1 worker2 worker3 jar + job.xml + splits → staging dir in HDFS;then “submit application” to the RM MRAppMasterthe RM starts the job's ApplicationMasterin a container; it asks for task containers map 0map 1map 2each map in its own YarnChild JVM,reading the block stored on its worker map 0 ✗ kill -9the JVM dies; the container exits with 137and the AM marks attempt _0 FAILED map 0, attempt _1reducea new attempt on worker3 redoes the whole split;the reducer fetches only successful map outputs reduce ✓part-r-00000 committed + _SUCCESS;history goes to the JobHistory Server myphatshala.in

Try it yourself: a job that survives a failure

Upload a file, run the word count, then fail a node and run it again. Watch which tasks are rerun.

6 Real-world use

Word count's real cousins

tasty 17338cold 8452

1. Feedback and review analysis

E-commerce and food apps count words and phrases in millions of reviews to find recurring complaints (“late”, “cold”) per city or restaurant, before any fancy model is trained.

Reviews→count (word, city)→Complaint trends
ERROR 412WARN 3,980

2. Log-level and error-code counts

The same job with “error code” instead of “word”: count exceptions per service per hour across thousands of servers' logs.

App logs→(code, 1)→Alerts and reports
smoke test

3. Cluster smoke tests

After an upgrade or a new node, admins run the bundled wordcount example: if it succeeds, HDFS, YARN, the shuffle service and the history server all work together.

New cluster→examples wordcount→Go / no-go
term freq.

4. Search and NLP preprocessing

Term frequencies over large text collections (news archives, court judgments, documents in Indian languages) are the first step of search ranking (TF-IDF) and of building vocabularies for language models.

Documents→(term, 1) per doc→TF-IDF, vocabulary

Simplified; organisations differ in tools and policies.

7 Run the code

Word count, exactly like the Java version

The tokenizer below uses the same delimiters as WordCount.java (space, tab and .,!?;:"'(), after lowercasing), so for the same lines it gives the same counts as the real job.

DELIMS = " \t.,!?;:\"'()"

def tokens(line):                         # like java.util.StringTokenizer(line.toLowerCase(), DELIMS)
    word, out = "", []
    for ch in line.lower():
        if ch in DELIMS:
            if word: out.append(word)
            word = ""
        else:
            word += ch
    if word: out.append(word)
    return out

lines = ["the idli was fresh", "Why is the chai was cold?", "loved the vada pav was tasty!", "the chai was hot"]
pairs = [(w, 1) for line in lines for w in tokens(line)]       # map
print(len(pairs), "pairs, e.g.", pairs[:5])

counts = {}
for w, one in pairs:                                           # shuffle + reduce
    counts[w] = counts.get(w, 0) + one
for w in sorted(counts):
    print(w, counts[w])
20 pairs, e.g. [('the', 1), ('idli', 1), ('was', 1), ('fresh', 1), ('why', 1)] chai 2 cold 1 fresh 1 hot 1 idli 1 is 1 loved 1 pav 1 tasty 1 the 4 vada 1 was 4 why 1
  • for ch in line.lower():toLowerCase() first, so “Why” and “why” are one key.
  • if ch in DELIMS:Punctuation splits words just like spaces: “cold?” becomes “cold”.
  • pairs = [(w, 1) for line in lines for w in tokens(line)] # mapOne pair per word: our 2 lakh lines produced 1,033,238 pairs.

Run it on your PC: build, run and break word count

Use the Module 8 lab from Lesson 8.1. Create feedback.txt with make_feedback.py, copy it and WordCount.java into the namenode container, put the text into HDFS, then run wordcount_lab.sh inside the container. kill_task.sh runs on your PC because it needs docker exec into a worker.

Compile, package, run, read, fail

// WordCount.java: the classic first MapReduce program (new org.apache.hadoop.mapreduce API).
import java.io.IOException;
import java.util.StringTokenizer;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;

public class WordCount {

  public static class TokenizerMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    private static final IntWritable ONE = new IntWritable(1);
    private final Text word = new Text();

    @Override
    public void map(LongWritable offset, Text line, Context context) throws IOException, InterruptedException {
      StringTokenizer it = new StringTokenizer(line.toString().toLowerCase(), " \t.,!?;:\"'()");
      while (it.hasMoreTokens()) {
        word.set(it.nextToken());
        context.write(word, ONE);                 // ("chai", 1)
      }
    }
  }

  public static class IntSumReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
    private final IntWritable result = new IntWritable();

    @Override
    public void reduce(Text word, Iterable<IntWritable> ones, Context context) throws IOException, InterruptedException {
      int sum = 0;
      for (IntWritable one : ones) sum += one.get();
      result.set(sum);
      context.write(word, result);                // ("chai", 41873)
    }
  }

  public static void main(String[] args) throws Exception {
    Configuration conf = new Configuration();
    String[] rest = new GenericOptionsParser(conf, args).getRemainingArgs();
    if (rest.length != 2) { System.err.println("Usage: wordcount <in> <out>"); System.exit(2); }
    Job job = Job.getInstance(conf, "word count");
    job.setJarByClass(WordCount.class);
    job.setMapperClass(TokenizerMapper.class);
    job.setReducerClass(IntSumReducer.class);
    job.setOutputKeyClass(Text.class);
    job.setOutputValueClass(IntWritable.class);
    FileInputFormat.addInputPath(job, new Path(rest[0]));
    FileOutputFormat.setOutputPath(job, new Path(rest[1]));
    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }
}

// ---- make_feedback.py (run on your PC) ----
// # make_feedback.py: create feedback.txt, 2 lakh short canteen feedback lines (same file every time)
// import random
// random.seed(11)
// items = ["chai", "samosa", "vada pav", "poha", "idli", "dosa"]
// good = ["tasty", "hot", "fresh", "crispy", "perfect", "good", "nice", "amazing"]
// bad = ["cold", "late", "oily", "salty", "costly", "stale", "slow"]
// starts = ["the", "today the", "my", "loved the", "please improve the", "why is the"]
// with open("feedback.txt", "w") as f:
//     for _ in range(200_000):
//         item = random.choice(items)
//         words = random.choice(good if random.random() < 0.7 else bad)
//         f.write(f"{random.choice(starts)} {item} was {words}\n")
// print(open("feedback.txt").read().count("\n"), "lines")

// ---- wordcount_lab.sh (inside the namenode container) ----
// # wordcount_lab.sh: compile, package and run WordCount on YARN; read the result; make it fail on purpose
// cd /tmp/mr
// echo "== 1. compile and package"
// mkdir -p wc_classes
// javac -cp $(hadoop classpath) -d wc_classes WordCount.java && jar cf wc.jar -C wc_classes .
// jar tf wc.jar | grep class
// echo "== 2. run on feedback.txt"
// hdfs dfs -rm -r -f -skipTrash /out/wc > /dev/null 2>&1
// hadoop jar wc.jar WordCount /user/hadoop/data/feedback.txt /out/wc 2>&1 \
//   | grep -vE "resource-types|ResourceUtils|Executing with tokens|Disabling Erasure" | sed -E 's/^[0-9-]+ [0-9:]+ INFO  //'
// echo "== 3. the result"
// hdfs dfs -ls /out/wc | awk '{print $5, $8}'
// hdfs dfs -cat /out/wc/part-r-00000 | head -5
// echo "-- top 8 words"
// hdfs dfs -cat /out/wc/part-r-00000 | sort -k2,2nr | head -8
// echo "== 4. what YARN remembers"
// yarn application -list -appStates FINISHED 2>/dev/null | grep "word count" | awk -F'\t' '{print $1 " | " $2 " | " $6 " | " $7}'
// echo "== 5. run again into the same output folder"
// hadoop jar wc.jar WordCount /user/hadoop/data/feedback.txt /out/wc 2>&1 | grep -E "Exception" | head -1 | cut -c1-160
// echo "== 6. wrong input path"
// hadoop jar wc.jar WordCount /user/hadoop/data/feedbak.txt /out/wc2 2>&1 | grep -E "Exception" | head -1 | cut -c1-160

// In the mr-lab folder:
//   python3 make_feedback.py
//   docker cp feedback.txt lab8-namenode-1:/tmp/mr/
//   docker cp WordCount.java lab8-namenode-1:/tmp/mr/
//   docker cp wordcount_lab.sh lab8-namenode-1:/tmp/mr/
//   docker exec lab8-namenode-1 hdfs dfs -put feedback.txt /user/hadoop/data/   (run from /tmp/mr)
//   docker exec lab8-namenode-1 bash /tmp/mr/wordcount_lab.sh
== 1. compile and package WordCount$IntSumReducer.class WordCount$TokenizerMapper.class WordCount.class == 2. run on feedback.txt DefaultNoHARMFailoverProxyProvider:64 - Connecting to ResourceManager at resourcemanager/172.22.0.2:8032 FileInputFormat:303 - Total input files to process : 1 JobSubmitter:203 - number of splits:1 JobSubmitter:299 - Submitting tokens for job: job_1792601400460_0030 YarnClientImpl:356 - Submitted application application_1792601400460_0030 Job:1681 - The url to track the job: http://resourcemanager:8088/proxy/application_1792601400460_0030/ Job:1726 - Running job: job_1792601400460_0030 Job:1747 - Job job_1792601400460_0030 running in uber mode : false Job:1754 - map 0% reduce 0% Job:1754 - map 100% reduce 0% Job:1754 - map 100% reduce 100% Job:1765 - Job job_1792601400460_0030 completed successfully Job:1772 - Counters: 54 File System Counters FILE: Number of bytes read=11293396 FILE: Number of bytes written=23222269 FILE: Number of read operations=0 FILE: Number of large read operations=0 FILE: Number of write operations=0 HDFS: Number of bytes read=5094072 HDFS: Number of bytes written=350 HDFS: Number of read operations=8 HDFS: Number of large read operations=0 HDFS: Number of write operations=2 HDFS: Number of bytes read erasure-coded=0 Job Counters Launched map tasks=1 Launched reduce tasks=1 Data-local map tasks=1 Total time spent by all maps in occupied slots (ms)=6732 Total time spent by all reduces in occupied slots (ms)=6202 Total time spent by all map tasks (ms)=3366 Total time spent by all reduce tasks (ms)=3101 Total vcore-milliseconds taken by all map tasks=3366 Total vcore-milliseconds taken by all reduce tasks=3101 Total megabyte-milliseconds taken by all map tasks=1723392 Total megabyte-milliseconds taken by all reduce tasks=1587712 Map-Reduce Framework Map input records=200000 Map output records=1033238 Map output bytes=9226914 Map output materialized bytes=11293396 Input split bytes=110 Combine input records=0 Combine output records=0 Reduce input groups=31 Reduce shuffle bytes=11293396 Reduce input records=1033238 Reduce output records=31 Spilled Records=2066476 Shuffled Maps =1 Failed Shuffles=0 Merged Map outputs=1 GC time elapsed (ms)=83 CPU time spent (ms)=4220 Physical memory (bytes) snapshot=504164352 Virtual memory (bytes) snapshot=4358414336 Total committed heap usage (bytes)=273678336 Peak Map Physical memory (bytes)=298934272 Peak Map Virtual memory (bytes)=2176516096 Peak Reduce Physical memory (bytes)=205230080 Peak Reduce Virtual memory (bytes)=2181898240 Shuffle Errors BAD_ID=0 CONNECTION=0 IO_ERROR=0 WRONG_LENGTH=0 WRONG_MAP=0 WRONG_REDUCE=0 File Input Format Counters Bytes Read=5093962 File Output Format Counters Bytes Written=350 == 3. the result 0 /out/wc/_SUCCESS 350 /out/wc/part-r-00000 amazing 17687 chai 33431 cold 8452 costly 8410 crispy 17585 -- top 8 words was 200000 the 166921 loved 33570 dosa 33566 chai 33431 samosa 33431 improve 33398 please 33398 == 4. what YARN remembers application_1792601400460_0028 | word count | FINISHED | SUCCEEDED application_1792601400460_0030 | word count | FINISHED | SUCCEEDED == 5. run again into the same output folder Exception in thread "main" org.apache.hadoop.mapred.FileAlreadyExistsException: Output directory hdfs://namenode/out/wc already exists == 6. wrong input path Exception in thread "main" org.apache.hadoop.mapreduce.lib.input.InvalidInputException: Input path does not exist: hdfs://namenode/user/hadoop/data/feedbak.txt

Real output; only the timestamp prefix of each log line was removed by the script. Section 4 lists two applications because the job had been run once before; yours shows one.

Kill a running map task

# kill_task.sh (run on your PC, in the lab folder): kill a running map task and watch Hadoop retry it
docker exec lab8-namenode-1 bash -c 'cd /tmp/mr; for i in $(seq 20); do cat feedback.txt; done > feedback_x20.txt
  hdfs dfs -D dfs.blocksize=33554432 -put -f feedback_x20.txt /user/hadoop/data/ 2>/dev/null
  hdfs dfs -rm -r -f -skipTrash /out/wc_big > /dev/null 2>&1'
docker exec -d lab8-namenode-1 bash -c 'hadoop jar /tmp/mr/wc.jar WordCount /user/hadoop/data/feedback_x20.txt /out/wc_big > /tmp/wc_big.log 2>&1'
until docker exec lab8-worker2-1 pgrep -f "java.*YarnChild.*_m_" > /dev/null; do sleep 1; done
sleep 6                                              # let the map task get going
echo "map tasks running on worker2:"
docker exec lab8-worker2-1 pgrep -af "java.*YarnChild.*_m_" | grep -o "attempt_[0-9]*_[0-9]*_m_[0-9]*_[0-9]*" | sort -u
victim=$(docker exec lab8-worker2-1 pgrep -f "java.*YarnChild.*_m_" | head -1)
docker exec lab8-worker2-1 kill -9 $victim && echo "killed JVM $victim on worker2 at $(date +%T)"
until docker exec lab8-namenode-1 grep -qE "completed successfully|failed with state" /tmp/wc_big.log; do sleep 2; done
docker exec lab8-namenode-1 bash -c 'grep -E "number of splits|map [0-9]+%|Task Id|Error:|exit code|completed|Failed map tasks|Launched map tasks|Data-local|Rack-local" /tmp/wc_big.log | sed -E "s/^[0-9-]+ [0-9:]+ INFO  //" | cut -c1-150
  hdfs dfs -cat /out/wc_big/part-r-00000 | grep -wE "chai|was"
  echo "-- every attempt of this job"
  bash /tmp/mr/attempts.sh $(grep -o "job_[0-9_]*" /tmp/wc_big.log | head -1)'

# Needs attempts.sh (Lesson 8.1) in /tmp/mr in the namenode container.
# Run on your PC, in the mr-lab folder:  bash kill_task.sh
map tasks running on worker2: attempt_1792601400460_0033_m_000000_0 killed JVM 6151 on worker2 at 17:15:00 JobSubmitter:203 - number of splits:3 Job:1754 - map 0% reduce 0% Job:1793 - Task Id : attempt_1792601400460_0033_m_000000_0, Status : FAILED [2026-10-21 17:15:00.157]Container exited with a non-zero exit code 137. Job:1754 - map 26% reduce 0% Job:1754 - map 48% reduce 0% Job:1754 - map 56% reduce 0% Job:1754 - map 63% reduce 0% Job:1754 - map 67% reduce 0% Job:1754 - map 90% reduce 0% Job:1754 - map 100% reduce 0% Job:1754 - map 100% reduce 95% Job:1754 - map 100% reduce 100% Job:1765 - Job job_1792601400460_0033 completed successfully Failed map tasks=1 Launched map tasks=5 Data-local map tasks=3 chai 668620 was 4000000 -- every attempt of this job attempt_1792601400460_0033_m_000000_2 KILLED worker2 4.4 s Speculation: attempt_1792601400460_0033_m_000000_1 succeeded first! attempt_1792601400460_0033_m_000000_0 FAILED worker2 7.9 s [2026-10-21 17:15:00.155]Container killed on request. Exit code is 137 attempt_1792601400460_0033_m_000000_1 SUCCEEDED worker3 21.7 s attempt_1792601400460_0033_m_000001_0 SUCCEEDED worker3 27.1 s attempt_1792601400460_0033_m_000002_0 SUCCEEDED worker1 20.2 s attempt_1792601400460_0033_r_000000_0 SUCCEEDED worker3 17.1 s

Real output. The killed attempt _0 is reported FAILED (exit code 137); _1 succeeded on worker3; _2 was a speculative copy, killed when _1 won. Which worker and which map you hit will differ.

8 Line by line

Tokenizing one feedback line

Follow how the tokenizer turns a line with capitals and punctuation into clean words. This is the only logic in the mapper.

Worked examples: real problems, solved and traced in memory

Three end-to-end problems: sizing the shuffle, reading counters after a failure, and planning reruns.

Example 1BeginnerCounters · shuffle

How much data did the shuffle move?

Problem statement

Our job had 1,033,238 map output records and 9,226,914 map output bytes. Map output materialized bytes were 11,293,396. How many bytes per record were shuffled, and what is the extra over the raw bytes?

Input
1,033,238 records · 9,226,914 bytes · 11,293,396 materialized
Expected output
10.9 bytes per record shuffled, 8.9 raw
framing overhead: 2,066,482 bytes (2.0 per record)

Approach

  1. Divide materialized bytes by records.
  2. Compare with raw map output bytes.
  3. The extra is per-record framing (key and value lengths) and partition 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.

Example 2IntermediateFailures · counters

Read the counters after a failure

Problem statement

The 20× job had 3 splits. Counters: Launched map tasks=5, Failed map tasks=1, Data-local map tasks=3. Attempts: one FAILED, one KILLED by speculation. Explain why 5 launches for 3 maps, and how many map outputs the reducer used.

Input
3 splits · 5 launched · 1 failed · 1 killed
Expected output
extra attempts: 2 = 1 failed + 1 killed
map outputs used by the reducer: 3

Approach

  1. Successful attempts = number of maps (one each).
  2. Extra launches = failed + killed attempts.
  3. The reducer fetches only from successful attempts.

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 3AdvancedOperations · reruns

Make a daily job safe to rerun

Problem statement

A daily word count writes to /out/wc. It fails with FileAlreadyExistsException on day 2. Design output paths so that every day has its own folder, reruns of the same day are explicit, and the driver can check before submitting.

Input
dates · existing folders
Expected output
/out/wc/dt=2026-10-19 -> skip (already done)
/out/wc/dt=2026-10-20 -> delete half-written folder, then run
/out/wc/dt=2026-10-21 -> run

Approach

  1. Use a dated path: /out/wc/dt=YYYY-MM-DD.
  2. If it exists with _SUCCESS, skip; if it exists without _SUCCESS, it is a failed run: remove and rerun.
  3. Simulate the decision for three days.

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 8.6 in one screen

  • A job = Mapper + Reducer + driver; nested classes must be static; mapper output types = reducer input types.
  • Build: javac -cp $(hadoop classpath) → jar cf wc.jar; run: hadoop jar wc.jar WordCount in out; -D key=value via GenericOptionsParser/ToolRunner.
  • Submission: output check → splits → staging dir in HDFS → RM → MRAppMaster container → task containers (YarnChild JVMs).
  • Lab: 2 lakh lines → 1,033,238 pairs → 31 words; top: was 200000, the 166921.
  • Errors before start: FileAlreadyExistsException (output exists), InvalidInputException (input missing).
  • Failure: kill -9 → exit code 137 → attempt FAILED → rerun elsewhere (max 4 attempts); same counts.
  • New API org.apache.hadoop.mapreduce (Context, Iterable) vs old mapred (OutputCollector, Reporter, JobConf).
  • UIs: ResourceManager :8088 while running, JobHistory :19888 after; yarn logs -applicationId for task logs.

Interview and viva questions

How do you run a MapReduce program in Hadoop?

Compile the Java classes against the Hadoop libraries (javac -cp $(hadoop classpath)), package them into a jar (jar cf app.jar ...), make sure the input is in HDFS and the output folder does not exist, then run hadoop jar app.jar MainClass <input> <output>. Progress and counters are printed in the terminal; the result is in the output folder as part-r-NNNNN files.

What happens when you submit a MapReduce job to YARN?

The client checks the output path, computes input splits and uploads the jar, job.xml and split files to a staging directory in HDFS, then submits an application to the ResourceManager. The RM launches the MRAppMaster in a container; the AM requests containers for map and reduce tasks, NodeManagers launch them, the AM tracks progress and failures, and on success commits the output and passes the job history to the JobHistory Server.

What is the difference between the mapred and mapreduce APIs?

org.apache.hadoop.mapred is the original (old) API with Mapper and Reducer interfaces, OutputCollector, Reporter and JobConf. org.apache.hadoop.mapreduce is the newer API with abstract Mapper and Reducer classes, a Context object, Iterable values, setup/cleanup methods and the Job class. Both still work; new code should use org.apache.hadoop.mapreduce and never mix classes from the two.

What happens if a map task fails in Hadoop?

The MRAppMaster notices the failed attempt (an exception, a non-zero container exit code, or no progress for mapreduce.task.timeout) and schedules a new attempt, preferably on another node. A task may be attempted mapreduce.map.maxattempts times (4 by default) before the job fails. Output of failed attempts is discarded, so results are not affected.

Why does my job fail with “Output directory already exists”?

FileOutputFormat refuses to write into an existing directory so a job never overwrites earlier results by accident. Delete the folder with hdfs dfs -rm -r, or better, write each run to a new path such as a dated folder.

Practice problems

Word count exercises using the real counters and output from our lab.

P1Easy

Words per line

2,00,000 lines produced 1,033,238 map output records. How many words per line on average?

Input
1,033,238 / 200,000
Expected output
5.17 words per line
Show solution and explanation
print(round(1033238 / 200000, 2), "words per line")
5.17 words per line

How it works: About 5.17 words per line, matching lines like “the idli was fresh” (4) and “today the poha was crispy” (5).

P2Easy

Scale check after a failure

The 20× run gave chai 668620 and was 4000000. Check both against 20 × the small run.

Input
chai 33431 · was 200000
Expected output
True True
Show solution and explanation
print(33431 * 20 == 668620, 200000 * 20 == 4000000)
True True

How it works: True, True: the killed attempt left no trace in the answer.

P3Easy

Exit codes

Decode exit code 137 and 143 for a container (128 + signal number).

Input
137 · 143
Expected output
137 -> signal 9 SIGKILL
143 -> signal 15 SIGTERM
Show solution and explanation
for code in (137, 143):
    sig = code - 128
    print(code, "-> signal", sig, {9: "SIGKILL", 15: "SIGTERM"}[sig])
137 -> signal 9 SIGKILL 143 -> signal 15 SIGTERM

How it works: 137 = SIGKILL (kill -9, or YARN killing a container over its memory limit); 143 = SIGTERM (a polite stop request, e.g. when YARN stops a container it no longer needs).

P4Medium

Good vs bad words

Using real counts, compute the share of feedback words that are positive.

Input
8 good words · 7 bad words
Expected output
140137 59863 70.1% positive
Show solution and explanation
good = {"tasty": 17338, "hot": 17294, "fresh": 17426, "crispy": 17585, "perfect": 17706, "good": 17616, "nice": 17485, "amazing": 17687}
bad = {"cold": 8452, "late": 8792, "oily": 8444, "salty": 8541, "costly": 8410, "stale": 8617, "slow": 8607}
g, b = sum(good.values()), sum(bad.values())
print(g, b, f"{g / (g + b):.1%} positive")
140137 59863 70.1% positive

How it works: 140,137 good vs 59,863 bad: about 70% positive, matching the 0.7 probability used in make_feedback.py.

P5Medium

Join the two-word item

“vada pav” is split into “vada” and “pav”. Write a tokenizer that keeps known two-word phrases together.

Input
one line
Expected output
['loved', 'the', 'vada pav', 'was', 'tasty']
Show solution and explanation
PHRASES = {"vada pav"}
def tokens(line):
    w = line.lower().split()
    out, i = [], 0
    while i < len(w):
        if i + 1 < len(w) and w[i] + " " + w[i + 1] in PHRASES:
            out.append(w[i] + " " + w[i + 1]); i += 2
        else:
            out.append(w[i]); i += 1
    return out
print(tokens("loved the vada pav was tasty"))
['loved', 'the', 'vada pav', 'was', 'tasty']

How it works: Mapper logic decides what a “word” is; Hadoop just counts whatever keys you emit.

P6Medium

How many reducers' files?

The same job is run with -D mapreduce.job.reduces=4. How many part files, and can one word appear in two of them?

Input
4 reducers · 31 words
Expected output
4 part-r files: part-r-00000 .. part-r-00003
each word goes to exactly one reducer: (hash(word) & MAX) % 4
Show solution and explanation
print(4, "part-r files: part-r-00000 .. part-r-00003")
print("each word goes to exactly one reducer: (hash(word) & MAX) % 4")
4 part-r files: part-r-00000 .. part-r-00003 each word goes to exactly one reducer: (hash(word) & MAX) % 4

How it works: 4 files; a word never appears in two files, because the partitioner sends every pair of one key to the same reducer (Lesson 8.5).

P7Business problem

Retry cost

A nightly job has 2,000 map tasks of 60 s each. 1% of attempts fail once and are rerun. How much extra cluster time does that cost, and is it worth fixing?

Input
2,000 tasks · 60 s · 1% retries
Expected output
20 extra task-minutes per night (1% of the job)
Show solution and explanation
extra = 2000 * 0.01 * 60
print(f"{extra / 60:.0f} extra task-minutes per night ({extra / (2000 * 60):.0%} of the job)")
20 extra task-minutes per night (1% of the job)

How it works: 20 task-minutes, 1% overhead: retries are cheap insurance. Fix the cause only if failures cluster on one node or grow.

P8Advanced

Word count with a combiner (preview)

Simulate 3 map tasks; each first sums its own (word, 1) pairs (a combiner) before the shuffle. Count shuffled records with and without the combiner.

Input
3 splits
Expected output
shuffled without combiner: 20 | with combiner: 15
Show solution and explanation
splits = [["the chai was hot", "the chai was cold"], ["the dosa was hot"], ["the chai was late", "the idli was hot"]]
plain = combined = 0
for split in splits:
    words = [w for line in split for w in line.split()]
    plain += len(words)
    combined += len(set(words))
print("shuffled without combiner:", plain, "| with combiner:", combined)
shuffled without combiner: 20 | with combiner: 15

How it works: 20 vs 15 here; on our real job 1,033,238 records would shrink to at most 31 per map task. That is Lesson 9.1.

Real-world scenario questions (asked in company interviews)

Interviewers love asking you to walk through a job end to end, and what happens when it breaks.

Concepts · flowFresher

“What happens after you type hadoop jar?”

A favourite follow-up after word count.

The interviewer is checking: the YARN execution flow.

Show a strong answer
  • The driver configures a Job; the client checks the output path, computes splits and uploads jar, job.xml and splits to the HDFS staging directory.
  • The ResourceManager starts the MRAppMaster in a container; the AM requests containers for maps (near their data) and reducers.
  • NodeManagers launch task JVMs; maps run, shuffle, reducers write part files; the committer writes _SUCCESS; history goes to the JobHistory Server.
Debugging · errorsFresher

Job fails with ClassNotFoundException: WordCount$TokenizerMapper

It ran fine in local mode but fails on the cluster.

The interviewer is checking: how code reaches the tasks.

Show a strong answer
  • The tasks run on other machines and only see what is in the job jar: check that setJarByClass is called and the jar contains the $ classes (jar tf).
  • Third-party libraries must be shipped too: -libjars (with ToolRunner), a fat jar, or the distributed cache.
  • Make sure nested Mapper/Reducer classes are public static.
Operations · failuresExperienced

Map tasks keep failing with exit code 137 on a big input

Nobody is running kill -9. The job eventually fails after 4 attempts.

The interviewer is checking: container memory limits.

Show a strong answer
  • 137 means SIGKILL; YARN kills containers that exceed their memory (“running beyond physical memory limits” in the diagnostics), or the OS OOM killer does.
  • Check yarn logs and the attempt diagnostics; raise mapreduce.map.memory.mb and keep mapreduce.map.java.opts (-Xmx) at about 80% of it.
  • Also look for a mapper that holds too much in memory (big HashMaps) and fix the code.
Design · pipelinesExperienced

“Top 10 words by count” with MapReduce

Word count output is sorted by word, not count.

The interviewer is checking: chaining jobs.

Show a strong answer
  • Job 1: word count. Job 2: each map keeps a local top 10 (counts are already final per word) and one reducer merges them.
  • Or swap key and value in job 2 and sort counts in descending order with a custom comparator and one reducer.
  • For small outputs like our 31 lines, hdfs dfs -cat | sort -k2,2nr | head is enough.