Review and interview preparation

The last part of the capstone does what senior engineers and interviewers do with any project: question it. We measure where the time goes, try to make it faster (and catch two real bugs with our checks), break a worker during a job to watch Hadoop recover, size the system for growth, and turn the whole project into answers you can give in an interview.

Module 14 · Capstone project⏱ 34 minIntermediate4 real experiments on our clusterInterview question bankRuns in your browser (Python)8 practice problems
1 The story

"Make it faster" broke it. The checks noticed.

The pipeline works, and Mrs. Shinde is happy. Arjun Sir now plays the reviewer every real project meets before production: is it correct, is it fast enough for 40 canteens, what happens when a machine dies, and can you explain it in an interview? Kabir tries to speed up the clean job. His first two attempts finish in under 45 seconds and give wrong numbers. Only check.py and the counters catch it.

Same job, four ways (real runs on our cluster) baseline 104 files → 104 maps 4 min 39 s correct combine #1 3 maps 38 s CLEAN 192,799 DUPLICATE 164,120 combine #2 3 maps 44 s CLEAN 354,135 ✓ LATE 46,646 (want 2,100) compacted input 4 files → 4 maps 53 s byte-identical md5 e4cb8704… 5.3× faster, and proven equal: same counters, same output checksum, 10/10 checks myphatshala.in
Kabir

Thirty-eight seconds! Seven times faster. Ship it?

Arjun Sir

Read the counters first. CLEAN 192,799, but we know there are 354,135 orders. Fast and wrong is worse than slow and right, because nobody notices.

Riya

How did a speed setting change the answer?

Arjun Sir

Two real Hadoop behaviours we had never met before. That is why reviews exist, and why "tell me about a bug you found" is the most common interview question about a project. Today we review the capstone the way a senior engineer would, then turn it into answers for your interviews.

2 Definition

A project review, and a project story

Formal definition (write this in exams)

A production-readiness review examines a data pipeline before it is relied on, across five areas: correctness (acceptance tests, reconciliation, conservation of records), performance and scalability (job time, task counts, behaviour at expected growth), reliability (behaviour under task, node and disk failures; idempotent re-runs), cost and capacity (storage, replication, cluster size) and operability (counters, logs, alerts, runbooks). Every change made during the review must be regression-tested: the new version must produce results proven equal to the old one. In interviews the same project is presented with the STAR method: Situation, Task, Action and Result, with measured numbers.

In simple words

Before trusting a pipeline, check that it is right, fast enough, survives failures, does not waste money and can be run by someone else. Every improvement must give exactly the same answers. Then tell the story: what was the problem, what did you do, what did it achieve.

Words you'll meet in this lesson

Regression testProof that a change did not change the results: counters, checksums, checks.
CombineTextInputFormatPacks many small files into fewer splits (fewer map tasks).
CompactionRewriting many small files into a few large ones (Lesson 7.5).
Lineage columnData about where a row came from, e.g. the day of its source file.
Failure drillBreaking something on purpose to watch recovery.
Task attemptOne try of a task; failed attempts are re-run elsewhere (Lesson 11.2).
Write pipelineThe chain of DataNodes a block is written through (Lesson 6.1).
STARSituation, Task, Action, Result: the shape of a good project answer.

Architecture at a glance: what the review changed

Before review bronze/orders 104 files, ~0.2 MB each file day in the file NAME clean job 104 maps (~20 s each) 4 min 39 s After review bronze/orders unchanged (raw) compact.py Spark, 1 min 17 s input_file_name() bronze_monthly 4 files (4–8.6 MB) file day IN each line clean v2 4 maps 53 s also added for a 3-DataNode cluster: dfs.client.block.write. replace-datanode-on-failure.policy=NEVER Regression proof: sorted output of v1 and v2 both hash to md5 e4cb870499eba837b494c5e4e85582dc; all counters equal. myphatshala.in

From our lab (the review experiments)

Baseline104 splits, 104 maps (total map time 2,049,842 ms ≈ 19.7 s per map, mostly start-up), 4 min 39 s
Combine #1CombineTextInputFormat, 8 MB splits: 3 maps, 38 s; CLEAN 192,799, DUPLICATE 164,120, MALFORMED 400: Streaming passed the byte offset as a key, so the mapper's "order_id" became the offset
Combine #2+ stream.map.input.ignoreKey=true: 44 s, all counters right except LATE 46,646: the file-name variable is set once per task, not per file
FixSpark compaction tags every line with its file day (1 min 17 s, 4 files) → clean v2: 4 maps, 53 s, output md5 identical to baseline
DrillKilled task JVMs and stopped worker2's DataNode mid-job: failed attempts re-ran, YARN said SUCCEEDED, output identical, but the client printed "Streaming Command Failed!": the job-history file lost its write pipeline
Drill fixedreplace-datanode-on-failure.policy=NEVER: 4 failed attempts, 108 maps launched, "completed successfully", same counters and md5, 5 min 48 s

The review at three levels

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

Beginner What a review asks

  • Is it right? 10/10 checks, and the numbers add up.
  • Is it fast enough? Today yes; at 40 canteens the 104 tiny files become 1,040 a month.
  • Does it survive failures? We broke a worker on purpose to find out.
  • Can you explain it? In two minutes, with numbers, to an interviewer.

Intermediate Why the two speed-ups went wrong

  • Keys in Streaming: with plain TextInputFormat the keys (byte offsets) are discarded and only the lines are piped to the mapper. With CombineTextInputFormat our mapper received key TAB line until we set stream.map.input.ignoreKey=true. Our mapper split on commas, so the "order_id" was offset TAB order_id and its key was the offset.
  • File name per task: Streaming copies the job configuration into the environment (dots become underscores) when it starts the Python process. With combined splits one Python process reads many files, so mapreduce_map_input_file holds one name for all of them. 44,546 extra orders looked late.
  • The real fix: stop depending on file names. Put the lineage (file day) into the data during compaction, then any engine and any split size gives the same answer.

