Memory and CPU tuning

YARN hands out memory and CPU in containers of fixed steps, and inside each container a JVM has its own, smaller heap. This lesson measures both on our three-worker cluster: how requests are rounded, how many containers fit, when CPU counts, and how a heap that is too small fails a job. Then it sizes a real production server with the same rules.

Module 11 · YARN⏱ 30 minIntermediateRounding and packing measuredReal heap failure and fixRuns in your browser (Python)8 practice problems
1 The story

Why did 384 MB become 512?

Riya asked YARN for map containers of 384 MB. The ResourceManager's numbers say every map is holding 512 MB. Kabir has the opposite problem: his job keeps every order line in a list, and with a 64 MB heap it died with “Java heap space” ten times before Hadoop gave up. Arjun Sir pulls up one worker's memory on the whiteboard.

One worker: NodeManager pool 2048 MB 512 512 512 512 4 containers fit; a 5th must wait for another node asked 384 MB → granted 512 MB (rounded up to a multiple of 256 MB) Inside one 512 MB container JVM heap: -Xmx300m your objects live here off-heap: code, threads, buffers -Xmx64m: “Error: Java heap space”, 10 failed attempts myphatshala.in
Arjun Sir

Two different limits are at work. YARN hands out containers: slices of a node's memory and CPU, in fixed steps. Inside each container runs a JVM with its own heap limit, which must be smaller than the container.

Riya

So my 384 MB was rounded up by YARN, and Kabir's job ran out of heap inside the JVM?

Kabir

And I can't just set the heap to 2 GB, because then it won't fit in the container, or the container won't fit on the node.

Arjun Sir

Exactly. Today we measure both on our cluster: how requests are rounded, how many containers fit, what happens to CPU, and how a heap that is too small kills a job. Then we size a real 128 GB server the same way.

2 Definition

YARN memory and CPU tuning

Formal definition (write this in exams)

Memory and CPU tuning in YARN is the configuration of how much of each node's resources YARN may allocate and how large each container and its process may be. Each NodeManager advertises a capacity (yarn.nodemanager.resource.memory-mb, yarn.nodemanager.resource.cpu-vcores), which must leave room for the operating system and co-located daemons. Applications request containers of a given memory and number of virtual cores; the scheduler normalizes each request up to a multiple of yarn.scheduler.minimum-allocation-mb and rejects requests above the maximum allocation. A resource calculator decides which dimensions are compared: the DefaultResourceCalculator uses memory only, while the DominantResourceCalculator uses memory and CPU (Dominant Resource Fairness). Inside a container, the JVM heap (-Xmx, set via mapreduce.map.java.opts or a heap-to-container ratio) must be smaller than the container, leaving room for off-heap memory; exceeding the heap raises an OutOfMemoryError, while exceeding the container's physical memory causes the NodeManager to kill it when memory checks are enabled.

In simple words

Decide how much of each machine YARN may hand out, what size the pieces are, and how big the Java program inside each piece may grow. Pieces too big: few tasks run at once. Pieces too small: tasks crash.

Words you'll meet in this lesson

NodeManager capacityMemory and vcores a node offers to YARN (ours: 2048 MB, 2 vcores).
Minimum allocationThe step size: every request is rounded up to a multiple of it (ours: 256 MB).
Maximum allocationThe largest container allowed; never bigger than the largest node.
vcoreA virtual CPU share; usually one per physical core or hyper-thread.
Resource calculatorDefault: compares memory only. Dominant: memory and CPU.
Heap (-Xmx)The JVM's object memory, set inside the container; about 75–80% of it.
Off-heapJVM memory outside the heap: code, thread stacks, buffers, metaspace.
WaveOne round of tasks when there are more tasks than containers.

From our lab (Hadoop 3.5.0, 3 workers × 2048 MB / 2 vcores)

RoundingMap asks 300 → 512 MB, 384 → 512, 600 → 768, 1100 → 1280 MB (multiples of 256)
Packing24 maps of 384 MB: 12 containers running, 6144 of 6144 MB. Maps of 1024 MB: 6 containers, 5632 MB
CPUDefault calculator: 12 containers holding 12 vcores on 6 real vcores. DominantResourceCalculator: 6 containers, 3072 MB, 6 of 6 vcores
HeapA map that keeps every line: -Xmx64m → FAILED, “Error: Java heap space”, Failed map tasks=10. -Xmx300m → succeeded, 169–235 MB used per map

Architecture at a glance: the memory stack of one worker

Every number in a YARN cluster is one layer of this stack. Left: a typical production worker. Right: our lab worker.

Production worker: 128 GB, 32 cores 1. physical RAM 128 GB 16 GB 2. NodeManager pool 112 GB OS, DataNode, NM daemons 4 GB4 GB4 GB… ×28 3. containers: 28 × 4 GB (1 vcore each) 4. heap -Xmx3276m (80%) off ratio set by mapreduce.job.heap.memory-mb.ratio (0.8) Our lab worker (shared laptop) 1. a share of an 8 GB machine 2. NodeManager pool 2048 MB, 2 vcores 512512512512 3. containers: asked 384 MB, granted 512 MB 4. heap -Xmx300m off-heap pmem/vmem checks off in this lab Rule: heap < container ≤ maximum allocation ≤ NodeManager pool < physical RAM Break the first “<” → Java heap space. Break the second → container killed (when checks are on). myphatshala.in

Tuning at three levels

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

Beginner A hostel's rooms and beds

  • A hostel building (node) has 2048 sq ft that the warden (YARN) may rent out; the rest is corridors and the warden's office (OS and daemons).
  • Rooms come only in fixed sizes of 256 sq ft. Ask for 384 and you get 512: the room size is rounded up.
  • Inside your room, your cupboard (heap) is smaller than the room. If you stuff more clothes than the cupboard holds, it bursts (Java heap space), even if the room has space.
  • Bigger rooms mean fewer students fit in the building: 12 rooms of 512 vs 6 of 1024 in our cluster.

