A ten-second report that waited two minutes
Monday morning. Kabir starts a big experiment on the college cluster: 30 map tasks, 40 seconds each. Fifteen seconds later Riya submits the placement-cell report the principal needs at 9:30. Her job has two tiny tasks, 5 seconds of work each. It sits in ACCEPTED, then waits. And waits. The report that needs ten seconds of work finishes after 2 minutes 40 seconds, because the cluster gave every free container to whoever asked first.
Sir, my job was tiny. Why did YARN make it wait behind Kabir's?
Because the ResourceManager was running the FIFO scheduler: first in, first out. Kabir asked first, so every container that freed up went to him until his last task had a home. YARN doesn't decide who is important. The scheduler does, and you choose the scheduler and its queues.
And when we gave our team a separate queue, my experiment became slower: 430 seconds instead of 199.
That is the whole lesson in two numbers. Every scheduler trades something. Today we run the same pair of jobs under FIFO, the Capacity Scheduler (three different settings) and the Fair Scheduler, and read exactly what each one gave and took away.
The YARN scheduler
The scheduler is the pluggable component of the YARN ResourceManager that decides which application receives each free container, based on configured queues and policies. It is a pure scheduler: it doesn't monitor or restart applications. Hadoop ships three: the FIFO Scheduler, which serves applications in submission order; the Capacity Scheduler (the default in Apache Hadoop 3), which divides the cluster into a hierarchy of queues, each guaranteed a percentage of resources, able to grow elastically up to a maximum when others are idle, with per-user limits and optional preemption to reclaim guaranteed capacity; and the Fair Scheduler, which divides resources so that all running applications (or queues, by weight) receive an equal fair share over time, with minimum and maximum shares and optional preemption. The scheduler is selected with yarn.resourcemanager.scheduler.class; applications choose a queue with mapreduce.job.queuename or placement rules.
The ResourceManager hands out containers; the scheduler decides who gets the next one. FIFO: whoever came first. Capacity: every team owns a slice and can borrow idle slices. Fair: everybody running gets an equal share.
Words you'll meet in this lesson
root.prod.Architecture at a glance
The queue tree of our lab, and where its containers land. The scheduler walks this tree every time a NodeManager reports free space.
From our lab (Hadoop 3.5.0, 3 × 2048 MB, SleepJob: big = 30 × 40 s, small = 2 × 5 s, 15 s later)
| FIFO | big 198.8 s · small waited 120.4 s for its AM, finished after 159.6 s |
|---|---|
| Capacity, defaults | prod 70 / dev 30, max 100: big 430.0 s · small 24.2 s, no wait. Alone, the big job held only 2,560 of 6,144 MB: user-limit-factor 1 |
| Capacity, ULF 4 | big 207.6 s · small waited 51.0 s, took 121.5 s (dev borrowed the whole cluster) |
| + preemption | big 215.4 s with 2 containers preempted · small waited 20.7 s, took 77.9 s |
| Fair | big 390.6 s · small waited 37.5 s, took 152.8 s. Fair rounded 384 MB maps up to 1,024 MB: 6 containers instead of 12 |
Schedulers at three levels
Beginner A canteen with one counter
- FIFO is one counter and one line. If the person in front orders 30 plates, you wait for all 30, even for one chai.
- Capacity is separate counters for staff and students. Each counter has its own cooks; if the staff counter is empty, its cooks may help the students, and come back when staff arrive.
- Fair is one cook who serves everyone in the line in turns, a plate each, so a short order is never stuck behind a long one for long.
Intermediate What really decides who gets a container
- Every NodeManager heartbeat (about once a second) tells the RM how much is free. The scheduler then picks a queue, then an app inside it, then a request, and hands out a container.
- Capacity picks the queue that is furthest below its guaranteed capacity first. A queue may grow up to
maximum-capacity, but one user may hold onlyuser-limit-factor × capacity. With the default factor 1, our single-user big job could never pass 30%, which is why it took 430 s. - Without preemption, the scheduler only reassigns containers when they finish. Our small job in prod waited 51 s for a 40-second dev task to end.
- Fair computes each queue's fair share from weights and gives the next container to whoever is furthest below theirs.
Advanced Production design
- Typical layout:
root.prod.etl,root.prod.reports,root.adhoc,root.ds, with guarantees that add up to 100% at each level and maximums that allow borrowing. - Preemption makes guarantees real. The cost is wasted work: killed containers rerun. Tune
max_wait_before_kill, total preemption per round and natural termination factor, and exempt queues withdisable_preemption. maximum-am-resource-percent(we set 0.5) limits how much of a queue AMs may take, so 50 waiting jobs can't fill a queue with ApplicationMasters and no tasks.- Change queues without restarting: edit
capacity-scheduler.xml, thenyarn rmadmin -refreshQueues. Node labels let a queue own specific machines (GPU nodes, for example). - Cloudera's platform standardised on the Capacity Scheduler; the Fair Scheduler is still in Apache Hadoop, but new clusters usually start with Capacity.
Exam tip: For “Compare FIFO, Capacity and Fair schedulers”, give one line each (order of arrival / guaranteed queue shares with elasticity / equal share over time), name the default (Capacity in Hadoop 3), and the two settings that make Capacity behave well: maximum-capacity for elasticity and preemption to win back guarantees.
The three schedulers compared
| FIFO | Capacity | Fair | |
|---|---|---|---|
| Idea | First submitted, first served | Each queue guaranteed a %, can borrow idle capacity | Everyone converges to an equal (weighted) share |
| Small job behind big job (lab) | Waited 120.4 s, done at 159.6 s | 24.2 s (ULF 1) · 121.5 s (ULF 4) · 77.9 s (+ preemption) | Waited 37.5 s, done at 152.8 s |
| Big job alone-ish (lab) | 198.8 s | 430.0 s (ULF 1) · 207.6 s (ULF 4) | 390.6 s (1 GB containers) |
| Queues | One | Hierarchy, % of parent | Hierarchy, weights, min/max |
| Preemption | No | Yes (scheduler monitor) | Yes (per queue) |
| Used where | Single-user test clusters | Default in Hadoop 3, Cloudera CDP, most shared clusters | Older CDH clusters, some multi-tenant setups |
The settings, in the files our lab used
yarn.resourcemanager.scheduler.class = ...scheduler.capacity.CapacityScheduler
yarn.scheduler.capacity.root.queues = prod,dev
yarn.scheduler.capacity.root.prod.capacity = 70
yarn.scheduler.capacity.root.dev.capacity = 30
yarn.scheduler.capacity.root.dev.maximum-capacity = 100
yarn.scheduler.capacity.root.dev.user-limit-factor = 4 # only in the "ULF 4" runs
yarn.resourcemanager.scheduler.monitor.enable = true # only in the "preemption" runs<allocations>
<queue name="prod"><weight>1.0</weight><schedulingPolicy>fair</schedulingPolicy></queue>
<queue name="dev"><weight>1.0</weight><schedulingPolicy>fair</schedulingPolicy></queue>
</allocations>Animated example: the 9 AM regulatory report at a bank
A bank must send a regulatory report by 9:00 every morning, from the same cluster where analysts run ad-hoc queries. Watch the same morning under three set-ups. (A story with round numbers, built on what our lab showed.)
- FIFO, 8:30: an analyst's huge query arrives first and takes every free container.
- FIFO, 8:40: the regulatory report waits behind it and misses 9:00.
- Capacity: the report queue owns 70%, so the report starts at once.
- Capacity, at night: with no elasticity the query can't use the idle 70%, so hardware sits idle.
- Elastic capacity: with maximum-capacity 100% the query borrows everything at night, but in the morning the report must wait for borrowed containers to finish.
- Preemption: the RM takes back 2 containers from the query; the report is on time and the query reruns a little work.
How an application lands in a queue
You ask
-D mapreduce.job.queuename=prod, spark-submit --queue prod, Hive SET tez.queue.name=prod.
Placement rules
Capacity: queue-mappings such as u:kabir:dev or g:analysts:adhoc. Fair: rules like specified, primaryGroup, default.
ACLs
acl_submit_applications and acl_administer_queue decide who may submit to and kill apps in a queue.
Leaf queues only
Apps go to leaf queues (root.prod.etl); parent queues only divide capacity.
Check where a job really ran: the RM REST API shows "queue":"root.prod" for each app, and mapred queue -list shows every queue's capacity and current use, as in our lab output.
Six runs, one pair of jobs
Each run recreates only the ResourceManager with another scheduler (rm.sh); HDFS and the NodeManagers keep running and re-register. Then sched_lab.sh starts the big job, waits 15 s and starts the small one. Both are Hadoop's built-in SleepJob, so the work is identical every time.
FIFO: the head of the line takes everything
The small job couldn't even start its ApplicationMaster for two minutes: FIFO gives every free container to the oldest app that still wants one.bash rm.sh fifo docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh default defaultscheduler: scheduler.fifo.FifoScheduler big queue=default AM waited 0.6 s, job took 198.8 s, SUCCEEDED, preempted containers: 0 small queue=default AM waited 120.4 s, job took 159.6 s, SUCCEEDED, preempted containers: 0Capacity with queues: the small job flies, the big one crawls
Prod had room the whole time. But the big job took more than twice as long as under FIFO, even though dev's maximum is 100%.bash rm.sh capacity docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh dev prodQueue Name : prod Capacity: 70.0, MaximumCapacity: 100.0, CurrentCapacity: 0.0 Queue Name : dev Capacity: 30.000002, MaximumCapacity: 100.0, CurrentCapacity: 0.0 big queue=root.dev AM waited 0.6 s, job took 430.0 s, SUCCEEDED, preempted containers: 0 small queue=root.prod AM waited 0.1 s, job took 24.2 s, SUCCEEDED, preempted containers: 0Why so slow? Measure the big job alone
Under Capacity the job never grew past about 30% (5 containers of 512 MB) with the rest of the cluster idle. The cause iscurl -s http://resourcemanager:8088/ws/v1/cluster/metrics # allocatedMB, containersAllocatedcapacity allocated 2560 of 6144 MB, containers 5 capacity allocated 2560 of 6144 MB, containers 5 fair allocated 6144 of 6144 MB, containers 6user-limit-factor, default 1: a single user may use at most 1 × the queue's capacity. Under Fair it took all 6,144 MB, but in only 6 containers: Fair rounds requests up to its 1,024 MB increment, so each 384 MB map got a 1 GB container.Capacity with user-limit-factor 4: elastic again
Dev borrowed the whole idle cluster, so the big job is back to FIFO speed. The price: when prod's job arrived, every container was busy, and the scheduler could only wait for dev's 40-second tasks to finish.bash rm.sh capacity-ulf # adds root.dev.user-limit-factor=4 docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh dev prodbig queue=root.dev AM waited 0.7 s, job took 207.6 s, SUCCEEDED, preempted containers: 0 small queue=root.prod AM waited 51.0 s, job took 121.5 s, SUCCEEDED, preempted containers: 0Add preemption: borrowed containers are taken back
The preemption monitor (ProportionalCapacityPreemptionPolicy) saw prod below its 70% guarantee, waited, then killed 2 of dev's containers. Prod's wait dropped from 51 s to 21 s; the big job paid 8 seconds for the redone work.bash rm.sh capacity-ulf-preempt # + yarn.resourcemanager.scheduler.monitor.enable=true docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh dev prodbig queue=root.dev AM waited 0.7 s, job took 215.4 s, SUCCEEDED, preempted containers: 2 small queue=root.prod AM waited 20.7 s, job took 77.9 s, SUCCEEDED, preempted containers: 0Fair Scheduler with two equal queues
Without preemption, Fair also waits for containers to finish, and with 1 GB containers the cluster ran only half as many tasks at once. Our lab's small maps simply don't suit Fair's default 1 GB increment (bash rm.sh fair docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh dev prodscheduler: scheduler.fair.FairScheduler big queue=root.dev AM waited 1.0 s, job took 390.6 s, SUCCEEDED, preempted containers: 0 small queue=root.prod AM waited 37.5 s, job took 152.8 s, SUCCEEDED, preempted containers: 0yarn.resource-types.memory-mb.increment-allocation).
A real trap we hit: the Docker image writes capacity-scheduler.xml from the environment, and when we set only one capacity key, the file contained only that key. The ResourceManager refused to start: Queue configuration missing child queue names for root. On any cluster, a partial capacity-scheduler.xml breaks the RM the same way; always keep root.queues and the capacities in the file.
The same 12 containers, three schedulers
Six steps through the real runs: what the terminal reported, and how the scheduler filled the cluster's 12 container slots.
- FIFO run: the big job starts, the small one follows 15 s later.
- The small job waits 120 s just to get its ApplicationMaster.
- Capacity with two queues: the small job is done in 24 s, but the big job takes 430 s.
- Measured alone, the big job never holds more than 2,560 MB: the user limit.
- With user-limit-factor 4 the big job is fast again, and the small one waits 51 s.
- With preemption, 2 containers are taken back and the small job's wait drops to 21 s.
- FIFO hands every free container to the oldest application that still has tasks waiting.
- The small job sits in the queue until the big job's last tasks have been placed.
- Capacity keeps 70% for prod, so the small job starts immediately while dev stays near its 30%.
- The cap comes from user-limit-factor 1: one user may hold only 1 × the queue's capacity, so slots stay idle.
- With user-limit-factor 4, dev borrows the idle cluster; prod must wait for a container to free up.
- Preemption returns 2 containers to prod quickly; dev loses and reruns the work they held.
Schedulers at work
1. Banking: regulatory reports with a deadline
A bank gives its overnight regulatory and risk jobs a queue with a large guarantee and preemption enabled, so analysts' ad-hoc work can borrow the cluster during the day but never delays the morning filing.
2. E-commerce: a sale day
During a big festive sale, an online retailer raises the live-dashboard and fraud queues' capacity for the week (a config change plus yarn rmadmin -refreshQueues) and caps the data-science queue's maximum, then reverts afterwards.
3. Telecom: billing windows
Call-detail-record billing runs in a fixed night window. Its queue has a high guarantee; the day-time analytics queues use a large maximum capacity so the hardware isn't idle while billing sleeps.
4. University research cluster
Departments that paid for the hardware get queues in proportion to their share, with user-limit-factor and per-user limits so one PhD student's thousand-task job can't take a whole department's capacity.
5. Mixed hardware with node labels
A healthcare-imaging team's queue is allowed onto nodes labelled gpu; other queues can't use them. Node labels plus queues let one YARN cluster serve very different workloads.
Simplified; organisations differ in tools and policies.
Simulate the four schedulers
A small model of our lab: 12 container slots, the big job (30 tasks × 40 s) at time 0 and the small job (2 tasks × 5 s) at 15 s. Each ApplicationMaster takes a slot and needs about 5 s before it asks for task containers. It is a model, so the seconds differ from the lab, but the pattern is the same: FIFO hurts the small job, capacity with user-limit-factor 1 hurts the big job, preemption gets the small job going fastest without holding the big job back for long.
import math
def simulate(policy, slots=12):
jobs = {"big": dict(arrive=0, left=30, secs=40, run=[], am=None, end=None, queue="dev"),
"small": dict(arrive=15, left=2, secs=5, run=[], am=None, end=None, queue="prod")}
if policy == "fair":
slots //= 2 # Fair rounds 384 MB maps up to 1 GB: half as many containers
preempted = 0
for t in range(1000):
for j in jobs.values(): # tasks that finished free their slot
j["run"] = [e for e in j["run"] if e > t]
if j["am"] is not None and j["left"] == 0 and not j["run"] and j["end"] is None:
j["end"] = t
active = [n for n, j in jobs.items() if j["arrive"] <= t and j["end"] is None]
used = lambda n: len(jobs[n]["run"]) + (jobs[n]["am"] is not None)
free = slots - sum(used(n) for n in jobs)
def limit(n):
if policy == "capacity-ulf1" and jobs[n]["queue"] == "dev":
return math.ceil(0.3 * slots) + 1 # one user <= 1 x 30% of the cluster
if policy == "fair" and len(active) == 2:
return slots // 2
return slots
def wants(n):
j = jobs[n]
return j["am"] is None or (j["left"] > 0 and t >= j["am"] + 5)
if policy == "preempt" and t >= 30 and wants("small") and free == 0 and jobs["big"]["run"]:
jobs["big"]["run"].pop(); jobs["big"]["left"] += 1; preempted += 1; free += 1
order = active # FIFO: order of arrival
if policy in ("capacity-ulf4", "preempt"):
order = sorted(active, key=lambda n: jobs[n]["queue"] != "prod") # prod is under its guarantee
if policy == "fair":
order = sorted(active, key=lambda n: used(n)) # furthest below fair share first
for n in order:
while free > 0 and wants(n) and used(n) < limit(n):
if jobs[n]["am"] is None:
jobs[n]["am"] = t
else:
jobs[n]["run"].append(t + jobs[n]["secs"]); jobs[n]["left"] -= 1
free -= 1
if policy == "fifo" and wants(n):
break # nobody passes the head of the line
if all(j["end"] is not None for j in jobs.values()):
break
s = jobs["small"]
return jobs["big"]["end"], s["am"] - s["arrive"], s["end"] - s["arrive"], preempted
for p in ["fifo", "capacity-ulf1", "capacity-ulf4", "preempt", "fair"]:
big, wait, small, pre = simulate(p)
print(f"{p:14} big {big:4d} s | small: AM waited {wait:3d} s, took {small:3d} s | preempted {pre}")return math.ceil(0.3 * slots) + 1 # one user <= 1 x 30% of the clusteruser-limit-factor 1: our big job alone held 2,560 of 6,144 MB (5 containers) in the lab.if policy == "preempt" and t >= 30 and wants("small") and free == 0 and jobs["big"]["run"]:Preemption: after a wait, take a container back from the over-using queue. In the lab 2 were preempted.break # nobody passes the head of the lineFIFO in one line: if the first app still wants containers, nobody behind it gets any.
Run it on your PC: switch schedulers and race two jobs
Use the Module 8 lab cluster. Put rm.sh, the sched-*.env files and fair-scheduler.xml in the mr-lab folder next to docker-compose.yaml. rm.sh recreates only the ResourceManager with another scheduler; HDFS and the NodeManagers keep running and re-register within a few seconds. Then copy sched_lab.sh into the namenode container and race the jobs. Each run takes 3–8 minutes.
Switch the scheduler
# 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 : //"'
# The files rm.sh combines with ./config:
# ---- sched-fifo.env ----
# YARN-SITE.XML_yarn.resourcemanager.scheduler.class=org.apache.hadoop.yarn.server.resourcemanager.scheduler.fifo.FifoScheduler
# ---- sched-capacity.env ----
# YARN-SITE.XML_yarn.resourcemanager.scheduler.class=org.apache.hadoop.yarn.server.resourcemanager.scheduler.capacity.CapacityScheduler
# CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.root.queues=prod,dev
# CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.root.prod.capacity=70
# CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.root.dev.capacity=30
# CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.root.dev.maximum-capacity=100
# CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.root.prod.maximum-capacity=100
# CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.maximum-am-resource-percent=0.5
# ---- sched-capacity-ulf.env ----
# CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.root.dev.user-limit-factor=4
# CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.root.prod.user-limit-factor=4
# ---- sched-capacity-preempt.env ----
# YARN-SITE.XML_yarn.resourcemanager.scheduler.monitor.enable=true
# CAPACITY-SCHEDULER.XML_yarn.scheduler.capacity.root.dev.disable_preemption=false
# ---- sched-fair.env ----
# YARN-SITE.XML_yarn.resourcemanager.scheduler.class=org.apache.hadoop.yarn.server.resourcemanager.scheduler.fair.FairScheduler
# YARN-SITE.XML_yarn.scheduler.fair.allocation.file=/opt/fair-scheduler.xml
# YARN-SITE.XML_yarn.scheduler.fair.user-as-default-queue=false
# ---- fair-scheduler.xml ----
# <?xml version="1.0"?>
# <!-- fair-scheduler.xml: two queues with equal weight; inside a queue, apps share fairly -->
# <allocations>
# <queue name="prod"><weight>1.0</weight><schedulingPolicy>fair</schedulingPolicy></queue>
# <queue name="dev"><weight>1.0</weight><schedulingPolicy>fair</schedulingPolicy></queue>
# <queueMaxAMShareDefault>0.5</queueMaxAMShareDefault>
# </allocations>
# Run, in the mr-lab folder:
# bash rm.sh capacityReal output. If you set capacity keys yourself, always keep root.queues and every capacity: a capacity-scheduler.xml with only some keys stops the ResourceManager with “Queue configuration missing child queue names for root” (we hit this in Lesson 11.4).
Race a big and a small job, under every scheduler
# sched_lab.sh BIG_QUEUE SMALL_QUEUE : a big job fills the cluster, a small job arrives 15 s later. Who waits?
# Uses Hadoop's built-in "sleep" test job: tasks that just sleep, so timings are easy to read.
# Run inside the namenode container.
T=$(ls /opt/hadoop/share/hadoop/mapreduce/hadoop-mapreduce-client-jobclient-*-tests.jar)
export HADOOP_CLASSPATH=$T
SLEEP="yarn org.apache.hadoop.mapreduce.SleepJob -libjars $T"
BQ=${1:-default}; SQ=${2:-default}
# big: 30 map tasks of 40 s each (the cluster runs about 11 at a time)
$SLEEP -D mapreduce.job.queuename=$BQ -D mapreduce.job.name=big -m 30 -r 1 -mt 40000 -rt 1000 > /tmp/s_big.log 2>&1 &
sleep 15
# small: 2 map tasks of 5 s each
$SLEEP -D mapreduce.job.queuename=$SQ -D mapreduce.job.name=small -m 2 -r 1 -mt 5000 -rt 1000 > /tmp/s_small.log 2>&1 &
wait
for j in big small; do
app=$(grep -o "application_[0-9_]*" /tmp/s_$j.log | head -1)
curl -s http://resourcemanager:8088/ws/v1/cluster/apps/$app | python3 -c '
import sys, json; a = json.load(sys.stdin)["app"]
print("%-5s queue=%-12s AM waited %5.1f s, job took %6.1f s, %s, preempted containers: %s" % (sys.argv[1], a["queue"],
(a["launchTime"] - a["startedTime"]) / 1000, a["elapsedTime"] / 1000, a["finalStatus"], a.get("numNonAMContainerPreempted", 0)))' $j
done
grep -m1 -i "preempted" /tmp/s_big.log | sed -E 's/^[0-9-]+ [0-9:]+ INFO //' | cut -c1-160
# In the mr-lab folder:
# docker cp sched_lab.sh lab8-namenode-1:/tmp/mr/
# bash rm.sh fifo && docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh default default
# bash rm.sh capacity && docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh dev prod
# … capacity-preempt, fair, capacity-ulf, capacity-ulf-preempt the same way
# The "probe" lines: start only the big job in dev and read
# curl -s http://resourcemanager:8088/ws/v1/cluster/metrics every 15 sReal output of all six runs plus the probe. Times will differ on your machine; the pattern (who waits, who is capped) will not. “capacity-preempt” with user-limit-factor 1 preempted nothing: dev never borrowed beyond its share, so there was nothing to take back.
How the Capacity Scheduler limits one user
The limit our big job hit, computed step by step: a queue's guaranteed share, its maximum, and the user limit that applies when one user is alone in the queue.
Worked examples: real problems, solved and traced in memory
Four scheduler design problems from different industries: sizing queues, guarantee vs elasticity, the user limit, and the cost of preemption.
Size the queues for a telecom cluster
Problem statement
A telecom company has 60 nodes with 96 GB for YARN each. Billing must be guaranteed 50%, fraud detection 20%, analytics 30%. How much memory does each queue get, and how many 4 GB containers is that?
60 × 96 GB · 50 / 20 / 30 · 4 GB containers
billing 2880 GB -> 720 containers of 4 GB fraud 1152 GB -> 288 containers of 4 GB analytics 1728 GB -> 432 containers of 4 GB
Approach
- Cluster memory = nodes × memory per node.
- Guarantee = cluster × capacity %.
- Containers = guarantee ÷ container size.
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.
Guarantee or elasticity?
Problem statement
An online retailer's dashboard queue is guaranteed 40% of 1,000 containers but is busy only 4 hours a day. The data-science queue (30%) has maximum-capacity 30%. How many container-hours does data science lose per day by not being elastic up to 70% during the 20 idle hours?
1,000 containers · idle 40% for 20 h
8,000 container-hours a day left idle
Approach
- Idle capacity = 40% × 1,000 for 20 hours.
- Data science could use it only if maximum-capacity allows.
- Loss = idle containers × hours.
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.
One PhD student, one department queue
Problem statement
The CSE department queue is guaranteed 25% of a 400-container cluster with maximum 60%. With user-limit-factor 1, 2 and 3, how many containers can one student take when the queue is otherwise empty?
400 containers · capacity 25% · max 60%
user-limit-factor 1: up to 100 containers user-limit-factor 2: up to 200 containers user-limit-factor 3: up to 240 containers
Approach
- Guarantee = 100 containers.
- User limit = factor × guarantee.
- Never more than maximum-capacity (240).
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.
What does preemption cost?
Problem statement
A bank's risk queue preempts 50 containers from the adhoc queue each morning. On average a preempted task had run 6 minutes of a 10-minute task, and its work is lost. How many container-minutes are redone per day, and per year (250 working days)? Compare with our lab: 2 containers preempted made the big job 7.8 s slower (207.6 → 215.4 s).
50 containers × 6 min lost · 250 days
300 container-minutes a day, 1,250 container-hours a year lab: big job 7.8 s slower, small job 43.6 s faster
Approach
- Lost work per container = time already run.
- Per day = containers × lost minutes.
- Per year = per day × working days.
Solution: run it live
What happens in memory, step by step
Recorded from a real Python run: press Next to execute one line at a time. Watch variables change in each frame, and lists and dictionaries (numbered #1, #2 …) fill up as objects.
Check yourself
Quick revision
Lesson 11.3 in one screen
- The scheduler in the ResourceManager decides who gets each free container: FIFO, Capacity (default in Hadoop 3) or Fair.
- Capacity: queue tree;
capacity= guarantee,maximum-capacity= elastic ceiling,user-limit-factor= how many × capacity one user may hold (default 1). - Lab: FIFO small job waited 120.4 s (159.6 s total); Capacity small 24.2 s but big 430.0 s (held to 2,560 MB by ULF 1); ULF 4: big 207.6 s, small 121.5 s; + preemption: small 77.9 s, 2 containers preempted.
- Without preemption, guarantees are honoured only as containers finish; preemption kills over-share containers and their work is redone.
- Fair: weighted fair share, min/max resources, DRF policy; it rounds to 1 GB by default (6 containers in our lab).
- Choose a queue:
-D mapreduce.job.queuename=prod, placement rules, ACLs; apply changes withyarn rmadmin -refreshQueues. - A partial
capacity-scheduler.xmlstops the RM: “Queue configuration missing child queue names for root”.
Interview and viva questions
What is the difference between the Capacity and Fair schedulers in YARN?
The Capacity Scheduler gives each queue a guaranteed percentage of the cluster, lets queues borrow idle resources up to a maximum, and limits how much a single user may take; it is the default in Hadoop 3 and the standard in Cloudera's platform. The Fair Scheduler shares resources so that all running applications or weighted queues get an equal share over time, with minimum and maximum shares. Both support hierarchical queues and preemption; they differ mainly in how they decide who is most entitled to the next container.
What is user-limit-factor in the Capacity Scheduler?
It is the multiple of a queue's configured capacity that a single user may use. The default of 1 means one user can never use more than the queue's guarantee, even if maximum-capacity is higher and the cluster is idle. In our lab a lone job in a 30% queue held only 2,560 of 6,144 MB and took 430 s; with user-limit-factor 4 it used the whole cluster and took 207.6 s.
What is preemption in YARN?
Preemption lets the ResourceManager take containers back from queues that use more than their guaranteed share when another queue is starved. The scheduling monitor (ProportionalCapacityPreemptionPolicy for the Capacity Scheduler) first asks the application to release containers and kills them after a timeout. The killed tasks are rerun, so preemption trades some wasted work for meeting guarantees.
How do I submit a job to a specific YARN queue?
For MapReduce use -D mapreduce.job.queuename=prod (or set it in the job configuration), for Spark use --queue prod, and for Hive on Tez set tez.queue.name. Administrators can also map users and groups to queues with placement rules, and queue ACLs control who may submit.
Why is my small job stuck in ACCEPTED?
ACCEPTED means the ResourceManager has the application but its ApplicationMaster has no container yet. Usually the queue is full (or at its maximum-am-resource-percent), the user has reached the user limit, or, under FIFO, an earlier application is still being served. Check the queue's use with mapred queue -list or the RM web UI's Scheduler page; in our lab a small job waited 120.4 s in ACCEPTED under FIFO.
Practice problems
Scheduler exercises built on the six real runs.
How much slower?
The small job needs about 10 s of work. How many times longer did it take under FIFO (159.6 s) and with Capacity queues (24.2 s)?
159.6 s · 24.2 s · 10 s
FIFO: 16.0x the work time Capacity: 2.4x the work time
Show solution and explanation
for name, t in [("FIFO", 159.6), ("Capacity", 24.2)]:
print(f"{name}: {t / 10:.1f}x the work time")How it works: About 16× under FIFO and 2.4× with queues (the rest is AM start-up and container launches).
Guaranteed share
Our cluster has 6,144 MB. What are prod's and dev's guaranteed shares in MB?
70% · 30%
prod 4300.799999999999 MB, dev 1843.1999999999998 MB
Show solution and explanation
print("prod", 6144 * 0.70, "MB, dev", 6144 * 0.30, "MB")How it works: 4,300.8 and 1,843.2 MB. The lone big job held 2,560 MB: its user limit plus one container of rounding.
Cost of the cap
How much longer did the big job take with user-limit-factor 1 (430.0 s) than with 4 (207.6 s)?
430.0 · 207.6
222.4 s longer, 2.07x
Show solution and explanation
print(f"{430.0 - 207.6:.1f} s longer, {430.0 / 207.6:.2f}x")How it works: 222.4 s longer: twice as slow, with most of the cluster idle.
Preemption trade
Preemption saved the small job some seconds and cost the big job some. Compute both from the ULF 4 runs.
small 121.5 → 77.9 · big 207.6 → 215.4
small saved 43.6 s, big lost 7.8 s
Show solution and explanation
print(f"small saved {121.5 - 77.9:.1f} s, big lost {215.4 - 207.6:.1f} s")How it works: 43.6 s saved for 7.8 s lost: preemption pays off when the waiting job matters more.
Containers per queue
A node manager offers 48 GB. With 20 nodes and 3 GB containers, how many containers does a 35% queue get?
20 × 48 GB · 3 GB · 35%
112 of 320 containers
Show solution and explanation
total = 20 * 48 // 3
print(int(total * 0.35), "of", total, "containers")How it works: 112 of 320 containers guaranteed.
Fair with weights
Queues prod (weight 3) and adhoc (weight 1) are both busy on 200 containers. What are their fair shares? What if adhoc is idle?
weights 3 : 1 · 200
{'prod': 150, 'adhoc': 50}
{'prod': 200}Show solution and explanation
for active in [{"prod": 3, "adhoc": 1}, {"prod": 3}]:
t = sum(active.values())
print({q: 200 * w // t for q, w in active.items()})How it works: 150 and 50; alone, prod may use all 200.
Buy nodes or add preemption?
Reports miss their SLA 3 mornings a month because adhoc jobs hold the cluster. Option A: 10 more nodes at ₹6 lakh each. Option B: enable preemption, costing about 2% of adhoc compute (adhoc uses 40% of a 50-node cluster). Which is cheaper per year if a node-year costs ₹1.5 lakh to run?
A: 10 × ₹6 L + 10 × ₹1.5 L/yr · B: 2% × 40% × 50 nodes
A: Rs 75 lakh in year 1, B: Rs 0.6 lakh a year of redone work
Show solution and explanation
a = 10 * 6 + 10 * 1.5
b = 0.02 * 0.40 * 50 * 1.5
print(f"A: Rs {a:.0f} lakh in year 1, B: Rs {b:.1f} lakh a year of redone work")How it works: About ₹75 lakh vs about ₹0.6 lakh (illustrative). Configure before you buy.
Design the queues
Write a capacity layout for root → prod (etl, reports), adhoc, ds, and check that each level adds up to 100%.
a nested dict of capacities
root level: 100
prod children: 100 {'etl': '42% of cluster', 'reports': '18% of cluster'}Show solution and explanation
tree = {"prod": (60, {"etl": 70, "reports": 30}), "adhoc": (25, {}), "ds": (15, {})}
print("root level:", sum(c for c, _ in tree.values()))
for q, (c, kids) in tree.items():
if kids:
print(f"{q} children:", sum(kids.values()), {k: f"{c * v / 100:.0f}% of cluster" for k, v in kids.items()})How it works: Both levels add up to 100; etl gets 42% and reports 18% of the whole cluster. Child capacities are percentages of the parent.
Real-world scenario questions (asked in company interviews)
Scheduler questions come up for every Hadoop admin and data-engineering role, usually as “a job is stuck” or “design the queues”.
“Explain FIFO, Capacity and Fair schedulers”
A standard question in Hadoop interviews.
The interviewer is checking: one clear idea per scheduler and the default.
Show a strong answer
- FIFO serves apps in arrival order; a big job blocks small ones (our small job waited 120 s).
- Capacity (default in Hadoop 3): queues with guaranteed %, elastic up to maximum-capacity, user limits, preemption.
- Fair: everyone running converges to an equal or weighted share; also queues and preemption.
A job uses only a third of an idle cluster
The queue's maximum-capacity is 100% and nothing else is running, yet the job won't grow.
The interviewer is checking: user limits.
Show a strong answer
- Check user-limit-factor: at the default of 1, one user can't exceed the queue's capacity.
- Also check maximum-am-resource-percent and minimum-user-limit-percent.
- Raise the factor for that queue and run yarn rmadmin -refreshQueues; in our lab that cut the job from 430 s to 208 s.
Reports must finish by 7 AM, but analysts want the whole cluster
You run a shared cluster for a bank.
The interviewer is checking: guarantees, elasticity and preemption together.
Show a strong answer
- Reports queue with enough guaranteed capacity for the SLA (measured from past runs), analysts' queue elastic up to 100%.
- Enable preemption so borrowed capacity comes back in seconds; exempt the reports queue from being preempted.
- Limit AM share and user limits in the analysts' queue; monitor queue wait times and SLA misses.
Moving from the Fair Scheduler to the Capacity Scheduler
Your company is moving from an old CDH cluster to CDP.
The interviewer is checking: what changes in behaviour.
Show a strong answer
- Translate weights and min/max shares into capacities and maximum-capacities (tools exist, e.g. fs2cs), then review user limits, which behave differently.
- Check container sizing: Fair's increment (1 GB by default) vs Capacity's minimum allocation changes how many containers fit.
- Replay a typical day's jobs on a test cluster and compare queue waits before switching.