Advanced Reliability on a tiny cluster

  • Task failures are routine: YARN re-ran the killed attempts on other nodes and the counters of failed attempts were discarded, so totals stayed exact.
  • With 3 DataNodes and replication 3, losing one leaves no spare node for an open write pipeline. The client's default policy tries to replace the bad node and gives up: "Failed to replace a bad datanode on the existing pipeline due to no more good datanodes being available to try". The AM's job-history file died; the client could not fetch the final status.
  • hdfs-default.xml notes that on extremely small clusters (3 nodes or less) administrators may want policy NEVER: keep writing to the remaining nodes. On normal clusters keep DEFAULT.
  • Lesson for operators: the client's exit code is not the job's result. Check yarn application -status (FINISHED / SUCCEEDED) and the output.

Exam tip: When asked "how do you know an optimisation is safe?", answer: run old and new on the same input and compare every counter and a checksum of the sorted output; ours matched exactly (md5 e4cb8704…), while two faster versions failed the comparison.

3 Types and ways to use it

The review checklist, with our evidence

AreaQuestionOur evidenceStatus
CorrectnessDo results equal an independent truth?check.py 10/10; conservation 357,332 → 357,215 → 354,431✓
IdempotencyIs a re-run safe?backfill ×2 unchanged; append demo doubled✓
PerformanceHow long, and where does time go?104 maps × ~20 s start-up → compaction: 53 sfixed
ScalabilityWhat at 40 canteens, 3 years?sizing: ~6 GB raw a year at 40 canteens, small for HDFS; the file count is the issueplanned
ReliabilityTask and node failures?drill: same output; history bug on 3 nodes found and fixedfixed
OperabilityCan someone else run it?counters per rule, quarantine, scripts, runbook (to write)open
SecurityWho may read student IDs?lab has no Kerberos; production needs it + Ranger (Lesson 12.5)open

Ways you will be asked about this project

"Walk me through your project"

Two minutes: problem, data, architecture, one hard problem, result with numbers.

every interview

"What was the hardest bug?"

The combine-input bugs: fast, wrong, caught by counters and checks; fixed with lineage in the data.

depth

"What if a node fails?"

Our drill: attempts re-ran, output identical; the 3-node history-file problem and its fix.

reliability

"How would it scale?"

Sizing with real numbers; small files, partitioning, compaction, more reducers.

design

Animated example: a fast job that was wrong

CombineTextInputFormat meets Python 6 steps
Animated example TextInputFormat: 1 file per map; Python readsKHR-0914-00449,2026-09-14 10:42:17,… CombineTextInputFormat: 35 files in one map; Python reads48211⇥KHR-0914-00449,2026-09-14 10:42:17,… + ignoreKey=true: lines are right again, but the env var saysmapreduce_map_input_file = one name for ~35 files compacted input: each line carries its own file day2026-09-14⇥KHR-0914-00449,2026-09-14 10:42:17,… key = order_id → copies of an order meetCLEAN 354,135 · DUPLICATE 2,784 · LATE 2,100but 104 maps × 20 s start-up = 4 min 39 s split(",")[0] = "48211⇥KHR-0914-00449"printed before the first TAB → key = 48211different orders with the same offset "collide" counters (38 s):CLEAN 192,799 · DUPLICATE 164,120MALFORMED 400 (104 headers no longer skipped) env vars are set once, when Python startsone process reads 35 files, but "knows" only one namerows from earlier files look as if they came late counters (44 s):CLEAN 354,135 ✓ DUPLICATE 2,784 ✓LATE 46,646 ✗ (truth 2,100) 4 files → 4 maps, 53 severy counter equal, output md5 equallineage lives in the data, not the file name who noticed? the counters + check.py, not the run time 5.3× faster and proven identical myphatshala.in

Keep a "golden" output from a trusted run (counters + checksum of sorted output). Every change, even a config change, must reproduce it before it ships.

4 Step by step

