Five lakh orders, no output file, all the answers
The canteen manager wants three quick numbers from September: how many orders per category, how busy each hour was, and how much money each category made. Riya starts writing a reducer. Arjun Sir stops her: “You don't need an output file for twenty numbers. Let the map tasks count, and read the counts in the driver.” The job finishes with no part files at all, and prints every answer. Then Kabir adds “one counter per student, just to see”, and the same job dies after a few seconds.
Sir, so counters are just a free way to get totals?
Almost. Every task keeps its own little counters, sends them to the MRAppMaster with its heartbeat, and the framework adds them up. You already read 54 of them after your first job in Lesson 8.1, and you made your own in Lesson 8.3 to count bad records.
Then why did my per-student version crash? It's the same code with one more line.
Because counters live in memory in every task, the MRAppMaster and the JobHistory Server. Hadoop allows only 120 of them per job. 2,000 students is a dataset, not a counter. Today we see what counters are good for, how to read them in the driver, how to make a job fail on bad data, and where the limit bites.
Counters
A counter in Hadoop MapReduce is a named, job-wide long integer that tasks increment while they run and the framework aggregates. Counters are organised in groups. Hadoop maintains built-in counters (file system, job, task/framework, input and output format, shuffle errors); programs define custom counters either as Java enum constants (the enum class is the group) or as dynamic counters named by two strings, group and counter, at run time. Each task attempt keeps its counters locally and reports them to the ApplicationMaster with its progress updates; the job's final values are the sum over the successful attempt of every task, so counts from failed or killed attempts are not included. The driver can read the final values through Job.getCounters(). The number of counters per job is limited (mapreduce.job.counters.max, 120 by default), because every counter is kept in memory by tasks, the ApplicationMaster and the JobHistory Server.
Counters are tally marks every task makes on its own sheet. Hadoop collects the sheets, adds them up, and gives you the totals at the end. Use them for a few dozen facts about the job (records read, bad rows, rows per category), never for one tally per customer.
Words you'll meet in this lesson
ctx.getCounter(Category.DRINK): names checked by the compiler.ctx.getCounter("Orders by hour", "14"): names chosen at run time.mapreduce.job.counters.max = 120 per job by default.From our lab (Hadoop 3.5.0, 5 lakh student orders, 3 map tasks)
| Enum counters | DRINK 190,033 · SNACK 139,638 · BREAKFAST 90,110 · MEAL 80,219 (sum 5,00,000 = Map input records) |
|---|---|
| Dynamic counters | 13 “Orders by hour” counters, busiest 14:00 with 62,213 orders, quietest 20:00 with 18,724; 4 “Revenue (Rs)” counters, MEAL ₹46,70,075 highest |
| Output | None: NullOutputFormat, map-only, exit code 0. Revenue matches the Python Streaming job of Lesson 9.8 to the rupee |
| Too many | One counter per student (2,000): exit code 1, “LimitExceededException: Too many counters: 121 max=120”, job FAILED |
Counters at three levels
Beginner Tally marks on many sheets
- Each map task is a clerk with a tally sheet: one line per category, a mark for every order.
- From time to time the clerk shows the sheet to the supervisor (the MRAppMaster), who keeps a running total.
- At the end the supervisor hands you the totals. You never read the orders yourself.
- A sheet for each of 2,000 students would not fit the supervisor's desk. Hadoop's desk holds 120 lines.
Intermediate Built-in, enum and dynamic
- Built-in: 54 of them printed after our first job (Lesson 8.1). The most useful: Map input/output records, Reduce input groups, Spilled Records, HDFS bytes read/written, Launched/Failed/Killed tasks, Data-local map tasks.
- Enum:
enum Category { DRINK, SNACK, BREAKFAST, MEAL, UNKNOWN }thenctx.getCounter(Category.DRINK).increment(1). The group name is the enum's class name,OrderCounters$Category. - Dynamic:
ctx.getCounter("Orders by hour", "14").increment(1). Flexible, but a typo silently makes a new counter, and the number of names must stay small. - Reading: after
waitForCompletion,job.getCounters().findCounter(...)or loop overgetGroup(name).
Advanced What the framework guarantees
- Final counters come only from the successful attempt of each task. A map that failed half-way and was retried is not counted twice, and the killed copy from speculative execution adds nothing.
- While the job runs, counters are approximate (they arrive with heartbeats). Decide on final values only.
- Every counter costs memory in every task, the MRAppMaster and the JobHistory Server, and travels in every progress report. That is why
mapreduce.job.counters.maxis 120 and the number of groups is limited (50 by default). - Counters are not a side channel for results. Anything keyed by a value with many possibilities (student, item, URL) belongs in the job's output.
Exam tip: For “What are counters in MapReduce? Give an example”, name the two kinds of custom counters (enum and dynamic) with one line of code each, two built-in counters (Map input records, Reduce input groups), and one use: counting malformed records so the driver can fail the job.
The counter groups Hadoop prints
| Group | Typical counters | What it tells you |
|---|---|---|
| File System Counters | HDFS: Number of bytes read / written, FILE: bytes read / written | Input size and how much intermediate data hit local disk |
| Job Counters | Launched / Failed / Killed map tasks, Data-local map tasks, total time and MB-ms | Retries, speculation, locality, resource use |
| Map-Reduce Framework | Map input / output records, Combine input / output records, Reduce input groups, Spilled Records, GC time, CPU time | Data flow through each phase (Lessons 8.3 – 9.1) |
| File Input / Output Format Counters | Bytes Read, Bytes Written | What the InputFormat read and the OutputFormat wrote |
| Shuffle Errors | BAD_ID, CONNECTION, IO_ERROR, WRONG_LENGTH, WRONG_MAP, WRONG_REDUCE | Problems while reducers fetch map output |
| Your own groups | enum constants, or (group, name) strings | Business and data-quality facts |
Good and bad uses of custom counters
Data quality
Malformed lines, missing fields, unknown items. Lesson 8.3: HEADER 42, WRONG_FIELDS 94, BAD_AMOUNT 107.
goodSmall category totals
Orders per category, per hour, per status code: a bounded, small set of names.
goodJoin diagnostics
Rows that found or did not find a partner (Lesson 9.5: ORDERS_WITHOUT_MENU 40,364).
goodOne per key
Per student, per product, per URL: unbounded names hit the 120 limit or bloat memory. Write output instead.
badCounters are visible in many places: printed by waitForCompletion(true), in the ResourceManager UI while running (port 8088), in the JobHistory UI and REST API afterwards (port 19888), and from the shell with mapred job -counter <job-id> <group> <name>.
A job whose only result is its counters
Run on the Module 8 lab cluster with counters_lab.sh (in “Run it on your PC”). The data is student_orders.csv: 5 lakh orders, 2,000 students, September 2026.
Count in the mapper
A counter is a long, so it can add more than 1 at a time: the revenue counters add each order's amount. The mapper writes no key-value pairs at all.ctx.getCounter(c).increment(1); // enum: Category.DRINK … ctx.getCounter("Orders by hour", f[2].substring(11, 13)).increment(1); // dynamic ctx.getCounter("Revenue (Rs)", c.name()).increment(Long.parseLong(f[4]));Write nothing
No reducer, no output folder, no part files. Withjob.setNumReduceTasks(0); job.setOutputFormatClass(NullOutputFormat.class); // no output files at all boolean ok = job.waitForCompletion(false); // false: don't print progress and counterswaitForCompletion(false)Hadoop doesn't print its own counter dump, so the driver prints exactly what it wants.Read the counters in the driver
hadoop jar mr9.jar OrderCounters /user/hadoop/data/student_orders.csvexit code: 0 job SUCCEEDED, map input records = 500000 [OrderCounters$Category] BREAKFAST 90,110 DRINK 190,033 MEAL 80,219 SNACK 139,638findCounter(TaskCounter.MAP_INPUT_RECORDS)reads a built-in counter;getGroup(Category.class.getName())lists our enum group. The four categories add up to exactly 5,00,000.Dynamic counters: hours and money
The hour names (08 … 20) were invented while the job ran. Lunch at 14:00 is the busiest hour; drinks are the most orders but meals bring the most money.[Orders by hour] 08 55,852 13 56,026 14 62,213 20 18,724 [Revenue (Rs)] BREAKFAST 2,476,620 DRINK 3,052,315 MEAL 4,670,075 SNACK 2,652,992Use a counter as a quality gate
UNKNOWN was 0, so the exit code was 0. In a nightly pipeline (Oozie, Airflow) a non-zero exit stops the next step from loading bad data.if (cs.findCounter(Category.UNKNOWN).getValue() > 0) { System.out.println("unknown items found!"); System.exit(2); // the scheduler sees a failure }One counter per student
Each map task met hundreds of students within seconds; at the 121st counter the task threw an exception. Every retry failed the same way; once one map task had failed 4 times, the job was failed and the other attempts killed. Because only successful attempts count, the driver read 0 map input records.hadoop jar mr9.jar OrderCounters -D counters.perStudent=true /user/hadoop/data/student_orders.csvexit code: 1 job FAILED, map input records = 0 Diagnostics : Task failed task_1792643020992_0036_m_000002 Error: org.apache.hadoop.mapreduce.counters.LimitExceededException: Too many counters: 121 max=120
Don't raise mapreduce.job.counters.max to make per-key counters “work”. Counters are held in memory by every task, the MRAppMaster and the JobHistory Server, and sent with every heartbeat. Per-key numbers are the job's output: write them with a reducer.
How counters travel
Six steps: tasks count locally, report with heartbeats, a failed attempt is thrown away, the driver reads the totals, and a per-student counter breaks the limit.
- The job is submitted; the driver chose not to print progress.
- While it runs, counters can be watched in the ResourceManager UI.
- If an attempt fails and is retried, the final counts are still right.
- The driver prints the category counters it read with getCounters().
- Dynamic counters give the busiest hour and revenue; the quality gate passes.
- One counter per student: the 121st counter fails every attempt and the job.
- Each map task counts in its own memory as it reads orders.
- With each progress update, tasks send their counters to the MRAppMaster.
- A failed attempt's counts are thrown away; its retry counts again from zero.
- When the job ends, the final totals go to the driver and are archived.
- The driver checks a quality counter and chooses its exit code.
- Per-student names pass 120 counters in every task, so every attempt fails.
Counters at work
1. Quality gates in nightly pipelines
An ingestion job counts malformed rows; the driver compares them with the input count and exits non-zero above a threshold, so the scheduler doesn't publish a broken day.
2. Job monitoring and alerting
Operations teams scrape counters from the JobHistory REST API (records in and out, spilled records, GC time) and alert when a daily job suddenly reads half its usual input.
3. Reconciliation
Joins and lookups count rows that found no partner, so finance teams can reconcile totals before reports go out.
4. The same idea in Spark
Spark's accumulators play the role of counters, and Hive shows MapReduce or Tez counters for every query. The rules carry over: few names, read final values only.
Simplified; organisations differ in tools and policies.
Simulate how task counters become job counters
Each map task attempt keeps its own counters. The job total is the sum over the successful attempt of each task. Here map 1's first attempt fails half-way and is retried.
attempts = [
# (task, attempt, state, counters this attempt incremented)
("m_0", 0, "SUCCEEDED", {"DRINK": 64000, "SNACK": 46500, "MEAL": 26700}),
("m_1", 0, "FAILED", {"DRINK": 30000, "SNACK": 21000, "MEAL": 12000}), # died half-way
("m_1", 1, "SUCCEEDED", {"DRINK": 63500, "SNACK": 46800, "MEAL": 26900}),
("m_2", 0, "SUCCEEDED", {"DRINK": 62533, "SNACK": 46338, "MEAL": 26619}),
]
job = {}
for task, n, state, counters in attempts:
if state != "SUCCEEDED":
print(f"{task} attempt {n}: {state}, its counts are dropped")
continue
for name, value in counters.items():
job[name] = job.get(name, 0) + value
for name in sorted(job):
print(f"{name:6} {job[name]:,}")if state != "SUCCEEDED":Only successful attempts count, so a retried task is never counted twice.job[name] = job.get(name, 0) + valueThe framework adds the same-named counter from every task: DRINK becomes 190,033 as in our lab.
Run it on your PC: a counters-only job, then too many counters
Uses the Module 8 lab cluster (Lesson 8.1). student_orders.csv and menu.csv come from make_student_orders.py, shared by all Module 9 lessons. Copy the files into the namenode container, compile the Module 9 programs into mr9.jar, then run the script.
Enum and dynamic counters, read by the driver
// OrderCounters.java: a map-only job that only counts. Enum counters, dynamic counters,
// and reading every counter back in the driver after the job.
// -D counters.perStudent=true adds one dynamic counter per student (to hit the counter limit)
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Counter;
import org.apache.hadoop.mapreduce.CounterGroup;
import org.apache.hadoop.mapreduce.Counters;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.TaskCounter;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.NullOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;
public class OrderCounters {
enum Category { DRINK, SNACK, BREAKFAST, MEAL, UNKNOWN }
public static class M extends Mapper<LongWritable, Text, NullWritable, NullWritable> {
private boolean perStudent;
@Override
protected void setup(Context ctx) { perStudent = ctx.getConfiguration().getBoolean("counters.perStudent", false); }
@Override
protected void map(LongWritable k, Text line, Context ctx) {
String[] f = line.toString().split(","); // order_id,student_id,time,item,amount
String item = f[3];
Category c;
switch (item) {
case "chai": case "coffee": c = Category.DRINK; break;
case "samosa": case "vada pav": c = Category.SNACK; break;
case "poha": case "idli": c = Category.BREAKFAST; break;
case "dosa": case "thali": c = Category.MEAL; break;
default: c = Category.UNKNOWN;
}
ctx.getCounter(c).increment(1); // enum counter
ctx.getCounter("Orders by hour", f[2].substring(11, 13)).increment(1); // dynamic counter: group, name
ctx.getCounter("Revenue (Rs)", c.name()).increment(Long.parseLong(f[4]));
if (perStudent) ctx.getCounter("Orders by student", f[1]).increment(1);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
String[] rest = new GenericOptionsParser(conf, args).getRemainingArgs();
Job job = Job.getInstance(conf, "order counters");
job.setJarByClass(OrderCounters.class);
job.setMapperClass(M.class);
job.setNumReduceTasks(0);
job.setOutputFormatClass(NullOutputFormat.class); // no output files at all
FileInputFormat.addInputPath(job, new Path(rest[0]));
boolean ok = job.waitForCompletion(false); // false: don't print progress and counters
// The driver reads the final counters and decides what to do with them
Counters cs = job.getCounters();
long in = cs.findCounter(TaskCounter.MAP_INPUT_RECORDS).getValue();
System.out.println("job " + (ok ? "SUCCEEDED" : "FAILED") + ", map input records = " + in);
for (String group : new String[] {OrderCounters.Category.class.getName(), "Orders by hour", "Revenue (Rs)"}) {
CounterGroup g = cs.getGroup(group);
System.out.println("[" + g.getDisplayName() + "]");
for (Counter c : g) System.out.printf(" %-10s %,d%n", c.getDisplayName(), c.getValue());
}
if (cs.findCounter(Category.UNKNOWN).getValue() > 0) { System.out.println("unknown items found!"); System.exit(2); }
System.exit(ok ? 0 : 1);
}
}
// In the mr-lab folder:
// python3 make_student_orders.py
// docker exec lab8-namenode-1 mkdir -p /tmp/mr9
// docker cp OrderCounters.java lab8-namenode-1:/tmp/mr9/
// docker cp student_orders.csv lab8-namenode-1:/tmp/mr9/
// docker exec -it lab8-namenode-1 bash
// Inside the container:
// cd /tmp/mr9
// hdfs dfs -D dfs.blocksize=8388608 -put student_orders.csv /user/hadoop/data/
// mkdir -p classes && javac -cp $(hadoop classpath) -d classes *.java && jar cf mr9.jar -C classes .
// hadoop jar mr9.jar OrderCounters /user/hadoop/data/student_orders.csvReal output. The file is stored with 8 MB blocks so the job has 3 map tasks; the counters are the sum of all three. Nothing is written to HDFS.
The whole lab, including the counter limit
# counters_lab.sh: enum and dynamic counters read back by the driver; then too many counters
cd /tmp/mr9
D=/user/hadoop/data/student_orders.csv
echo "== OrderCounters (map-only, NullOutputFormat: no output files)"
hadoop jar mr9.jar OrderCounters $D > /tmp/oc.log 2>&1; echo "exit code: $?"
grep -vE "INFO|WARN" /tmp/oc.log
echo "== one dynamic counter per student (2,000 of them)"
hadoop jar mr9.jar OrderCounters -D counters.perStudent=true $D > /tmp/oc2.log 2>&1; echo "exit code: $?"
grep "job FAILED" /tmp/oc2.log
app=$(grep -o "application_[0-9_]*" /tmp/oc2.log | head -1)
yarn application -status $app 2>/dev/null | grep -A1 "Diagnostics :" | sed 's/^\s*//'
echo "-- why the map attempts failed (JobHistory REST)"
J=job_${app#application_}
curl -s http://historyserver:19888/ws/v1/history/mapreduce/jobs/$J/tasks/${J/job/task}_m_000000/attempts \
| python3 -c 'import sys, json; t = json.load(sys.stdin)["taskAttempts"]["taskAttempt"]; t = t if isinstance(t, list) else [t]; print(next(a for a in t if a["state"] == "FAILED")["diagnostics"].splitlines()[0])'
echo "== the limit"
hdfs getconf -confKey mapreduce.job.counters.max
# Inside the namenode container, in /tmp/mr9 (attempts are read from the JobHistory Server REST API):
# bash counters_lab.shReal output. With per-student counters every map attempt fails within seconds with LimitExceededException; after one task fails 4 times the job is failed. The limit is mapreduce.job.counters.max (120).
Counting categories the way the mapper does
Follow four orders updating an enum-style group and a dynamic “Revenue” group, exactly like the three getCounter(...).increment(...) lines in OrderCounters.java.
Worked examples: real problems, solved and traced in memory
Three counter problems: reading a quality gate, designing counter names, and explaining a surprising number.
What do these counters say about the job?
Problem statement
From Lesson 8.3: Map input records = 300,302; GOOD = 300,059; HEADER = 42; WRONG_FIELDS = 94; BAD_AMOUNT = 107. Do the custom counters explain every input record? What share of the input was bad?
five counters from CleanOrders
explained: True bad rows 243 = 0.081% of input
Approach
- Add the custom counters and compare with Map input records.
- Bad = everything except GOOD.
- Share = bad / input.
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.
Counters or output?
Problem statement
A team wants, for a 10-crore-row clickstream job: (a) number of rows with a missing user id, (b) clicks per page for 40,000 pages, (c) clicks per device type (mobile, desktop, tablet, other), (d) clicks per country (about 200). Which belong in counters?
4 requested statistics
missing user id 1 names -> counter clicks per device 4 names -> counter clicks per country 200 names -> job output clicks per page 40000 names -> job output
Approach
- Counters suit a small, fixed set of names.
- Count the names each statistic needs.
- Compare with the 120-counter limit and the built-ins already used (about 50).
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.
Why didn't the speculative copy double the count?
Problem statement
In Lesson 8.1, 5 map attempts ran for 4 splits (one speculative copy was killed after processing about a quarter of its split). Map input records was exactly 27,000,000, the true number of lines. Show why, using attempt states.
4 successful attempts + 1 killed
all attempts 28,700,000 · successful only 27,000,000
Approach
- List what every attempt read.
- Sum all attempts (wrong) and only successful ones (what Hadoop does).
- Compare with 27,000,000.
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.4 in one screen
- Counter = job-wide long in a group; tasks increment, the framework sums.
- Built-in groups: File System, Job Counters, Map-Reduce Framework, File Input/Output Format, Shuffle Errors.
- Enum counters:
getCounter(Category.DRINK). Dynamic:getCounter("group", "name"). - Final values = sum over each task's successful attempt; failed and killed attempts don't count.
- Driver:
job.getCounters(),findCounter,getGroup; exit non-zero as a quality gate. - Lab: DRINK 190,033 · SNACK 139,638 · BREAKFAST 90,110 · MEAL 80,219; busiest hour 14:00 (62,213); no output files.
- Limit:
mapreduce.job.counters.max= 120 (groups 50). Per-student counters → “Too many counters: 121 max=120”.
Interview and viva questions
What are counters in Hadoop MapReduce?
Counters are named long values that tasks increment while a job runs and the framework aggregates into job totals. Hadoop provides built-in counters (records read and written, bytes, spills, launched and failed tasks) and lets programs define their own, typically to count categories or data-quality problems.
What is the difference between enum counters and dynamic counters?
Enum counters are Java enum constants passed to context.getCounter(Enum); the enum class becomes the group and names are checked at compile time. Dynamic counters are created with context.getCounter(String group, String name), so names can be decided at run time, for example one counter per hour. Dynamic names must still stay few, because of the counter limit.
How do you read counters in the driver?
After job.waitForCompletion() returns, call job.getCounters() to get a Counters object. Use findCounter(TaskCounter.MAP_INPUT_RECORDS) or findCounter(MyEnum.X) for a single value, or getGroup("name") to loop over a group. The driver can then print the values or decide its exit code.
What is the limit on the number of counters?
mapreduce.job.counters.max limits a job to 120 counters by default, and mapreduce.job.counters.groups.max to 50 groups. Exceeding it throws LimitExceededException in the task (“Too many counters: 121 max=120” in our lab) and the job fails. The limit exists because counters are kept in memory by tasks, the ApplicationMaster and the JobHistory Server.
Are counters from failed task attempts included?
No. The job's final counters are aggregated from the successful attempt of each task, so retries and speculative copies do not inflate the totals. While a job is still running, the displayed values are progress snapshots and can change.
Practice problems
Counter exercises using the real numbers from the lab.
Do the categories add up?
Check that the four category counters add up to Map input records (5,00,000).
90,110 · 190,033 · 80,219 · 139,638
True
Show solution and explanation
print(90110 + 190033 + 80219 + 139638 == 500000)How it works: True: every order fell in exactly one category, and UNKNOWN was 0.
Average order value per category
Divide each revenue counter by its order counter.
revenue and order counters
BREAKFAST Rs 27.48 DRINK Rs 16.06 MEAL Rs 58.22 SNACK Rs 19.00
Show solution and explanation
orders = {"BREAKFAST": 90110, "DRINK": 190033, "MEAL": 80219, "SNACK": 139638}
revenue = {"BREAKFAST": 2476620, "DRINK": 3052315, "MEAL": 4670075, "SNACK": 2652992}
for c in orders:
print(f"{c:9} Rs {revenue[c] / orders[c]:.2f}")How it works: Meals average about ₹58 per order, drinks about ₹16: drinks lead in orders, meals in money.
Peak hour share
What share of all orders came in the 14:00 hour?
62,213 of 5,00,000
12.4%
Show solution and explanation
print(f"{62213 / 500000:.1%}")How it works: 12.4%: one hour in thirteen takes an eighth of the day's orders.
Busiest and quietest hour
From the “Orders by hour” group, find the busiest and quietest hours and their ratio.
13 hourly counters
14 62213 20 18724 ratio 3.3x
Show solution and explanation
hours = {"08": 55852, "09": 43655, "10": 24986, "11": 25002, "12": 31091, "13": 56026, "14": 62213,
"15": 37945, "16": 25218, "17": 37777, "18": 50133, "19": 31378, "20": 18724}
busy = max(hours, key=hours.get)
quiet = min(hours, key=hours.get)
print(busy, hours[busy], quiet, hours[quiet], f"ratio {hours[busy] / hours[quiet]:.1f}x")How it works: 14:00 vs 20:00, about 3.3 times busier: the canteen needs most staff around lunch, least at closing time.
Counter budget
A job has 54 built-in counters. You add 5 enum quality counters and an hour group (24 names). How many counters are left before the limit?
54 + 5 + 24, limit 120
37 left
Show solution and explanation
print(120 - (54 + 5 + 24), "left")How it works: 37 left. Enough for small groups, nowhere near enough for one per customer.
Merge with a retry
Merge these attempt counters into job counters, ignoring failed and killed attempts.
4 attempts
{'BAD': 8}Show solution and explanation
attempts = [("SUCCEEDED", {"BAD": 3}), ("FAILED", {"BAD": 2}), ("SUCCEEDED", {"BAD": 5}), ("KILLED", {"BAD": 1})]
total = {}
for state, cs in attempts:
if state == "SUCCEEDED":
for k, v in cs.items():
total[k] = total.get(k, 0) + v
print(total)How it works: BAD = 8. Adding all attempts would wrongly give 11.
Is today's load normal?
Yesterday the job read 5,00,000 orders; today 2,10,000. Alert if today is below 70% of yesterday. What should happen?
500,000 vs 210,000
today is 42% of yesterday -> ALERT: check the upstream feed
Show solution and explanation
y, t = 500000, 210000
print(f"today is {t / y:.0%} of yesterday ->", "ALERT: check the upstream feed" if t < 0.7 * y else "normal")How it works: 42%: almost certainly a missing input file, not a quiet day. Counters caught it before the report went out.
Counters vs output for skewed students
Instead of per-student counters, find the 3 students with the most orders from (student, orders) pairs, the way a reducer's output would be used.
a few (student, orders) rows
S0003 4031 S0002 4015 S0007 3934
Show solution and explanation
rows = [("S0001", 3899), ("S0007", 3934), ("S1413", 184), ("S0002", 4015), ("S0003", 4031)] # real counts from Lessons 9.1 and 9.7
top = sorted(rows, key=lambda r: -r[1])[:3]
for s, n in top:
print(s, n)How it works: Per-key numbers come from the job's output (a reducer keyed by student), then any sort or top-N runs on that output. No counter limit involved.
Real-world scenario questions (asked in company interviews)
Counters come up in interviews as “how do you track bad records?” and in debugging questions about strange numbers.
“How would you count malformed records in a MapReduce job?”
A standard question after you explain mappers.
The interviewer is checking: custom counters and what to do with them.
Show a strong answer
- Define an enum (e.g. GOOD, BAD_FORMAT, BAD_AMOUNT) and call context.getCounter(Quality.BAD_FORMAT).increment(1) when skipping a row.
- After the job, read job.getCounters() in the driver and fail the pipeline if bad rows exceed a threshold.
- Optionally write the bad rows themselves to a side output (MultipleOutputs) for inspection.
A job fails with “Too many counters: 121 max=120”
It started after a developer added counters “for every product”.
The interviewer is checking: knowing the right fix, not just the property.
Show a strong answer
- Unbounded dynamic counter names (one per product) hit mapreduce.job.counters.max.
- Raising the limit is the wrong fix: counters live in memory in every task, the AM and the history server.
- Move per-product numbers into the job output (a reducer keyed by product) and keep counters for a small, fixed set of facts.
“Our task was retried twice. Are the counters triple-counted?”
An analyst doubts the record counts in the job report.
The interviewer is checking: attempt-level aggregation.
Show a strong answer
- No: final job counters sum only the successful attempt of each task; failed and killed attempts are excluded.
- Values seen while the job runs are snapshots and may include in-progress attempts.
- Side effects are a different story: writes to external systems from a failed attempt are NOT undone, which is why tasks should be idempotent.
The nightly report was published with half the data
An upstream feed delivered only some files, and the job “succeeded”.
The interviewer is checking: using counters as guardrails.
Show a strong answer
- Compare Map input records (or HDFS bytes read) with the previous run or an expected range, and exit non-zero on a large drop.
- Collect counters from the JobHistory REST API into monitoring and alert on anomalies.
- Also validate inputs before the job (expected file count, _SUCCESS markers from upstream).