Schedulers: FIFO, Capacity and Fair

When many teams share one cluster, the YARN scheduler decides whose task gets the next free container. This lesson runs the same pair of jobs under FIFO, three Capacity Scheduler set-ups and the Fair Scheduler on a real cluster, and turns the measured waits into rules for designing queues.

Module 11 · YARN⏱ 30 minIntermediate6 real scheduler runsQueue architecture diagramRuns in your browser (Python)8 practice problems
1 The story

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.

FIFO: one line for everyone big job (Kabir) · 30 tasks × 40 s · 198.8 s small job waits for its ApplicationMaster: 120.4 s runs · done at 159.6 s Capacity Scheduler: two queues, prod 70% · dev 30% big job in dev · held to about 30% · 430.0 s 24.2 s ✓ small job in prod: no wait at all Real runs on our 3-worker lab. Same jobs, same cluster: only the scheduler changed. myphatshala.in
Riya

Sir, my job was tiny. Why did YARN make it wait behind Kabir's?

Arjun Sir

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.

Kabir

And when we gave our team a separate queue, my experiment became slower: 430 seconds instead of 199.

Arjun Sir

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.

2 Definition

The YARN scheduler

Formal definition (write this in exams)

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.

In simple words

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

QueueA named share of the cluster that applications are submitted to, e.g. root.prod.
CapacityThe guaranteed share of a queue, in percent of its parent: prod 70.
Maximum capacityHow far a queue may grow when others are idle (elasticity).
User limit factorHow many times its capacity one user may use. Default 1.
PreemptionKilling containers of an over-using queue to give an under-served one its guarantee.
Fair shareWhat a queue or app would get if resources were split equally (by weight).
DRFDominant Resource Fairness: fairness counted on memory AND CPU.
Placement ruleA rule that picks the queue for an app (by user, group, or what was asked).

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.

ResourceManager CapacityScheduler root 100% root.prod capacity 70% · max 100% placement report, billing ETL root.dev capacity 30% · max 100% user-limit-factor 1 → one user ≤ 30% worker1 · 2048 MB worker2 · 2048 MB worker3 · 2048 MB 4 × 512 MB containers per node, 12 in all · green = prod · orange = dev · empty = free myphatshala.in

From our lab (Hadoop 3.5.0, 3 × 2048 MB, SleepJob: big = 30 × 40 s, small = 2 × 5 s, 15 s later)

FIFObig 198.8 s · small waited 120.4 s for its AM, finished after 159.6 s
Capacity, defaultsprod 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 4big 207.6 s · small waited 51.0 s, took 121.5 s (dev borrowed the whole cluster)
+ preemptionbig 215.4 s with 2 containers preempted · small waited 20.7 s, took 77.9 s
Fairbig 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

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

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 only user-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 with disable_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, then yarn 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.

3 Types and ways to use it

The three schedulers compared

FIFOCapacityFair
IdeaFirst submitted, first servedEach queue guaranteed a %, can borrow idle capacityEveryone converges to an equal (weighted) share
Small job behind big job (lab)Waited 120.4 s, done at 159.6 s24.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 s430.0 s (ULF 1) · 207.6 s (ULF 4)390.6 s (1 GB containers)
QueuesOneHierarchy, % of parentHierarchy, weights, min/max
PreemptionNoYes (scheduler monitor)Yes (per queue)
Used whereSingle-user test clustersDefault in Hadoop 3, Cloudera CDP, most shared clustersOlder 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.)

Report vs ad-hoc queries 6 steps
Animated example 12 containers in the cluster 8:30 · an analyst starts a huge ad-hoc query.FIFO: it takes every container (orange). 8:40 · report submitted · waits in line…FIFO: the report starts only when thequery has no more tasks waiting. 9:00 missed. Capacity: reports 70% (green) · adhoc 30%.The green part stays reserved for reports.8:40 · report starts at once. 9:00 met ✓ But at 3 AM no report runs, and thequery still uses only 4 of 12 containers.Idle hardware: guarantees without elasticity. + maximum-capacity 100% and user limit:at night adhoc borrows everything.8:40 · report arrives: no free container… + preemption: after a short wait, the RMkills 2 adhoc containers for the report.Report on time ✓ · query loses a little work Lab: small job waited 120.4 s under FIFO(job took 159.6 s for 10 s of work) Lab: small 24.2 s, but big job 430.0 s(held to 2,560 of 6,144 MB by user-limit-factor 1) Lab (ULF 4): big 207.6 s, but smallwaited 51.0 s for containers to finish Lab (ULF 4 + preemption): small 77.9 s,big 215.4 s with 2 containers preempted myphatshala.in

How an application lands in a queue

You ask

-D mapreduce.job.queuename=prod, spark-submit --queue prod, Hive SET tez.queue.name=prod.

per job

Placement rules

Capacity: queue-mappings such as u:kabir:dev or g:analysts:adhoc. Fair: rules like specified, primaryGroup, default.

admin