Intermediate The settings and how they connect

  • Node: yarn.nodemanager.resource.memory-mb and …cpu-vcores say what a NodeManager offers. Ours: 2048 MB, 2 vcores.
  • Scheduler: yarn.scheduler.minimum-allocation-mb (step) and …maximum-allocation-mb (cap). A 1100 MB request became 1280 MB.
  • Job: mapreduce.map.memory.mb (container) and mapreduce.map.java.opts=-Xmx… (heap). Rule of thumb: heap ≈ 80% of the container. Since Hadoop 3 you may set only the container and let mapreduce.job.heap.memory-mb.ratio (0.8) derive the heap.
  • AM: yarn.app.mapreduce.am.resource.mb (ours 512) is a container too, and counts against the cluster.

Advanced CPU, fairness and safety nets

  • With the DefaultResourceCalculator (the default for the Capacity Scheduler), vcores are recorded but not enforced in placement: our nodes got 12 containers with 1 vcore each on 6 real vcores.
  • The DominantResourceCalculator compares each request on its dominant share (memory or CPU). Our cluster then ran only 6 containers and left half the memory free: CPU had become the limit.
  • Placement is only half the story: to stop a process from using more than its share, NodeManagers use memory checks (yarn.nodemanager.pmem-check-enabled) or Linux cgroups for memory and CPU. Our lab has the memory checks switched off.
  • Tiny jobs can run in uber mode (mapreduce.job.ubertask.enable): all tasks inside the AM's container, saving container start-ups.

Exam tip: For “How do you configure memory for MapReduce on YARN?”, draw the stack (node RAM → yarn.nodemanager.resource.memory-mb → container mapreduce.map.memory.mb → heap -Xmx ≈ 80%), mention rounding to minimum-allocation-mb, and give the two failure modes: Java heap space (heap too small) and container killed for exceeding physical memory (heap or off-heap too big for the container).

3 Types and ways to use it

The properties, and the two resource calculators

PropertyWhereMeansOur labDefault
yarn.nodemanager.resource.memory-mbyarn-siteMemory a node offers to YARN20488192 (or detected)
yarn.nodemanager.resource.cpu-vcoresyarn-sitevcores a node offers28
yarn.scheduler.minimum-allocation-mbyarn-siteStep size of every request2561024
yarn.scheduler.maximum-allocation-mbyarn-siteLargest container8192 (capped by the 2048 MB nodes)8192
mapreduce.map.memory.mb / reducemapred-site or -DContainer size per task384 (→ 512)-1 (derived)
mapreduce.map.java.opts / reducemapred-site or -DJVM options, e.g. -Xmx-Xmx300mfrom ratio
mapreduce.job.heap.memory-mb.ratiomapred-siteHeap = ratio × container, if -Xmx not set(not used)0.8
yarn.app.mapreduce.am.resource.mbmapred-siteApplicationMaster container5121536
mapreduce.map.cpu.vcoresmapred-site or -Dvcores per map11

Default vs Dominant resource calculator

DefaultResourceCalculator

Compares memory only. Simple, and fine when all jobs have similar CPU needs. Our lab: 12 containers on 6 vcores, CPUs oversubscribed 2×.

memory only

DominantResourceCalculator

Each request is judged by its largest share of the cluster. Mixes of CPU-heavy and memory-heavy jobs are shared fairly. Our lab: 6 containers, CPU full, memory half used.

memory + CPU

Fewer, bigger containers

Room for big heaps (joins, sorts, caches) but fewer tasks at once and more waste if tasks are small.

trade-off

More, smaller containers

More parallelism and better packing, but each JVM has fixed overhead and heap-hungry tasks fail.

trade-off

Animated example: sizing a telecom billing job

A telecom operator processes call detail records (CDRs) every night on 20 workers with 96 GB each for YARN. Follow one wrong guess and its fix.

From request to running containers 6 steps
Animated example CDR job · 20 workers × 96 GB YARN · min allocation 1024 MB -D mapreduce.map.memory.mb=3000-D mapreduce.map.java.opts=-Xmx2400m The developer asks for 3000 MB containers. asked 3000 granted 3072 Rounded up to 3 × 1024 MB. … 32 per node 96 GB ÷ 3 GB = 32 containers per node,640 maps at once on 20 nodes. night 1: maps failError: Java heap spacea lookup of 12 lakh tower IDs needs ~3 GB of heap The heap (2400 MB) is too small for the data. -D mapreduce.map.memory.mb=4096-D mapreduce.map.java.opts=-Xmx3276m Fix: bigger container AND heap at 80% of it. night 2: succeeds24 containers per node, 480 maps at oncefewer at once, but none fail and retry Bigger containers cost parallelism. Measure, then size. Illustrative example; numbers chosen to show the arithmetic. myphatshala.in

Size containers from the task's real needs, not from the node. Look at “Peak Map Physical memory” and GC time in the job counters (Lesson 8.1 showed about 290 MB for a 4.7 MB file), add headroom, and round to your minimum allocation.

4 Step by step

Measuring containers, CPU and heap on our cluster