Reviewing, fixing and presenting the capstone

  1. Find where the time goes

    Launched map tasks=104
    Total time spent by all map tasks (ms)=2049842
    2,049,842 ms / 104 maps ≈ 19.7 s per map for ~3,400 lines each
    The work per map is tiny; the cost is starting a container, a JVM and Python 104 times on 6 vcores (Lesson 7.5's small files problem, now measured).
  2. Try combining small files

    -D mapreduce.input.fileinputformat.split.maxsize=8388608 \
    -inputformat org.apache.hadoop.mapred.lib.CombineTextInputFormat
    number of splits:3 real 0m38.006s CLEAN=192799 DUPLICATE=164120 LATE=192799 MALFORMED=400
    7× faster and badly wrong. Never judge an optimisation by its run time; judge it by its counters.
  3. Fix the key, find the second bug

    -D stream.map.input.ignoreKey=true \
    real 0m43.720s CLEAN=354135 DUPLICATE=2784 LATE=46646 MALFORMED=296
    Lines are right now, but LATE uses the file day from mapreduce_map_input_file, which is exported once per task. A combined split holds about 35 files.
  4. Put the lineage into the data

    .withColumn("fday", F.regexp_extract(F.input_file_name(), r"_(\d{4}-\d{2}-\d{2})\.csv$", 1))
    …partitionBy("college").text(f"{B}/bronze_monthly/orders")
    lines: 357228 8607649 /capstone/bronze_monthly/orders/college=KHR/part-00002-….c000.txt 4192061 /capstone/bronze_monthly/orders/college=NRL/part-00000-….c000.txt 5728542 /capstone/bronze_monthly/orders/college=PNL/part-00001-….c000.txt 7329058 /capstone/bronze_monthly/orders/college=VSH/part-00000-….c000.txt
    Spark's input_file_name() is evaluated per row, so each line gets its own file day. Headers are dropped: 357,332 − 104 = 357,228 lines. Bronze itself stays untouched.
  5. Run v2 and prove it equal

    hdfs dfs -cat '/capstone/stage/clean/part-*'    | sort | md5sum
    hdfs dfs -cat '/capstone/stage/clean_v2/part-*' | sort | md5sum
    number of splits:4 LATE=2100 e4cb870499eba837b494c5e4e85582dc - e4cb870499eba837b494c5e4e85582dc - real 0m52.718s
    The mapper change is two lines (read the tag if present). Same counters, same checksum: this is a regression test you can show a reviewer.
  6. Failure drill: break worker2 mid-job

    docker exec lab8-worker2-1 bash -c 'pkill -9 -f "[Y]arnChild"; hdfs --daemon stop datanode'
    Task Id : attempt_1792730826465_0014_m_000033_0, Status : FAILED Killed by external signal Task Id : attempt_1792730826465_0014_r_000000_0, Status : FAILED ERROR StreamJob - Error Launching job : java.io.IOException: Unknown Job job_1792730826465_0014 Streaming Command Failed! $ yarn application -status application_1792730826465_0014 Final-State : SUCCEEDED
    The data survived (output md5 identical), but the client "failed". The AM's history file was being written through all 3 DataNodes; with one dead there was no replacement, so the history server never got the job.
  7. Fix and repeat the drill

    -D dfs.client.block.write.replace-datanode-on-failure.policy=NEVER \
    Job job_1792730826465_0015 completed successfully Failed map tasks=4 Launched map tasks=108 CLEAN=354135 LATE=2100 e4cb870499eba837b494c5e4e85582dc - real 5m48.109s
    Four killed attempts re-ran elsewhere (108 launched for 104 maps); counters of failed attempts were discarded, so totals stayed exact. Recovery cost about 70 extra seconds.
  8. Write it up for interviews

    Built a Hadoop/Spark batch pipeline for 4 canteens (130 daily files, 3.57 lakh records):
    dedupe + quality rules in MapReduce, Parquet silver/gold in Spark, UPI reconciliation;
    10/10 acceptance checks; cut clean time 4m39s → 53s via compaction (byte-identical output).
    Three lines, all measured. The worked examples and the STAR scenario below turn this into a two-minute answer.

Our first drill command was pkill -9 -f YarnChild inside bash -c '…'. The shell's own command line contains "YarnChild", so pkill killed the shell before it could stop the DataNode. Writing the pattern as [Y]arnChild matches the processes but not the command that contains it.

5 See it happen

The failure drill: the log and the cluster

Our second drill (with the fix), second by second. Left: the client's terminal. Right: what YARN and HDFS do on the three workers.

Killing worker2 mid-job 6 steps
What you see 07:34:04 Running job: job_…_0015 07:35:13 worker2 broken 07:35:14 attempt_…_m_000019_0 FAILEDKilled by external signal (×4) map 26% … 100% reduce 100% 07:39:43 completed successfullyFailed map tasks=4 Launched=108CLEAN=354135 LATE=2100 sort | md5sum → e4cb870499eb…(same as the baseline run)real 5m48s (baseline 4m39s) myphatshala.in
Behind the screen worker1 worker2 worker3 tasks killed new attempts run DataNode ✓ DataNode ✓ DataNode stopped DataNode ✓ NodeManagerNodeManagerNodeManager maps read blocks with 3 replicas, one per workerthe AM writes job history (.jhist) through 3 DataNodes AM marks the attempts FAILED and asks the RM fornew containers; reads switch to other replicas .jhist pipeline loses worker2: no 4th node to replace itpolicy NEVER → keep writing to the 2 left counters come only from successful attemptsso CLEAN is still exactly 354,135 without the fix (first drill): history lost,client: "Streaming Command Failed!" · YARN: SUCCEEDED myphatshala.in
6 Real-world use

Reviews and interviews at work

readinessreview

1. Production-readiness reviews

Many data teams require a checklist before a pipeline is scheduled in production: tests and reconciliation, run time at expected volume, failure behaviour, alerts, a runbook and an owner, much like our table.

New pipeline→review + evidence→Scheduled in prod
goldenoutput

2. Platform upgrades

Before upgrading Hadoop, Spark or a library, teams re-run key jobs on a fixed input and compare outputs and counters with a golden run, the same comparison that caught our combine bugs.

Upgrade→compare with golden run→Approve
game daybreak it

3. Failure drills ("game days")

Operations teams switch off nodes, disks or whole racks in a planned window to prove that jobs recover and alerts fire, and they often find surprises like our job-history problem.

Planned failure→watch recovery→Fix + runbook
product co.deep dive

4. Product-company interviews

Interviews often include a project deep dive: one project, many follow-ups ("why that key?", "what if 10×?", "what broke?"). One project known to the last number beats five listed on a CV.

Your project→STAR + numbers + bug story→Design follow-ups
services co.concepts + tools

5. Service-company and campus interviews

Expect concept questions across the stack (HDFS, MapReduce, YARN, Hive, Spark) plus "explain your project". The question bank in this lesson maps each common question to the lesson that answers it.

Course modules→question bank→Confident answers

Interview question bank: where each answer lives

The questions that come up again and again in Hadoop and data-engineering interviews, with the lessons that answer them. Revise the linked lesson's "Quick revision" card and its interview questions the night before.

QuestionLesson
What is HDFS? Why 128 MB blocks and 3 replicas?5.2 · 5.3
What happens when a DataNode dies?5.5 · this lesson's drill
How is a file written and read in HDFS?6.1 · 6.2
NameNode high availability?7.1
What is the small files problem? How do you fix it?7.5 · this lesson
Explain MapReduce with word count; what happens in shuffle and sort?8.4 · 8.6
Combiner vs partitioner?9.1 · 9.2
How do you join two big datasets?9.5 · 9.6
Parquet vs ORC vs Avro; splittable compression?10.2 · 10.3
YARN architecture; life of an application?11.1 · 11.2
Capacity vs Fair scheduler; container memory errors?11.3 · 11.4
How do you size a cluster?12.3
Kerberos and Ranger in one minute?12.5
Hadoop vs Spark? Is Hadoop dead?13.2 · 2.1
What is a lakehouse? Idempotent pipelines?13.4 · 14.2
Walk me through your project.this lesson: STAR answer below

Simplified; organisations differ in process. Kadak Canteens and its data are fictional.

Sources (checked 23 Oct 2026): Apache Hadoop: Hadoop Streaming (keys discarded with TextInputFormat; configuration copied to the environment) · Cloudera Community: replace-datanode-on-failure.policy on a 3-DataNode cluster (quotes hdfs-default.xml)

7 Run the code

Why one file name for many files breaks "late"

A simulation of our second combine attempt. One Python process reads lines from three files but is told only one file name at start-up, as Streaming does with its environment.

files = {"orders_KHR_2026-09-12.csv": ["KHR-0912-1,2026-09-12", "KHR-0912-2,2026-09-12"],
         "orders_KHR_2026-09-14.csv": ["KHR-0912-9,2026-09-12", "KHR-0914-1,2026-09-14"],
         "orders_KHR_2026-09-15.csv": ["KHR-0915-1,2026-09-15"]}

def late_count(lines_with_day):
    return sum(1 for fday, line in lines_with_day if fday > line.split(",")[1])

# baseline: one process per file, each knows its own file name
per_file = [(name[-14:-4], line) for name, lines in files.items() for line in lines]
# combined split: one process, one file name for the whole split (here: the last file)
told = list(files)[-1][-14:-4]
combined = [(told, line) for lines in files.values() for line in lines]
# fix: the file day travels inside every line (compaction)
tagged = [(name[-14:-4], line) for name, lines in files.items() for line in lines]

print("one file per map :", late_count(per_file))
print("combined, env var:", late_count(combined))
print("lineage in data  :", late_count(tagged))
one file per map : 1 combined, env var: 4 lineage in data : 1
  • told = list(files)[-1][-14:-4]Like mapreduce_map_input_file: fixed when the process starts, wrong for the other files in the split.
  • tagged = [(name[-14:-4], line) for name, lines in files.items() for line in lines]compact.py does this with input_file_name(): each line carries its own file day.

Run it on your PC: the review experiments

Same container and folder as Lessons 14.1–14.2, after the baseline clean job. Job logs are filtered to the lines that matter (splits, attempts, counters, timing).

1 · Combine small files (first try: wrong)

# combine_job.sh: the same clean job, but pack the 104 small files into a few splits
hdfs dfs -rm -r -f -skipTrash /capstone/stage/clean_combined
mapred streaming \
  -D mapreduce.job.name=capstone-clean-combined \
  -D mapreduce.job.reduces=3 \
  -D mapreduce.input.fileinputformat.split.maxsize=8388608 \
  -files clean_mapper.py,clean_reducer.py,capstone_raw/menu.csv \
  -inputformat org.apache.hadoop.mapred.lib.CombineTextInputFormat \
  -input '/capstone/bronze/orders/college=*' \
  -output /capstone/stage/clean_combined \
  -mapper clean_mapper.py -reducer clean_reducer.py
05:57:08 INFO JobSubmitter - number of splits:3 05:57:09 INFO Job - Running job: job_1792730826465_0008 05:57:41 INFO Job - Job job_1792730826465_0008 completed successfully Launched map tasks=3 Launched reduce tasks=3 Data-local map tasks=2 Map input records=357332 Reduce output records=193199 IO_ERROR=0 CLEAN=192799 DUPLICATE=164120 LATE=192799 MALFORMED=400 PRICE_MISMATCH=4236 TEST_ORDER=13 UNDERCHARGED_RS=15771 UNKNOWN_ITEM=1194 real 0m38.006s

3 maps instead of 104 and 7× faster, but CLEAN is 192,799 instead of 354,135: the mapper received "offset TAB line".

2 · Combine with ignoreKey (second try: LATE wrong)

# combine_job.sh: the same clean job, but pack the 104 small files into a few splits
hdfs dfs -rm -r -f -skipTrash /capstone/stage/clean_combined
mapred streaming \
  -D mapreduce.job.name=capstone-clean-combined \
  -D mapreduce.job.reduces=3 \
  -D mapreduce.input.fileinputformat.split.maxsize=8388608 \
  -D stream.map.input.ignoreKey=true \
  -files clean_mapper.py,clean_reducer.py,capstone_raw/menu.csv \
  -inputformat org.apache.hadoop.mapred.lib.CombineTextInputFormat \
  -input '/capstone/bronze/orders/college=*' \
  -output /capstone/stage/clean_combined \
  -mapper clean_mapper.py -reducer clean_reducer.py
06:19:17 INFO JobSubmitter - number of splits:3 06:19:17 INFO Job - Running job: job_1792730826465_0010 06:19:53 INFO Job - Job job_1792730826465_0010 completed successfully Killed map tasks=1 Launched map tasks=3 Launched reduce tasks=3 Data-local map tasks=2 Map input records=357332 Reduce output records=354431 IO_ERROR=0 CLEAN=354135 DUPLICATE=2784 LATE=46646 MALFORMED=296 PRICE_MISMATCH=11711 TEST_ORDER=13 UNDERCHARGED_RS=43710 UNKNOWN_ITEM=1619 real 0m43.720s

Every counter equals truth except LATE: one file name per task for about 35 files.

3 · Compact with lineage, then clean v2

# compact.py: 104 small daily files -> one file per canteen, each line tagged with the day it arrived
from pyspark.sql import SparkSession, functions as F
spark = SparkSession.builder.appName("capstone-compact").getOrCreate()
B = "hdfs://namenode/capstone"
lines = (spark.read.text(f"{B}/bronze/orders/college=*")
         .withColumn("fday", F.regexp_extract(F.input_file_name(), r"_(\d{4}-\d{2}-\d{2})\.csv$", 1))
         .withColumn("college", F.regexp_extract(F.input_file_name(), r"college=(\w+)", 1))
         .where(~F.col("value").startswith("order_id,")))
(lines.select(F.concat_ws("\t", "fday", "value").alias("value"), "college")
      .repartition("college").write.mode("overwrite").partitionBy("college").text(f"{B}/bronze_monthly/orders"))
print("lines:", spark.read.text(f"{B}/bronze_monthly/orders").count())

export SPARK_HOME=/tmp/opt/spark-4.0.4-bin-hadoop3 HADOOP_CONF_DIR=/opt/hadoop/etc/hadoop
$SPARK_HOME/bin/spark-submit --master yarn --num-executors 3 --executor-memory 768m --driver-memory 768m compact.py
hdfs dfs -ls -R /capstone/bronze_monthly/orders | grep part- | awk '{print $5, $8}'

# clean_mapper_v2.py = clean_mapper.py plus two lines after line.rstrip():
#     if "\t" in line:
#         fday, line = line.split("\t", 1)
# clean_job_v2.sh: the clean job on 4 compacted files (one per canteen)
hdfs dfs -rm -r -f -skipTrash /capstone/stage/clean_v2
mapred streaming \
  -D mapreduce.job.name=capstone-clean-v2 \
  -D mapreduce.job.reduces=3 \
  -files clean_mapper_v2.py,clean_reducer.py,capstone_raw/menu.csv \
  -input '/capstone/bronze_monthly/orders/college=*' \
  -output /capstone/stage/clean_v2 \
  -mapper clean_mapper_v2.py -reducer clean_reducer.py
hdfs dfs -cat '/capstone/stage/clean/part-*' | sort | md5sum
hdfs dfs -cat '/capstone/stage/clean_v2/part-*' | sort | md5sum
lines: 357228 8607649 /capstone/bronze_monthly/orders/college=KHR/part-00002-bde4b71f-917a-4ab4-905c-5cdcde03e351.c000.txt 4192061 /capstone/bronze_monthly/orders/college=NRL/part-00000-bde4b71f-917a-4ab4-905c-5cdcde03e351.c000.txt 5728542 /capstone/bronze_monthly/orders/college=PNL/part-00001-bde4b71f-917a-4ab4-905c-5cdcde03e351.c000.txt 7329058 /capstone/bronze_monthly/orders/college=VSH/part-00000-bde4b71f-917a-4ab4-905c-5cdcde03e351.c000.txt real 1m16.745s 06:22:27 INFO JobSubmitter - number of splits:4 06:22:28 INFO Job - Running job: job_1792730826465_0012 06:23:06 INFO Job - Job job_1792730826465_0012 completed successfully Killed map tasks=1 Launched map tasks=4 Launched reduce tasks=3 Data-local map tasks=4 Map input records=357228 Reduce output records=354431 IO_ERROR=0 CLEAN=354135 DUPLICATE=2784 LATE=2100 MALFORMED=296 PRICE_MISMATCH=11711 TEST_ORDER=13 UNDERCHARGED_RS=43710 UNKNOWN_ITEM=1619 e4cb870499eba837b494c5e4e85582dc - e4cb870499eba837b494c5e4e85582dc - real 0m52.718s

Spark also printed a harmless "FileNotFoundException … college=*" while checking the glob for a metadata folder (removed). The two md5 lines are the baseline and v2 outputs: identical.

4 · Failure drill (first run: client fails, job succeeds)

# drill.sh: run the clean job and break worker2 while it runs (run from the host, not a container)
docker exec -w /tmp/capstone lab8-namenode-1 bash clean_job.sh > tx_drill.txt 2>&1 &
sleep 75
docker exec lab8-worker2-1 bash -c 'pkill -9 -f "[Y]arnChild"; hdfs --daemon stop datanode'   # kill running tasks + DataNode
wait
docker exec lab8-worker2-1 hdfs --daemon start datanode

# afterwards, inside the namenode container:
# yarn application -status application_<id> | grep State
# yarn logs -applicationId application_<id> -log_files syslog | grep JobHistoryEventHandler
06:34:00 INFO JobSubmitter - number of splits:104 06:34:01 INFO Job - Running job: job_1792730826465_0014 06:35:13 INFO Job - Task Id : attempt_1792730826465_0014_m_000033_0, Status : FAILED [2026-10-23 06:35:12.809]Killed by external signal 06:35:14 INFO Job - Task Id : attempt_1792730826465_0014_m_000034_0, Status : FAILED [2026-10-23 06:35:12.813]Killed by external signal 06:35:14 INFO Job - Task Id : attempt_1792730826465_0014_r_000000_0, Status : FAILED [2026-10-23 06:35:12.813]Killed by external signal 06:35:14 INFO Job - Task Id : attempt_1792730826465_0014_m_000028_0, Status : FAILED [2026-10-23 06:35:12.813]Killed by external signal 06:38:20 ERROR StreamJob - Error Launching job : java.io.IOException: Unknown Job job_1792730826465_0014 Streaming Command Failed! real 4m23.635s $ yarn application -status application_1792730826465_0014 State : FINISHED Final-State : SUCCEEDED $ yarn logs … | grep JobHistoryEventHandler ERROR … JobHistoryEventHandler: Error writing History Event: …MapAttemptFinishedEvent … Failed to replace a bad datanode on the existing pipeline due to no more good datanodes being available to try

The output in HDFS was byte-identical to the baseline (same md5). Only the job-history file was lost, so the client could not report the final status.

5 · Failure drill with the small-cluster setting

# clean_job_small.sh: clean_job.sh for a cluster of only 3 DataNodes (keep writing if one dies)
hdfs dfs -rm -r -f -skipTrash /capstone/stage/clean
mapred streaming \
  -D mapreduce.job.name=capstone-clean \
  -D mapreduce.job.reduces=3 \
  -D dfs.client.block.write.replace-datanode-on-failure.policy=NEVER \
  -files clean_mapper.py,clean_reducer.py,capstone_raw/menu.csv \
  -input '/capstone/bronze/orders/college=*' \
  -output /capstone/stage/clean \
  -mapper clean_mapper.py -reducer clean_reducer.py
07:34:03 INFO JobSubmitter - number of splits:104 07:34:04 INFO Job - Running job: job_1792730826465_0015 07:35:14 INFO Job - Task Id : attempt_1792730826465_0015_m_000019_0, Status : FAILED [2026-10-23 07:35:13.424]Killed by external signal 07:35:14 INFO Job - Task Id : attempt_1792730826465_0015_m_000018_0, Status : FAILED [2026-10-23 07:35:13.423]Killed by external signal 07:35:14 INFO Job - Task Id : attempt_1792730826465_0015_m_000015_0, Status : FAILED [2026-10-23 07:35:13.440]Killed by external signal 07:35:14 INFO Job - Task Id : attempt_1792730826465_0015_m_000016_0, Status : FAILED [2026-10-23 07:35:13.505]Killed by external signal 07:39:43 INFO Job - Job job_1792730826465_0015 completed successfully Failed map tasks=4 Killed map tasks=1 Launched map tasks=108 Launched reduce tasks=3 Data-local map tasks=104 Map input records=357332 Reduce output records=354431 IO_ERROR=0 CLEAN=354135 DUPLICATE=2784 LATE=2100 MALFORMED=296 PRICE_MISMATCH=11711 TEST_ORDER=13 UNDERCHARGED_RS=43710 UNKNOWN_ITEM=1619 real 5m48.109s e4cb870499eba837b494c5e4e85582dc - (sort | md5sum of the output)

4 failed attempts, 108 map attempts launched, every counter exact, same checksum. Same drill.sh, calling clean_job_small.sh.

8 Line by line

A regression check, line by line

The check that rejected the second combine attempt: compare every counter of a new run with the trusted baseline and list the differences.

Worked examples: real problems, solved and traced in memory

Three calculations a reviewer or interviewer will ask you to do on the spot.

Example 1BeginnerPerformance · overhead

How much of the job was start-up?

Problem statement

The baseline spent 2,049,842 ms in 104 map tasks of about 3,435 lines each. If the real processing is about 1 second per map, what share of the map time was overhead?

Input
2,049,842 ms · 104 maps · ~1 s real work per map
Expected output
avg seconds per map, overhead %: (19.7, 95)

Approach

  1. Average map time = total ÷ maps.
  2. Overhead = average − real work.
  3. Share = overhead ÷ average.

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 2IntermediateSizing · growth

Size the system for 40 canteens and 3 years

Problem statement

Per canteen per month our raw data is (20.9 + 28.8) ÷ 4 MB. Kadak plans 40 canteens and wants 3 years online with replication 3. How much HDFS space, and how many daily order files a year without compaction?

Input
12.4 MB per canteen-month · 40 canteens · 36 months · rf 3
Expected output
raw GB, with replicas GB: (17.5, 52.4)
order files per year: 12480

Approach

  1. Raw = per canteen-month × canteens × months.
  2. Disk = raw × 3.
  3. Files = canteens × open days per year.

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 3AdvancedReliability · recovery

What did the failure cost?

Problem statement

The drill killed 4 running map attempts on worker2. Each map takes about 20 s; the job ran 5 min 48 s instead of the baseline 4 min 39 s. How much of the extra time is re-running the 4 killed maps in parallel, and how much is something else?

Input
4 attempts × ~20 s · 348 s vs 279 s
Expected output
extra seconds, not explained by re-runs: (69, 49)

Approach

  1. Extra time = drill − baseline.
  2. Re-run cost ≈ one wave of 20 s.
  3. The rest: detecting failures, fewer DataNodes to read from, run-to-run noise.

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

  • Review areas: correctness, idempotency, performance, scalability, reliability, cost, operability, security.
  • Baseline: 104 maps × ~19.7 s, 4 min 39 s; the work per map was tiny: start-up dominated.
  • Combine #1: 3 maps, 38 s, wrong (offset keys). Combine #2 (ignoreKey): 44 s, LATE 46,646 (env var once per task).
  • Fix: compact.py tags each line with its file day → 4 maps, 53 s, identical counters and md5 e4cb8704…
  • Drill: killed tasks + DataNode: attempts re-ran, output identical; 3-node history-file failure fixed with policy NEVER.
  • Sizing: ~6 GB raw a year at 40 canteens; files, not bytes, are the scaling problem.
  • Interview: STAR with numbers, one hard bug, one failure story, one design trade-off.

Interview and viva questions

Walk me through your capstone project.

Situation: four college canteens sent 130 daily files a month (POS orders, UPI payments, a menu) and could not trust their totals. Task: daily revenue per canteen, item mix, peak hours and UPI reconciliation, proven correct. Action: landed raw files in HDFS, wrote a MapReduce Streaming job keyed on order_id to deduplicate and apply quality rules with counters, used Spark to write Parquet silver partitioned by day and five gold tables, and wrote ten acceptance checks against a trusted total. Result: 10/10 checks, ₹43,710 of undercharging and a ₹6,854 UPI gap found, and the clean job cut from 4 min 39 s to 53 s with byte-identical output.

What was the hardest problem?

Making the clean job faster. Packing small files with CombineTextInputFormat made it 7× faster but wrong: the Python mapper received byte-offset keys, and after fixing that the input-file environment variable named only one file per combined split, so 46,646 orders looked late instead of 2,100. Counters and acceptance checks caught both; the fix was compacting the files and writing each line's source day into the data.

How does Hadoop handle a node failure during a job?

The ApplicationMaster re-runs failed or lost task attempts on other nodes, and HDFS readers switch to other replicas. Counters from failed attempts are discarded, so results stay exact. In our drill 4 attempts were killed, 108 launched for 104 maps, and the output was byte-identical. On a 3-DataNode cluster open writes can fail when one DataNode dies unless the replace-datanode-on-failure policy is NEVER.

How do you prove that an optimisation did not change the results?

Run old and new versions on the same input, compare every counter and a checksum of the sorted output, and run the acceptance checks. Keep that golden output for future changes.

Why did you use both MapReduce and Spark?

MapReduce made deduplication explicit as a shuffle on order_id and shows the classic Hadoop job model many companies still run; Spark was better for typed columns, Parquet, joins and several aggregations in one job. In a new project I would likely do it all in Spark (dropDuplicates or a window) with the same rules and checks.

What would you change for production?

Schedule it in Airflow with retries and an SLA; compact daily; move silver to a table format such as Iceberg for safe corrections; add Kerberos and Ranger for access to student IDs; alert on check failures and on the UPI gap table; write a runbook that includes checking the YARN final status, not only the client exit code.

Practice problems

Review calculations and interview drills using the experiment numbers.

P1Easy

Speed-up

Baseline 4 min 39 s, compacted 53 s. What is the speed-up? Include the 1 min 17 s compaction: is it still worth it?

Input
279 s · 53 s · 77 s
Expected output
5.3 x without compaction, 2.1 x including it
Show solution and explanation
print(round(279 / 53, 1), "x without compaction,", round(279 / (53 + 77), 1), "x including it")
5.3 x without compaction, 2.1 x including it

How it works: 5.3× for the job, 2.1× even counting compaction; compaction runs once while the clean job may run many times.

P2Easy

Lines per map

357,332 lines over 104 maps, or 357,228 lines over 4 maps. How many lines per map task?

Input
two layouts
Expected output
3436 89307
Show solution and explanation
print(round(357332 / 104), round(357228 / 4))
3436 89307

How it works: 3,436 vs 89,307 lines per task: the second is a sensible amount of work per task.

P3Easy

Extra attempts

The drill launched 108 map attempts for 104 maps with 4 failed. How many attempts succeeded?

Input
108 · 4
Expected output
104
Show solution and explanation
print(108 - 4)
104

How it works: 104: one successful attempt per map, which is what the counters are built from.

P4Medium

How wrong was combine #1?

Compute the error in CLEAN and DUPLICATE against truth.

Input
192,799 vs 354,135 · 164,120 vs 2,784
Expected output
CLEAN -161336 -46%
DUPLICATE 161336 +5795%
Show solution and explanation
for name, got, want in [("CLEAN", 192799, 354135), ("DUPLICATE", 164120, 2784)]:
    print(name, got - want, f"{(got - want) / want:+.0%}")
CLEAN -161336 -46% DUPLICATE 161336 +5795%

How it works: Orders −46%, duplicates +5,795%: wrong enough to notice, if anyone looks at the counters.

P5Medium

Average file size after compaction

The four compacted files were 8,607,649, 4,192,061, 5,728,542 and 7,329,058 bytes. Average size, and share of a 128 MB block?

Input
4 sizes
Expected output
6.5 MB, 4.8% of a block
Show solution and explanation
sizes = [8607649, 4192061, 5728542, 7329058]
avg = sum(sizes) / len(sizes)
print(round(avg / 1e6, 1), "MB,", f"{avg / (128 * 1024 * 1024):.1%} of a block")
6.5 MB, 4.8% of a block

How it works: About 6.5 MB, 4.8% of a block: still small but fine for one month; at 40 canteens monthly files would be about 65 MB.

P6Medium

STAR in numbers

Print a one-line result statement from a dictionary of measured numbers.

Input
your project numbers
Expected output
10/10 checks; found Rs 43,710 undercharged and a Rs 6,854 UPI gap; clean job 279s -> 53s (5.3x)
Show solution and explanation
r = {"checks": "10/10", "undercharged": 43710, "upi_gap": 6854, "before_s": 279, "after_s": 53}
print(f"{r['checks']} checks; found Rs {r['undercharged']:,} undercharged and a Rs {r['upi_gap']:,} UPI gap; "
      f"clean job {r['before_s']}s -> {r['after_s']}s ({r['before_s'] / r['after_s']:.1f}x)")
10/10 checks; found Rs 43,710 undercharged and a Rs 6,854 UPI gap; clean job 279s -> 53s (5.3x)

How it works: Results with numbers are what interviewers remember.

P7Business problem

Is the pipeline worth it?

The accountant spent 4 days a month reconciling. The pipeline found ₹43,710 undercharged and ₹6,854 unconfirmed in one month. Accountant cost ₹2,500/day. What is the first month's value?

Input
4 days × ₹2,500 · ₹43,710 · ₹6,854
Expected output
Rs 60,564 in the first month (illustrative)
Show solution and explanation
time_saved = 4 * 2500
print(f"Rs {time_saved + 43710 + 6854:,} in the first month (illustrative)")
Rs 60,564 in the first month (illustrative)

How it works: About ₹60,600 in month one; the price bug, once fixed, keeps paying back.

P8Advanced

Plan a failure test matrix

List failure drills for the pipeline with what to observe, and print them as a checklist.

Input
components
Expected output
1. kill a map task JVM    -> attempt re-runs, counters exact
2. stop a DataNode        -> reads use replicas; writes continue (policy on small clusters)
3. stop a NodeManager     -> containers lost after expiry, tasks rescheduled
4. fill a disk to 97%     -> write failures; alert before it happens (Lesson 13.4 incident)
5. re-run a finished day  -> idempotent: totals unchanged
Show solution and explanation
drills = [("kill a map task JVM", "attempt re-runs, counters exact"),
          ("stop a DataNode", "reads use replicas; writes continue (policy on small clusters)"),
          ("stop a NodeManager", "containers lost after expiry, tasks rescheduled"),
          ("fill a disk to 97%", "write failures; alert before it happens (Lesson 13.4 incident)"),
          ("re-run a finished day", "idempotent: totals unchanged")]
for i, (what, observe) in enumerate(drills, 1):
    print(f"{i}. {what:<22} -> {observe}")
1. kill a map task JVM -> attempt re-runs, counters exact 2. stop a DataNode -> reads use replicas; writes continue (policy on small clusters) 3. stop a NodeManager -> containers lost after expiry, tasks rescheduled 4. fill a disk to 97% -> write failures; alert before it happens (Lesson 13.4 incident) 5. re-run a finished day -> idempotent: totals unchanged

How it works: A drill without a written expectation is just breaking things; each line is a test with a pass condition.

Real-world scenario questions (asked in company interviews)

Project deep-dive questions, the kind asked for 20–30 minutes in data-engineering interviews.

Project · storytellingFresher

"Tell me about a project you are proud of" (2 minutes)

The opening question of most interviews.

The interviewer is checking: clarity, ownership and numbers.

Show a strong answer
  • S: Four college canteens, 130 daily files a month from tills and the UPI gateway, totals never matched.
  • T: Daily revenue per canteen, item mix, peak hours and UPI reconciliation that the owner could trust.
  • A: HDFS bronze; MapReduce Streaming keyed on order_id for dedupe and rules with counters; Spark silver Parquet by day and gold tables; ten acceptance checks against trusted totals.
  • R: 10/10 checks; found ₹43,710 undercharged at one canteen and a ₹6,854 gateway gap in one hour; cut the clean job from 4 min 39 s to 53 s with byte-identical output.
Project · depthFresher

"What went wrong, and how did you find it?"

The follow-up to every project story.

The interviewer is checking: honesty and debugging method.

Show a strong answer
  • A speed-up (combining small files) produced wrong counters twice: offset keys, then one file name per task making 46,646 orders "late".
  • Found by comparing counters with the baseline and running acceptance checks, not by looking at run time.
  • Fixed by carrying the source file day in the data; proved with identical counters and output checksum.
Design · scaleExperienced

"The business grows 10×. What breaks first?"

40 canteens, 3 years of history.

The interviewer is checking: sizing with numbers, knowing bottlenecks.

Show a strong answer
  • Bytes are fine: about 6 GB raw a year, about 52 GB with replicas for 3 years.
  • Files are not: 12,480 order files a year would mean 1,040 map tasks a month and many NameNode objects; compact daily and partition silver by day.
  • Then: more reducers for the clean shuffle, Spark for everything, an Iceberg table for corrections, Airflow for scheduling and backfills.
Operations · reliabilityExperienced

"Your scheduler marked the job failed, but the data looks complete. What do you do?"

The client exited with an error at the very end.

The interviewer is checking: not trusting a single signal.

Show a strong answer
  • Check the application's final state in YARN (in our drill: FINISHED / SUCCEEDED), the output's _SUCCESS marker, counters and checks.
  • Read the AM log: ours showed the job-history file lost its write pipeline when a DataNode died on a 3-node cluster.
  • Decide from the checks (the job is idempotent, so a re-run is safe anyway); fix the root cause and add it to the runbook.
Career · resumeFresher

"I see Hadoop on your resume. Is it still relevant?"

A common challenge in 2026 interviews.

The interviewer is checking: balanced judgement, not tool loyalty.

Show a strong answer
  • New projects mostly use Spark, object storage and lakehouse tables, but many banks, telecoms and governments still run Hadoop clusters, and migrations need people who understand them (Lessons 2.1, 13.3).
  • The ideas carry over directly: blocks and replicas, data locality, shuffle by key, small files, YARN scheduling, idempotent pipelines.
  • My capstone shows both: a MapReduce job and a Spark job on the same cluster, verified the same way.