ACLs

acl_submit_applications and acl_administer_queue decide who may submit to and kill apps in a queue.

security

Leaf queues only

Apps go to leaf queues (root.prod.etl); parent queues only divide capacity.

rule

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.

4 Step by step

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.

  1. FIFO: the head of the line takes everything

    bash rm.sh fifo
    docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh default default
    scheduler: 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: 0
    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.
  2. Capacity with queues: the small job flies, the big one crawls

    bash rm.sh capacity
    docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh dev prod
    Queue 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: 0
    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%.
  3. Why so slow? Measure the big job alone

    curl -s http://resourcemanager:8088/ws/v1/cluster/metrics   # allocatedMB, containersAllocated
    capacity allocated 2560 of 6144 MB, containers 5 capacity allocated 2560 of 6144 MB, containers 5 fair allocated 6144 of 6144 MB, containers 6
    Under Capacity the job never grew past about 30% (5 containers of 512 MB) with the rest of the cluster idle. The cause is user-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.
  4. Capacity with user-limit-factor 4: elastic again

    bash rm.sh capacity-ulf      # adds root.dev.user-limit-factor=4
    docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh dev prod
    big 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: 0
    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.
  5. Add preemption: borrowed containers are taken back

    bash rm.sh capacity-ulf-preempt   # + yarn.resourcemanager.scheduler.monitor.enable=true
    docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh dev prod
    big 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: 0
    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.
  6. Fair Scheduler with two equal queues

    bash rm.sh fair
    docker exec lab8-namenode-1 bash /tmp/mr/sched_lab.sh dev prod
    scheduler: 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: 0
    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 (yarn.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.

5 See it happen

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.

Who gets the next container? 6 steps
What you see hadoop@namenode : /tmp/mr $ bash rm.sh fifo ; sched_lab.shbig submitted at 0 ssmall submitted at 15 s big AM waited 0.6 s, took 198.8 ssmall AM waited 120.4 s, took 159.6 s $ bash rm.sh capacity (dev / prod)big took 430.0 ssmall AM waited 0.1 s, took 24.2 s $ curl …/cluster/metrics (big alone)allocated 2560 of 6144 MB, containers 5 $ bash rm.sh capacity-ulfbig took 207.6 ssmall AM waited 51.0 s, took 121.5 s $ bash rm.sh capacity-ulf-preemptbig took 215.4 s, preempted: 2small AM waited 20.7 s, took 77.9 s myphatshala.in
Behind the screen ResourceManager · scheduler decides each container 12 slots of 512 MB (3 workers × 4) FIFO: the big job asked first, so everyfree slot goes to it while it has tasks waiting small: queuedwaits until the big job's lasttasks have all been placed dev (orange) stops at ~30%; prod (blue)starts at once; 4 slots stay idle user limit = user-limit-factor 1 × 30%5 of 12 slots used, 7 idle, job waits ULF 4: dev borrows all 12 slotsprod waits for a 40 s task to end preemption monitor kills 2 dev containers;prod gets them, dev reruns those tasks small job total big job total 159.6 s (FIFO)198.8 s 24.2 s (Capacity)430.0 s 121.5 s (ULF 4)207.6 s 77.9 s (+ preemption)215.4 s orange = big job · blue = small job · white = idle myphatshala.in
6 Real-world use

Schedulers at work

reg-reportguaranteed 40%

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.

reports queue 40%→preemption on→Filed by 9 AM
saleds

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.

Sale week→refreshQueues→Dashboards first
CDR billing2 AM–6 AM

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.

Night: billing→elastic queues→Day: analytics
dept: cse 40%dept: ece 30%

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.

Funding share→queue per dept→Per-user limits
GPU nodeslabel: gpu

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.

Label nodes→queue → label→GPU jobs isolated

Simplified; organisations differ in tools and policies.

7 Run the code

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}")
fifo big 125 s | small: AM waited 70 s, took 80 s | preempted 0 capacity-ulf1 big 325 s | small: AM waited 0 s, took 10 s | preempted 0 capacity-ulf4 big 130 s | small: AM waited 30 s, took 75 s | preempted 0 preempt big 161 s | small: AM waited 15 s, took 26 s | preempted 3 fair big 325 s | small: AM waited 30 s, took 40 s | preempted 0
  • 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 capacity
scheduler: scheduler.capacity.CapacityScheduler Queue Name : prod Capacity: 70.0, MaximumCapacity: 100.0, CurrentCapacity: 0.0 Queue Name : dev Capacity: 30.000002, MaximumCapacity: 100.0, CurrentCapacity: 0.0