All of these use the Module 8 lab. Parts 1–3 use Hadoop's built-in sleep job (tasks that only sleep), so the only thing that changes is the request. The full script, tuning_lab.sh, is in “Run it on your PC”.

  1. Ask for odd sizes and see what YARN grants

    yarn org.apache.hadoop.mapreduce.SleepJob -D mapreduce.map.memory.mb=300 -m 1 -mt 20000 -r 1 -rt 1000
    curl -s http://resourcemanager:8088/ws/v1/cluster/apps/<app>     # allocatedMB while it runs
    map asks 300 MB -> app holds 1024 MB = AM 512 + map 512 MB map asks 384 MB -> app holds 1024 MB = AM 512 + map 512 MB map asks 600 MB -> app holds 1280 MB = AM 512 + map 768 MB map asks 1100 MB -> app holds 1792 MB = AM 512 + map 1280 MB
    Every request is normalized up to a multiple of yarn.scheduler.minimum-allocation-mb (256 MB here). 300 and 384 both become 512. With the common production minimum of 1024 MB, all four would have become 1024, 1024, 1024 and 2048.
  2. Count how many maps fit at once

    # 24 maps of 30 s each, first with 384 MB maps, then with 1024 MB maps
    yarn org.apache.hadoop.mapreduce.SleepJob -D mapreduce.map.memory.mb=384  -m 24 -mt 30000 -r 1 -rt 1000
    yarn org.apache.hadoop.mapreduce.SleepJob -D mapreduce.map.memory.mb=1024 -m 24 -mt 30000 -r 1 -rt 1000
    maps of 384 MB: running containers 12 | memory 6144 of 6144 MB | vcores 12 of 12 maps of 1024 MB: running containers 6 | memory 5632 of 6144 MB | vcores 6 of 6
    6144 MB ÷ 512 = 12 containers (1 AM + 11 maps). At 1024 MB: one node holds the 512 MB AM plus one map (1536 MB, 512 MB left unused), the other two hold 2 maps each: 6 containers, 5632 MB. 24 maps then need about 5 waves instead of 3.
  3. Notice CPU being ignored

    12 containers × 1 vcore = 12 vcores
    3 nodes × 2 vcores = 6 vcores offered
    With the DefaultResourceCalculator only memory decides placement, so 12 one-vcore containers ran on 6 vcores. (The metric's total is allocated + available, which is why it shows “of 12”.) Lesson 11.1 saw the same thing per node: “3 vcores used” on a 2-vcore worker.
  4. Switch to the DominantResourceCalculator (and crash the RM first)

    CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.resource-calculator=org.apache.hadoop.yarn.util.resource.DominantResourceCalculator
    Caused by: java.lang.IllegalStateException: Queue configuration missing child queue names for root
    The apache/hadoop image writes capacity-scheduler.xml from the env keys you give it, so the file contained only our one key and no queues: the ResourceManager refused to start. The fix repeats root.queues=default and root.default.capacity=100 in the same file.
  5. Run the same 24 maps under DRF

    bash rm.sh drf
    yarn org.apache.hadoop.mapreduce.SleepJob -D mapreduce.map.memory.mb=384 -m 24 -mt 30000 -r 1 -rt 1000
    calculator default: running containers 12 | memory 6144 of 6144 MB | vcores 12 of 12 calculator drf: running containers 6 | memory 3072 of 6144 MB | vcores 6 of 6
    Now every container's dominant share is CPU (1 of 2 vcores is 50% of a node, 512 of 2048 MB only 25%). Each node takes 2 containers, half the memory stays free. Safer for CPU-heavy work, but half as many tasks at once here.
  6. Make a map run out of heap, then fix it

    hadoop jar mr11.jar HeapHog -D mapreduce.map.java.opts=-Xmx64m \
      -D mapreduce.input.fileinputformat.split.maxsize=33554432 /user/hadoop/data/orders_x30.csv /out/hog64
    hadoop jar mr11.jar HeapHog -D mapreduce.map.java.opts=-Xmx300m \
      -D mapreduce.input.fileinputformat.split.maxsize=33554432 /user/hadoop/data/orders_x30.csv /out/hog300
    -Xmx64m: failed with state FAILED 7 Error: Java heap space Failed map tasks=10 Launched map tasks=15 kept 415,675 lines; heap used 54 MB of max 64 MB -Xmx300m: completed successfully Launched map tasks=5 kept 2,147,952 lines; heap used 222 MB of max 300 MB
    HeapHog keeps every line in a list (a common bug: collecting instead of streaming). A 32 MB split holds about 21 lakh lines, roughly 100 bytes each once they are Java objects. With 64 MB of heap only the small last split survived; each big split failed 4 times and the job was failed. 300 MB was enough: the container (512 MB) had room for it.

Raising -Xmx without raising the container is a trap. In our lab the memory checks are off, so a too-big heap would simply eat into other containers' memory. On a production cluster with pmem-check or cgroups, the NodeManager kills the container (“is running beyond physical memory limits … Killing container”, exit code 143). We did not reproduce that message here.

5 See it happen

Requests, rounding, packing and heap

Six steps from our lab: what the terminal shows, and what YARN and the JVM do.

Containers and heap 6 steps
What you see hadoop@namenode : /tmp/mr $ … SleepJob -D mapreduce.map.memory.mb=600map asks 600 MB -> app holds 1280 MB= AM 512 + map 768 MB maps of 384 MB:running containers 12memory 6144 of 6144 MB maps of 1024 MB:running containers 6memory 5632 of 6144 MB calculator drf:running containers 6memory 3072 of 6144 MBvcores 6 of 6 $ hadoop jar mr11.jar HeapHog -Xmx64m …Error: Java heap spaceFailed map tasks=10failed with state FAILED $ … HeapHog -Xmx300m …completed successfullykept 2,147,952 lines;heap used 222 MB of max 300 MB myphatshala.in
Behind the screen worker1worker2worker3 2048 MB · 2 vcores2048 MB · 2 vcores2048 MB · 2 vcores AM 512map 768 600 → next multiple of 256 = 768 4 × 512 MB per node = 12 containersbut 4 vcores used on a 2-vcore node AM 512 10241024102410241024 512 free 6 containers; 512 MB stranded on worker1 2 vcores full2 vcores full2 vcores full DRF: dominant share is CPU (50% per container)→ 2 per node, half the memory free heap 64 MBFULL ~21 lakh line objects need ~200 MB;OutOfMemoryError, 4 attempts per task heap 300 MB222 usedoff-heap 300 MB heap inside a 512 MB container:fits, with room for off-heap myphatshala.in
6 Real-world use

Tuning at work

CDRs4 GB maps

1. Telecom: nightly billing on fixed-size containers

Billing jobs join call records with tariff tables. Platform teams set a standard map size (e.g. 4 GB, heap 80%) and let only a few approved jobs ask for more, so the cluster packs predictably.

Job profile→Standard container size→Predictable waves
recordsDRF on

2. Hospital analytics: CPU-heavy and memory-heavy jobs together

Image-feature extraction burns CPU; patient-record joins need memory. With the DominantResourceCalculator neither kind can starve the other by grabbing only one resource.

Mixed workloads→DominantResourceCalculator→Fair CPU + memory
GC time

3. E-commerce sale day: watching GC before it hurts

Before a big sale, data teams replay a day of clickstream and check “GC time elapsed” and peak memory counters. Jobs spending more than about 10% of task time in GC get bigger heaps or smaller splits.

Rehearsal run→Counters: GC, peak memory→Resize before the sale
exec+10%

4. Spark on YARN: executor memory plus overhead

A Spark executor asks YARN for spark.executor.memory plus spark.executor.memoryOverhead (by default 10%, at least 384 MB), then rounded up. An 8 GB executor becomes about a 9 GB container: forget the overhead and the sizing math is off.

executor 8g→+ overhead 819 MB→container 9216 MB
uber

5. Banking reports: many tiny jobs

Hundreds of small reconciliation jobs each read a few MB. Uber mode, or moving them out of MapReduce entirely, avoids paying a container start-up per task.

Tiny job→ubertask: tasks in the AM→Seconds saved per job

Simplified; organisations differ in tools and policies.

7 Run the code

A container sizing calculator

This reproduces our lab: round each request up to the minimum allocation, then pack containers onto nodes by memory only (DefaultResourceCalculator) or by memory and CPU (DominantResourceCalculator). The AM goes first, as in YARN.

import math
MIN_MB = 256
NODES = [{"mb": 2048, "vc": 2} for _ in range(3)]

def normalize(mb):
    return math.ceil(mb / MIN_MB) * MIN_MB          # always rounded UP

def pack(map_mb, n_maps, am_mb=512, use_cpu=False):
    free = [dict(n) for n in NODES]
    asks = [normalize(am_mb)] + [normalize(map_mb)] * n_maps
    placed = 0
    for ask in asks:
        for n in free:
            if n["mb"] >= ask and (not use_cpu or n["vc"] >= 1):
                n["mb"] -= ask
                n["vc"] -= 1
                placed += 1
                break
    used = sum(2048 - n["mb"] for n in free)
    return placed, used

for mb in [300, 384, 600, 1100]:
    print(f"map asks {mb:4} MB -> granted {normalize(mb)} MB")
print("384 MB maps, memory only:", pack(384, 24))
print("1024 MB maps, memory only:", pack(1024, 24))
print("384 MB maps, DRF (CPU too):", pack(384, 24, use_cpu=True))
map asks 300 MB -> granted 512 MB map asks 384 MB -> granted 512 MB map asks 600 MB -> granted 768 MB map asks 1100 MB -> granted 1280 MB 384 MB maps, memory only: (12, 6144) 1024 MB maps, memory only: (6, 5632) 384 MB maps, DRF (CPU too): (6, 3072)
  • return math.ceil(mb / MIN_MB) * MIN_MB # always rounded UPyarn.scheduler.minimum-allocation-mb is a step size: 300 and 384 both become 512.
  • if n["mb"] >= ask and (not use_cpu or n["vc"] >= 1):The DefaultResourceCalculator only checks memory; the DominantResourceCalculator also needs a free vcore.
  • print("384 MB maps, DRF (CPU too):", pack(384, 24, use_cpu=True))Lab: 6 containers and 3072 MB under DRF, against 12 containers and 6144 MB with memory only.

Run it on your PC: containers, CPU and heap

This uses the Module 8 lab (mr-lab folder). rm.sh (from Lesson 11.3) recreates only the ResourceManager with another configuration; HDFS and the NodeManagers keep running. Parts 1–3 use Hadoop's built-in sleep job from the MapReduce tests jar. Each part runs for a minute or two, so the whole script takes about 10 minutes.

Rounding, packing and the CPU calculator

# tuning_lab.sh: how YARN sizes containers, how many fit, and what happens when the heap is too small
# Run on your PC in the mr-lab folder (it switches the ResourceManager's calculator in part 3).
T=/opt/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-3.5.0-tests.jar
sleepjob() {  # extra args...
  docker exec -d lab8-namenode-1 bash -c "export HADOOP_CLASSPATH=$T; yarn org.apache.hadoop.mapreduce.SleepJob -libjars $T $* -r 1 -rt 1000 > /tmp/tune.log 2>&1"
}
metrics() { docker exec lab8-namenode-1 curl -s http://resourcemanager:8088/ws/v1/cluster/metrics | python3 -c '
import sys, json; m = json.load(sys.stdin)["clusterMetrics"]
print("   running containers %2d | memory %4d of %d MB | vcores %d of %d" % (m["containersAllocated"], m["allocatedMB"], m["totalMB"], m["allocatedVirtualCores"], m["totalVirtualCores"]))'; }
wait_job() { until docker exec lab8-namenode-1 grep -qE "completed successfully|failed with state" /tmp/tune.log 2>/dev/null; do sleep 3; done; }
bash rm.sh default > /dev/null 2>&1
echo "== 1. a request is rounded UP to a multiple of the minimum allocation (256 MB here)"
for mb in 300 384 600 1100; do
  sleepjob -D mapreduce.map.memory.mb=$mb -m 1 -mt 20000
  sleep 14
  app=$(docker exec lab8-namenode-1 grep -o "application_[0-9_]*" /tmp/tune.log | head -1)
  docker exec lab8-namenode-1 curl -s http://resourcemanager:8088/ws/v1/cluster/apps/$app | python3 -c '
import sys, json; a = json.load(sys.stdin)["app"]
print("map asks %4s MB -> app holds %4d MB = AM 512 + map %d MB" % (sys.argv[1], a["allocatedMB"], a["allocatedMB"] - 512))' $mb
  docker exec lab8-resourcemanager-1 yarn application -kill $app > /dev/null 2>&1; sleep 3
done
echo "== 2. how many maps run at once: 384 MB maps vs 1024 MB maps (24 maps of 30 s each)"
for mb in 384 1024; do
  sleepjob -D mapreduce.map.memory.mb=$mb -m 24 -mt 30000
  sleep 25; echo " maps of $mb MB:"; metrics
  app=$(docker exec lab8-namenode-1 grep -o "application_[0-9_]*" /tmp/tune.log | head -1)
  docker exec lab8-resourcemanager-1 yarn application -kill $app > /dev/null 2>&1; sleep 3
done
echo "== 3. CPU: the default calculator ignores vcores; DominantResourceCalculator counts them"
for calc in default drf; do
  bash rm.sh $calc > /dev/null 2>&1
  sleepjob -D mapreduce.map.memory.mb=384 -m 24 -mt 30000
  sleep 25; echo " calculator $calc:"; metrics
  app=$(docker exec lab8-namenode-1 grep -o "application_[0-9_]*" /tmp/tune.log | head -1)
  docker exec lab8-resourcemanager-1 yarn application -kill $app > /dev/null 2>&1; sleep 3
done
bash rm.sh default > /dev/null 2>&1

# rm.sh (same folder) switches the ResourceManager configuration:
# # rm.sh fifo|capacity|capacity-preempt|capacity-ulf|capacity-ulf-preempt|fair|drf|default : recreate ONLY the ResourceManager with another scheduler
# # (run in the mr-lab folder; HDFS and the NodeManagers keep running)
# mode=$1
# envs="./config"
# case $mode in
#   fifo) envs="$envs, ./sched-fifo.env" ;;
#   capacity) envs="$envs, ./sched-capacity.env" ;;
#   capacity-preempt) envs="$envs, ./sched-capacity.env, ./sched-capacity-preempt.env" ;;
#   capacity-ulf) envs="$envs, ./sched-capacity.env, ./sched-capacity-ulf.env" ;;
#   capacity-ulf-preempt) envs="$envs, ./sched-capacity.env, ./sched-capacity-ulf.env, ./sched-capacity-preempt.env" ;;
#   fair) envs="$envs, ./sched-fair.env" ;;
#   drf) envs="$envs, ./sched-drf.env" ;;
#   default) ;;
# esac
# cat > rm-override.yaml <<Y
# services:
#   resourcemanager:
#     env_file: [$envs]
#     volumes: [./fair-scheduler.xml:/opt/fair-scheduler.xml:ro]
# Y
# docker compose -p lab8 -f docker-compose.yaml -f rm-override.yaml up -d resourcemanager 2>&1 | tail -1
# until [ "$(docker exec lab8-resourcemanager-1 yarn node -list 2>/dev/null | grep -c RUNNING)" = 3 ]; do sleep 3; done
# docker exec lab8-resourcemanager-1 bash -c 'echo "scheduler: $(grep -o "scheduler\.[a-z]*\.[A-Za-z]*Scheduler" /opt/hadoop/etc/hadoop/yarn-site.xml | tail -1)"; mapred queue -list 2>/dev/null | grep -E "Queue Name|Scheduling Info" | paste - - | sed "s/Scheduling Info : //"'

# Run, in the mr-lab folder:
#   bash tuning_lab.sh
== 1. a request is rounded UP to a multiple of the minimum allocation (256 MB here) map asks 300 MB -> app holds 1024 MB = AM 512 + map 512 MB map asks 384 MB -> app holds 1024 MB = AM 512 + map 512 MB map asks 600 MB -> app holds 1280 MB = AM 512 + map 768 MB map asks 1100 MB -> app holds 1792 MB = AM 512 + map 1280 MB == 2. how many maps run at once: 384 MB maps vs 1024 MB maps (24 maps of 30 s each) maps of 384 MB: running containers 12 | memory 6144 of 6144 MB | vcores 12 of 12 maps of 1024 MB: running containers 6 | memory 5632 of 6144 MB | vcores 6 of 6 == 3. CPU: the default calculator ignores vcores; DominantResourceCalculator counts them calculator default: running containers 12 | memory 6144 of 6144 MB | vcores 12 of 12 calculator drf: running containers 6 | memory 3072 of 6144 MB | vcores 6 of 6

Real output. The vcore metric adds allocated and available vcores, so with the memory-only calculator it reports “of 12” although the three nodes offer 6.

DominantResourceCalculator: the configuration that works

# Setting ANY capacity-scheduler key through env makes the image write a capacity-scheduler.xml
# with only those keys, so the queue list must be repeated here (without it the RM fails to start:
# "Queue configuration missing child queue names for root").
CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.root.queues=default
CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.root.default.capacity=100
CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.resource-calculator=org.apache.hadoop.yarn.util.resource.DominantResourceCalculator

# Our first version had only the last line. The ResourceManager did not start:
#   docker logs lab8-resourcemanager-1 | grep "missing child queue"
Caused by: java.lang.IllegalStateException: Queue configuration missing child queue names for root

Real log line from the failed first attempt. Setting any CAPACITY-SCHEDULER.XML_ key makes the apache/hadoop image write capacity-scheduler.xml with only those keys, so the queue list must be repeated.

A map that keeps every line in memory

// HeapHog.java: a deliberately memory-hungry map-only job. Each map task keeps EVERY line it reads
// in a list (a common real-world bug: collecting instead of streaming), then writes how many it kept.
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
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.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.GenericOptionsParser;

public class HeapHog {
  public static class M extends Mapper<LongWritable, Text, NullWritable, Text> {
    private final List<String> kept = new ArrayList<>();
    @Override
    protected void map(LongWritable k, Text line, Context ctx) { kept.add(line.toString()); }
    @Override
    protected void cleanup(Context ctx) throws IOException, InterruptedException {
      Runtime r = Runtime.getRuntime();
      String msg = String.format("kept %,d lines; heap used %d MB of max %d MB", kept.size(),
          (r.totalMemory() - r.freeMemory()) >> 20, r.maxMemory() >> 20);
      System.err.println(msg);
      ctx.write(NullWritable.get(), new Text(msg));
    }
  }

  public static void main(String[] args) throws Exception {
    Configuration conf = new Configuration();
    String[] rest = new GenericOptionsParser(conf, args).getRemainingArgs();
    Job job = Job.getInstance(conf, "heap hog");
    job.setJarByClass(HeapHog.class);
    job.setMapperClass(M.class);
    job.setNumReduceTasks(0);
    job.setOutputKeyClass(NullWritable.class);
    job.setOutputValueClass(Text.class);
    FileInputFormat.addInputPath(job, new Path(rest[0]));
    FileOutputFormat.setOutputPath(job, new Path(rest[1]));
    System.exit(job.waitForCompletion(true) ? 0 : 1);
  }
}

// In the mr-lab folder:
//   docker exec lab8-namenode-1 mkdir -p /tmp/mr11
//   docker cp HeapHog.java lab8-namenode-1:/tmp/mr11/
//   docker exec lab8-namenode-1 bash -c "cd /tmp/mr11 && mkdir -p c && javac -cp \$(hadoop classpath) -d c HeapHog.java && jar cf mr11.jar -C c ."
// Then run hog.sh below.
== 4. a map that keeps every line in memory: heap 64 MB vs 300 MB (orders_x30.csv cut into 32 MB splits) -Xmx64m: failed with state FAILED 7 Error: Java heap space Failed map tasks=10 Launched map tasks=15 kept 415,675 lines; heap used 54 MB of max 64 MB -Xmx300m: completed successfully Launched map tasks=5 kept 2,147,952 lines; heap used 222 MB of max 300 MB kept 2,147,373 lines; heap used 235 MB of max 300 MB kept 2,144,628 lines; heap used 169 MB of max 300 MB kept 2,144,372 lines; heap used 209 MB of max 300 MB kept 415,675 lines; heap used 68 MB of max 300 MB

Real output of hog.sh. With 64 MB of heap only the small last split (415,675 lines) survived; every 32 MB split failed 4 times and the job failed. The “7” counts distinct error lines the client printed, not attempts.

The heap experiment script

echo "== 4. a map that keeps every line in memory: heap 64 MB vs 300 MB (orders_x30.csv cut into 32 MB splits)"
for heap in 64 300; do
  docker exec lab8-namenode-1 bash -c "cd /tmp/mr11; hdfs dfs -rm -r -f -skipTrash /out/hog$heap > /dev/null 2>&1
    hadoop jar mr11.jar HeapHog -D mapreduce.map.java.opts=-Xmx${heap}m -D mapreduce.input.fileinputformat.split.maxsize=33554432 /user/hadoop/data/orders_x30.csv /out/hog$heap > /tmp/hog.log 2>&1
    echo \" -Xmx${heap}m: \$(grep -oE 'completed successfully|failed with state [A-Z]+' /tmp/hog.log)\"
    grep -oE 'Error: Java heap space|OutOfMemoryError: [A-Za-z ]+|GC overhead limit exceeded' /tmp/hog.log | sort | uniq -c | head -2
    grep -E 'Failed map tasks|Launched map tasks' /tmp/hog.log | sed 's/^\s*/   /'
    hdfs dfs -cat /out/hog$heap/part-m-* 2>/dev/null | sed 's/^/   /'"
done

# Run, in the mr-lab folder:  bash hog.sh
== 4. a map that keeps every line in memory: heap 64 MB vs 300 MB (orders_x30.csv cut into 32 MB splits) -Xmx64m: failed with state FAILED 7 Error: Java heap space Failed map tasks=10 Launched map tasks=15 kept 415,675 lines; heap used 54 MB of max 64 MB -Xmx300m: completed successfully Launched map tasks=5 kept 2,147,952 lines; heap used 222 MB of max 300 MB kept 2,147,373 lines; heap used 235 MB of max 300 MB kept 2,144,628 lines; heap used 169 MB of max 300 MB kept 2,144,372 lines; heap used 209 MB of max 300 MB kept 415,675 lines; heap used 68 MB of max 300 MB

Real output (same run as above). orders_x30.csv is the 140 MB file from Lesson 8.1.

8 Line by line

Normalizing three requests

Watch each request being rounded up to the next multiple of the minimum allocation.

Worked examples: real problems, solved and traced in memory

Four sizing problems from different industries, each solved with the same memory-stack rules.

Example 1BeginnerBanking · capacity

How many containers does a bank's worker give?

Problem statement

A bank's Hadoop worker has 128 GB RAM and 32 cores. Reserve 16 GB for the OS, the DataNode and the NodeManager. Map containers are 4 GB with 1 vcore. How many maps run at once per node, and on a 25-node cluster?

Input
128 GB − 16 GB · 4 GB maps · 32 cores (keep 2)
Expected output
by memory 28, by CPU 30 -> 28 per node, 700 on 25 nodes

Approach

  1. YARN memory = RAM − reserve.
  2. By memory: pool ÷ container size.
  3. By CPU: usable vcores. The smaller number wins (with DRF).

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 2IntermediateTelecom · rounding

What does a 3000 MB request really cost?

Problem statement

A telecom job asks for 3000 MB maps on a cluster whose minimum allocation is 1024 MB and whose NodeManagers offer 96 GB. How much memory does each map really hold, and how much is wasted per node compared with asking for exactly 3072 MB?

Input
ask 3000 · min 1024 · 96 GB pool
Expected output
granted 3072 MB, 32 per node, 2304 MB per node granted but never asked for

Approach

  1. Round up to a multiple of 1024.
  2. Containers per node = pool ÷ granted.
  3. Waste = granted − asked, per container.

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 3IntermediateE-commerce · Spark

Sizing Spark executors on YARN

Problem statement

A sale-day Spark job uses executors with spark.executor.memory=8g. Overhead is 10% of that, at least 384 MB. The YARN minimum allocation is 1024 MB, and nodes offer 112 GB. What container does each executor get, and how many fit per node?

Input
8 GB heap + max(384 MB, 10%) · step 1024 · 112 GB
Expected output
overhead 819 MB -> container 9216 MB, 12 executors per node

Approach

  1. Overhead = max(384, 0.10 × 8192).
  2. Request = heap + overhead, rounded up to 1024.
  3. Executors per node = pool ÷ container.

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 4AdvancedHealthcare · DRF

Which resource limits a hospital cluster?

Problem statement

A hospital's 10-node cluster offers 64 GB and 16 vcores per node to YARN. Job A (image features) uses containers of 2 GB / 2 vcores; job B (record joins) uses 8 GB / 1 vcore. With the DominantResourceCalculator, what is each container's dominant share of one node, and how many of each fit on an empty node?

Input
node 64 GB / 16 vcores · A 2 GB/2 · B 8 GB/1
Expected output
A: memory 3.1%, CPU 12.5% -> dominant CPU, 8 per node
B: memory 12.5%, CPU 6.2% -> dominant memory, 8 per node

Approach

  1. Share of memory and share of CPU per container.
  2. Dominant share = the larger of the two.
  3. Fit = min(memory fit, CPU fit).

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

  • Stack: physical RAM → reserve for OS/daemons → yarn.nodemanager.resource.memory-mb → containers → JVM heap (-Xmx ≈ 80%) + off-heap.
  • Requests round UP to yarn.scheduler.minimum-allocation-mb: lab 300/384 → 512, 600 → 768, 1100 → 1280 MB.
  • Packing: 384 MB maps → 12 containers (6144 MB); 1024 MB maps → 6 (5632 MB, 512 MB stranded).
  • DefaultResourceCalculator = memory only (12 vcores handed out on 6); DominantResourceCalculator = memory + CPU (6 containers).
  • “Java heap space” → raise -Xmx (and the container) or stop collecting data in memory; “beyond physical memory” → bigger container or smaller heap.
  • Setting any CAPACITY-SCHEDULER.XML_ key in the apache/hadoop image rewrites the whole file: repeat the queues.
  • Spark on YARN: container = executor memory + overhead (max 384 MB, 10%), rounded up.

Interview and viva questions

How do you calculate the number of containers per node in YARN?

Take the memory the NodeManager offers (yarn.nodemanager.resource.memory-mb) and divide by the container size after rounding it up to a multiple of yarn.scheduler.minimum-allocation-mb. If the DominantResourceCalculator is used, also divide the node's vcores by the vcores per container and take the smaller number. Remember the ApplicationMaster also occupies a container.

What is the difference between mapreduce.map.memory.mb and mapreduce.map.java.opts?

mapreduce.map.memory.mb is the size of the YARN container for a map task: what YARN reserves and enforces. mapreduce.map.java.opts sets JVM options such as -Xmx, the maximum heap inside that container. The heap must be smaller than the container, typically about 80% of it, to leave room for off-heap memory.

What is the DominantResourceCalculator in YARN?

A resource calculator for the Capacity Scheduler that considers both memory and CPU. Each request is compared on its dominant share, the larger of its memory and CPU fractions, following Dominant Resource Fairness. The default DefaultResourceCalculator compares memory only, so CPU can be oversubscribed.

Why does YARN give a container more memory than requested?

The scheduler normalizes every request up to a multiple of yarn.scheduler.minimum-allocation-mb (and a similar increment for the Fair Scheduler). In our lab, with a 256 MB minimum, a 384 MB request became 512 MB and 600 MB became 768 MB.

How do you fix “Container is running beyond physical memory limits”?

The task process (heap plus off-heap) used more memory than its container. Either increase the container size (mapreduce.map.memory.mb or mapreduce.reduce.memory.mb) while keeping the heap around 80% of it, or reduce the heap or off-heap use (smaller buffers, less data held in memory). For Spark, raise spark.executor.memoryOverhead.

Practice problems

Sizing and tuning exercises using the real numbers from our lab.

P1Easy

Round it

Round 384, 513 and 1025 MB up to multiples of 256 MB.

Input
three requests
Expected output
[512, 768, 1280]
Show solution and explanation
import math
print([math.ceil(x / 256) * 256 for x in (384, 513, 1025)])
[512, 768, 1280]

How it works: [512, 768, 1280]: one MB over a boundary costs a whole extra step.

P2Easy

Lab cluster total

Three NodeManagers offer 2048 MB each. How many 512 MB containers fit, and how many maps run once the 512 MB AM is placed?

Input
3 × 2048 MB
Expected output
12 containers, 11 maps at once
Show solution and explanation
total = 3 * 2048 // 512
print(total, "containers,", total - 1, "maps at once")
12 containers, 11 maps at once

How it works: 12 containers, 11 maps: exactly what the lab measured (running containers 12).

P3Easy

Heap ratio

Our maps asked for 384 MB with -Xmx300m but were granted 512 MB. What fraction of the request and of the grant is the heap?

Input
300 / 384 · 300 / 512
Expected output
78% of the request, 59% of the grant
Show solution and explanation
print(f"{300 / 384:.0%} of the request, {300 / 512:.0%} of the grant")
78% of the request, 59% of the grant

How it works: 78% of what we asked, 59% of what we got: rounding left extra off-heap room.

P4Medium

Stranded memory

Each node has 2048 MB. With a 512 MB AM on one node and 1024 MB maps, how much memory can no map use?

Input
AM 512 · maps 1024
Expected output
512 MB stranded
Show solution and explanation
node = 2048
left_on_am_node = node - 512 - (node - 512) // 1024 * 1024
print(left_on_am_node, "MB stranded")
512 MB stranded

How it works: 512 MB, which is why the lab used 5632 of 6144 MB.

P5Medium

Pick a container size

A worker offers 96 GB to YARN. Compare 2, 3, 4 and 6 GB maps: maps per node and leftover memory.

Input
96 GB pool
Expected output
2 GB maps: 48 per node, 0 MB left
3 GB maps: 32 per node, 0 MB left
4 GB maps: 24 per node, 0 MB left
6 GB maps: 16 per node, 0 MB left
Show solution and explanation
pool = 96 * 1024
for gb in (2, 3, 4, 6):
    c = gb * 1024
    print(f"{gb} GB maps: {pool // c:2d} per node, {pool % c} MB left")
2 GB maps: 48 per node, 0 MB left 3 GB maps: 32 per node, 0 MB left 4 GB maps: 24 per node, 0 MB left 6 GB maps: 16 per node, 0 MB left

How it works: All divide evenly here; on a 100 GB pool 3 GB would strand 1 GB. Choose sizes that divide the pool.

P6Medium

Heap for a lookup table

A map loads a lookup of 12 lakh entries at about 250 bytes each, plus 200 MB for the framework and buffers. What heap and what container (heap = 80%, step 1024 MB)?

Input
12 lakh × 250 B + 200 MB
Expected output
heap 486 MB -> container 1024 MB
Show solution and explanation
import math
heap = 1_200_000 * 250 / 1024**2 + 200
container = math.ceil(heap / 0.8 / 1024) * 1024
print(f"heap {heap:.0f} MB -> container {container} MB")
heap 486 MB -> container 1024 MB

How it works: About 486 MB of heap, so a 1024 MB container. Measure first; object overhead often doubles estimates.

P7Business problem

Bigger containers or more nodes?

A nightly job fails with heap errors at 2 GB maps. Option A: 4 GB maps (half as many at once, job 3 h instead of 2 h). Option B: add 10 nodes at ₹40,000 each per month to keep 2 h. Which is cheaper for a job that has a 4-hour window?

Input
3 h vs 2 h · 10 × ₹40,000
Expected output
A fits the window: True | B costs Rs 400000 per month
Show solution and explanation
window_h, a_hours = 4, 3
print("A fits the window:", a_hours <= window_h, "| B costs Rs", 10 * 40000, "per month")
A fits the window: True | B costs Rs 400000 per month

How it works: A fits the 4-hour window at no extra cost (illustrative numbers); B only makes sense if the window shrinks or other jobs need the capacity.

P8Advanced

DRF on our lab

Simulate DRF packing on our 3 nodes (2048 MB, 2 vcores) for an AM of 512 MB/1 vcore and 24 maps of 384 MB/1 vcore (rounded to 512). How many containers and how much memory are used?

Input
3 nodes · 1 AM + 24 maps
Expected output
6 containers, 3072 MB
Show solution and explanation
nodes = [[2048, 2] for _ in range(3)]
placed = used = 0
for mb in [512] + [512] * 24:
    for n in nodes:
        if n[0] >= mb and n[1] >= 1:
            n[0] -= mb; n[1] -= 1; placed += 1; used += mb
            break
print(placed, "containers,", used, "MB")
6 containers, 3072 MB

How it works: 6 containers, 3072 MB: exactly the lab's “running containers 6 | memory 3072 of 6144 MB”.

Real-world scenario questions (asked in company interviews)

Memory and CPU sizing questions come up in every Hadoop and Spark platform interview, usually with a failure story attached.

YARN · basicsFresher

“What is the difference between container memory and heap memory?”

A standard opening question for YARN.

The interviewer is checking: the two limits and how they relate.

Show a strong answer
  • Container memory (mapreduce.map.memory.mb) is what YARN reserves on a node and can enforce.
  • Heap (-Xmx in mapreduce.map.java.opts) is the JVM\'s object space inside that container, about 80% of it.
  • Heap too small → OutOfMemoryError; heap plus off-heap too big → the NodeManager kills the container.
Operations · failuresExperienced

Jobs fail with “Java heap space” after a data volume increase

Nothing changed in the code; input doubled after a new region was added.

The interviewer is checking: diagnosis before throwing memory at it.

Show a strong answer
  • Find which tasks fail (map or reduce) and why: a reducer with a hot key, or code that collects values into a list.
  • Fix the code if it buffers data; reduce split size or add reducers to spread load.
  • If memory is really needed, raise the container and the heap together, keeping the 80% ratio.
Capacity · designExperienced

Size YARN for a new 40-node cluster of 256 GB / 48-core machines

Each node also runs a DataNode and an HBase RegionServer with a 32 GB heap.

The interviewer is checking: reserves, steps and CPU.

Show a strong answer
  • Reserve OS (~8 GB), DataNode (~4 GB), NodeManager (~2 GB) and RegionServer (32 GB + overhead): about 200 GB for YARN.
  • Pick a minimum allocation (e.g. 2 GB) and standard container sizes that divide the pool; keep 2–4 cores for daemons.
  • Use the DominantResourceCalculator if CPU-heavy jobs share the cluster; enable cgroups for isolation.
YARN · CPUFresher

“The ResourceManager UI shows more vcores used than the cluster has. Is it broken?”

A junior admin sees 12 vcores in use on a 6-vcore cluster.

The interviewer is checking: resource calculators.

Show a strong answer
  • Not broken: the Capacity Scheduler\'s DefaultResourceCalculator places containers by memory only, so vcores are recorded but not limited.
  • Switch to the DominantResourceCalculator so CPU is respected; in our lab that cut 12 containers to 6.
  • For real CPU isolation, enable the LinuxContainerExecutor with cgroups.