Real 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 s
########## fifo scheduler: 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: 0 ########## capacity scheduler: scheduler.capacity.CapacityScheduler Queue 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: 0 ########## capacity-preempt scheduler: scheduler.capacity.CapacityScheduler Queue 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.7 s, job took 428.0 s, SUCCEEDED, preempted containers: 0 small queue=root.prod AM waited 0.1 s, job took 25.4 s, SUCCEEDED, preempted containers: 0 ########## fair scheduler: scheduler.fair.FairScheduler Queue Name : root.dev Capacity: 0.0, MaximumCapacity: UNDEFINED, CurrentCapacity: 0.0 Queue Name : root.prod Capacity: 0.0, MaximumCapacity: UNDEFINED, CurrentCapacity: 0.0 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: 0 ########## probe: big job ALONE in queue dev for 50 s (allocated memory sampled every 15 s) capacity allocated 2560 of 6144 MB, containers 5 capacity allocated 2560 of 6144 MB, containers 5 capacity allocated 2560 of 6144 MB, containers 5 fair allocated 6144 of 6144 MB, containers 6 fair allocated 6144 of 6144 MB, containers 6 fair allocated 6144 of 6144 MB, containers 6 ########## capacity-ulf scheduler: scheduler.capacity.CapacityScheduler Queue 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.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: 0 ########## capacity-ulf-preempt scheduler: scheduler.capacity.CapacityScheduler Queue 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.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: 0

Real 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.

8 Line by line

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.

Example 1BeginnerTelecom · queue sizing

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?

Input
60 × 96 GB · 50 / 20 / 30 · 4 GB containers
Expected output
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

  1. Cluster memory = nodes × memory per node.
  2. Guarantee = cluster × capacity %.
  3. 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.

Example 2IntermediateE-commerce · elasticity

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?

Input
1,000 containers · idle 40% for 20 h
Expected output
8,000 container-hours a day left idle

Approach

  1. Idle capacity = 40% × 1,000 for 20 hours.
  2. Data science could use it only if maximum-capacity allows.
  3. 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.

Example 3IntermediateUniversity · user limit

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?

Input
400 containers · capacity 25% · max 60%
Expected output
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

  1. Guarantee = 100 containers.
  2. User limit = factor × guarantee.
  3. 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.

Example 4AdvancedBanking · preemption cost

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).

Input
50 containers × 6 min lost · 250 days
Expected output
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

  1. Lost work per container = time already run.
  2. Per day = containers × lost minutes.
  3. 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.

9 Quiz and revision

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 with yarn rmadmin -refreshQueues.
  • A partial capacity-scheduler.xml stops 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.

P1Easy

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)?

Input
159.6 s · 24.2 s · 10 s
Expected output
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")
FIFO: 16.0x the work time Capacity: 2.4x the work time

How it works: About 16× under FIFO and 2.4× with queues (the rest is AM start-up and container launches).

P2Easy

Guaranteed share

Our cluster has 6,144 MB. What are prod's and dev's guaranteed shares in MB?

Input
70% · 30%
Expected output
prod 4300.799999999999 MB, dev 1843.1999999999998 MB
Show solution and explanation
print("prod", 6144 * 0.70, "MB, dev", 6144 * 0.30, "MB")
prod 4300.799999999999 MB, dev 1843.1999999999998 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.

P3Easy

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)?

Input
430.0 · 207.6
Expected output
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")
222.4 s longer, 2.07x

How it works: 222.4 s longer: twice as slow, with most of the cluster idle.

P4Medium

Preemption trade

Preemption saved the small job some seconds and cost the big job some. Compute both from the ULF 4 runs.

Input
small 121.5 → 77.9 · big 207.6 → 215.4
Expected output
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")
small saved 43.6 s, big lost 7.8 s

How it works: 43.6 s saved for 7.8 s lost: preemption pays off when the waiting job matters more.

P5Medium

Containers per queue

A node manager offers 48 GB. With 20 nodes and 3 GB containers, how many containers does a 35% queue get?

Input
20 × 48 GB · 3 GB · 35%
Expected output
112 of 320 containers
Show solution and explanation
total = 20 * 48 // 3
print(int(total * 0.35), "of", total, "containers")
112 of 320 containers

How it works: 112 of 320 containers guaranteed.

P6Medium

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?

Input
weights 3 : 1 · 200
Expected output
{'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()})
{'prod': 150, 'adhoc': 50} {'prod': 200}

How it works: 150 and 50; alone, prod may use all 200.

P7Business problem

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?

Input
A: 10 × ₹6 L + 10 × ₹1.5 L/yr · B: 2% × 40% × 50 nodes
Expected output
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")
A: Rs 75 lakh in year 1, B: Rs 0.6 lakh a year of redone work

How it works: About ₹75 lakh vs about ₹0.6 lakh (illustrative). Configure before you buy.

P8Advanced

Design the queues

Write a capacity layout for root → prod (etl, reports), adhoc, ds, and check that each level adds up to 100%.

Input
a nested dict of capacities
Expected output
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()})
root level: 100 prod children: 100 {'etl': '42% of cluster', 'reports': '18% of cluster'}

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”.

YARN · basicsFresher

“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.
Operations · debuggingExperienced

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.
Design · SLAsExperienced

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.
Migration · schedulersExperienced

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.