The life of a YARN application

Every job on a Hadoop cluster, MapReduce or Spark, goes through the same lifecycle in YARN: it is submitted, gets an ApplicationMaster, asks for containers, runs and reports back. This lesson follows one real application through every state the ResourceManager and NodeManager log, then kills one and breaks one, so you can read any stuck or failed job.

Module 11 · YARN⏱ 28 minIntermediateEvery state from the real RM logArchitecture diagram + 2 animationsRuns in your browser (Python)8 practice problems
1 The story

Thirty-six seconds, eighteen state changes

Riya submits the item-revenue job again, but this time Kabir has a script that asks the ResourceManager every 0.2 seconds how the job is doing, and a second window that follows the ResourceManager's own log. The job takes 36 seconds. In that time the application changes state 8 times, its first attempt changes state 10 times, and the container that runs the ApplicationMaster goes through 5 states on worker2. Nobody typed anything after pressing Enter.

submit0 s ACCEPTED+0.1 s · AM 512 MB RUNNING+5.0 s · AM registered map task container (+6.0 s → +27 s) reduce (+28 s) FINISHED+35.6 s · SUCCEEDED Aggregate Resource Allocation: 33,224 MB-seconds, 64 vcore-seconds the bill YARN keeps for every application Real timings from our lab, read from the ResourceManager REST API every 0.2 s. myphatshala.in
Riya

Sir, the job had already been accepted 0.1 seconds after I pressed Enter. Then why did it sit for five seconds before running?

Arjun Sir

Because “accepted” only means the ResourceManager found room for one container: the one that will run your job's manager, the ApplicationMaster. The NodeManager on worker2 then had to fetch your jar, start a JVM and let the ApplicationMaster register itself. That took the five seconds. Only a registered ApplicationMaster can ask for more containers.

Kabir

And the ResourceManager writes all of it into its log. Every line says “State change from X to Y on event = Z”. You can follow your job like a parcel.

Arjun Sir

Today we follow one application from the first RPC to the last log file: the client, the ResourceManager, the ApplicationMaster, the containers, and what happens when we kill it or ask for something impossible.

2 Definition

The YARN application lifecycle

Formal definition (write this in exams)

A YARN application is a unit of work submitted to the ResourceManager, such as a MapReduce job or a Spark program. Its lifecycle begins when a client obtains a new application ID and submits an ApplicationSubmissionContext containing the queue, the resources of the ApplicationMaster and a ContainerLaunchContext (command, environment and local resources such as the job jar staged in HDFS). The ResourceManager saves the application to its state store (NEW → NEW_SAVING → SUBMITTED), the scheduler admits it to a queue (ACCEPTED) and allocates a container for the ApplicationMaster; a NodeManager localizes the resources and launches it. After the ApplicationMaster registers, the application is RUNNING: the ApplicationMaster requests further containers through periodic allocate heartbeats, launches its tasks on NodeManagers, monitors them, and finally unregisters with a final status. Each run of an ApplicationMaster is an application attempt; if one fails, the ResourceManager can start a new attempt, up to yarn.resourcemanager.am.max-attempts (2 by default). The application ends in FINISHED, FAILED or KILLED, and the ResourceManager records its aggregate resource usage in MB-seconds and vcore-seconds.

In simple words

Your job first gets a manager (the ApplicationMaster) in one container. The manager introduces itself to YARN, asks for workers (more containers), runs the work, reports the result and leaves. YARN writes down every step and how much memory was used for how long.

Words you'll meet in this lesson

ApplicationOne submission to YARN, e.g. application_1792643020992_0136.
Application attemptOne run of its ApplicationMaster: appattempt_…_0136_000001.
ApplicationMaster (AM)The per-application manager, running in container …_01_000001.
ContainerLaunchContextWhat a NodeManager needs to start a container: command, environment, files.
LocalizationThe NodeManager downloading the jar and config from HDFS before launch.
Allocate heartbeatThe AM's regular call to the RM: “here are my requests, what did I get?”
Final statusSUCCEEDED, FAILED or KILLED: what the application itself reported.
MB-secondsMemory × time held by all containers: YARN's usage meter.

From our lab (Hadoop 3.5.0, ItemRevenue on 140 MB)

Polling the RM+0.1 s ACCEPTED (1 container, 512 MB) · +5.0 s RUNNING · +6.0 s 2 containers (1,024 MB) · +35.6 s FINISHED, final SUCCEEDED
RM logNEW → NEW_SAVING → SUBMITTED → ACCEPTED all at 06:25:43; attempt LAUNCHED → RUNNING on REGISTERED at 06:25:48; FINISHED at 06:26:25
AM container (worker2)NEW → LOCALIZING → SCHEDULED → RUNNING (06:25:43) … EXITED_WITH_SUCCESS → DONE (06:26:25)
UsageAggregate Resource Allocation: 33,224 MB-seconds, 64 vcore-seconds
KillKILLED at +16.9 s: “Application … was killed by user hadoop at 172.22.0.2”
Impossible requestAM of 3,072 MB on 2,048 MB nodes → InvalidResourceRequestException before anything is created

Architecture at a glance

Nine steps take one application from your terminal to its result. The numbers match the steps in Part 4.

Clienthadoop jar … ResourceManagerscheduler + state store HDFS.staging/job_… NodeManager · worker2 ApplicationMastercontainer _000001 NodeManager · worker1 map task _000002 NodeManager · worker3 reduce task _000003 1 2 3 4 5 6 7 8 9 1 new app ID · 2 upload jar + job.xml · 3 submit 4 RM saves app, schedules the AM container 5 NM localizes + launches AM · 6 AM registers, then allocate heartbeats · 7–8 AM starts tasks on NMs 9 AM unregisters; logs aggregated to HDFS myphatshala.in

The lifecycle at three levels

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

Beginner A wedding caterer

  • You (the client) call the catering company's office (the ResourceManager) and book an event. The office writes it in the register first, so it isn't lost if the phone line drops.
  • The office sends one event manager (the ApplicationMaster) to the venue. Nothing is cooked until the manager arrives and reports in.
  • The manager asks the office for cooks and waiters (containers) as needed, tells them what to do, and lets them go when their part is done.
  • At the end the manager reports “done” and the office sends you the bill: people × hours, just like MB-seconds.

Intermediate Who talks to whom

  • Client → RM (ApplicationClientProtocol): getNewApplication, then submitApplication. Before submitting, the MapReduce client uploads the jar, job.xml and split list to .staging in HDFS (Lesson 8.6).
  • RM → NM: launches the AM container. The NM localizes the staged files (LOCALIZING), then starts the JVM (RUNNING).
  • AM → RM (ApplicationMasterProtocol): registerApplicationMaster, then allocate about every second with container requests that name preferred hosts and racks (data locality), and finally finishApplicationMaster.
  • AM → NM (ContainerManagementProtocol): startContainers for each task. Map and reduce tasks then report progress to the AM over the “umbilical” protocol, not to the RM.

Advanced Attempts, recovery and the state store

  • An application can have several attempts. If the AM container dies, the RM starts attempt _000002 elsewhere, up to yarn.resourcemanager.am.max-attempts (default 2; MapReduce also reads mapreduce.am.max-attempts). The MapReduce AM can recover finished tasks from the job history of the previous attempt.
  • The _SAVING states (NEW_SAVING, ALLOCATED_SAVING, FINAL_SAVING) are writes to the RM state store, so a restarted or failed-over RM (RM HA with ZooKeeper) still knows every application.
  • ACCEPTED can last a long time on a busy cluster: the queue or its AM limit (maximum-am-resource-percent) may not allow another ApplicationMaster yet (Lesson 11.3).
  • The RM's state (FINISHED) and the app's final status (SUCCEEDED or FAILED) are different things: a MapReduce job whose tasks failed still ends as FINISHED with final status FAILED.

Exam tip: For “Explain the lifecycle of a YARN application”, draw client, RM, NM with the AM and NMs with containers, and number the steps: new ID, submit, AM container allocated, AM launched, AM registers, allocate requests, containers launched, AM unregisters. Name the states NEW, SUBMITTED, ACCEPTED, RUNNING, FINISHED/FAILED/KILLED.

3 Types and ways to use it

The three state machines

YARN tracks the same job at three levels. Each has its own states, and each appears in a different log.

ObjectKept byStates in our run (in order)Where you see it
Application (RMApp)ResourceManagerNEW, NEW_SAVING, SUBMITTED, ACCEPTED, RUNNING, FINAL_SAVING, FINISHING, FINISHEDRM log, REST /ws/v1/cluster/apps, yarn application -status
Attempt (RMAppAttempt)ResourceManagerNEW, SUBMITTED, SCHEDULED, ALLOCATED_SAVING, ALLOCATED, LAUNCHED, RUNNING, FINAL_SAVING, FINISHING, FINISHEDRM log, yarn applicationattempt -list
ContainerNodeManagerNEW, LOCALIZING, SCHEDULED, RUNNING, EXITED_WITH_SUCCESS, DONENM log, yarn container -list

How an application can end

FINISHED · SUCCEEDED

The AM unregistered and said all went well. Our run: +35.6 s.

normal

FINISHED · FAILED

The AM unregistered but its job failed, e.g. a task failed 4 times (Lesson 11.5).

job failure

FAILED

The AM itself died on every allowed attempt, so YARN gave up.

AM failure

KILLED

A user or admin ran yarn application -kill, or a timeout or preemption policy removed it. Our run: +16.9 s.

stopped

Animated example: results day at a university board

The same states, on a different job: a board computes 12 lakh students' results overnight. Times are from our lab run (06:25:43 → 06:26:25); only the story around them is invented.

Application and attempt states 6 steps
Animated example application_…_0136 appattempt_…_000001 NEW → NEW_SAVING→ SUBMITTED → ACCEPTED NEW → SUBMITTED→ SCHEDULED → ALLOCATED→ LAUNCHED LAUNCHED → RUNNINGACCEPTED → RUNNING RUNNING → FINAL_SAVING→ FINISHINGRUNNING → FINAL_SAVING→ FINISHING FINISHING → FINISHEDFINISHING → FINISHED 06:25:43 · 23:00 at the board: results job submittedsaved in the RM state store, admitted to the queue 06:25:43 · a 512 MB container is found for the AMthe NodeManager starts localizing the job jar 06:25:48 · AM registered (5 s of JVM start-up)only now can it ask for task containers 06:25:49 – 06:26:19 · tasks runno RM state changes: the AM manages its tasks itself 06:26:19 · AM unregisters: “SUCCEEDED”the outcome is saved before anything is cleaned up 06:26:25 · AM container exits, app FINISHEDresults ready for the 06:00 website update myphatshala.in

yarn application -list -appStates ALL shows every application the RM still remembers, with its state and final status. yarn applicationattempt -list <appId> and yarn container -list <attemptId> zoom in one level at a time.

4 Step by step

Following one application on the lab cluster

We use the Module 8 lab (HDFS + YARN, three workers) and the 140 MB orders_x30.csv. app_states.py polls the RM REST API every 0.2 s; docker logs shows the RM and NM logs. Both are in “Run it on your PC”.

  1. Steps 1–3: the client asks for an ID, stages files, submits

    hadoop jar mr.jar ItemRevenue /user/hadoop/data/orders_x30.csv /out/lc1 &
    python3 app_states.py application_1792643020992_0136
    submitted application_1792643020992_0136 + 0.1 s state=ACCEPTED final=UNDEFINED containers=1 allocatedMB=512
    By the time our first poll arrived, the application had already passed NEW, NEW_SAVING and SUBMITTED. ACCEPTED with 1 container and 512 MB means the scheduler has already reserved the ApplicationMaster's container.
  2. Step 4: the ResourceManager records and schedules

    docker logs lab8-resourcemanager-1 | grep application_1792643020992_0136
    06:25:43 RMAppImpl - application_…_0136 State change from NEW to NEW_SAVING on event = START 06:25:43 RMAppImpl - application_…_0136 State change from NEW_SAVING to SUBMITTED on event = APP_NEW_SAVED 06:25:43 RMAppImpl - application_…_0136 State change from SUBMITTED to ACCEPTED on event = APP_ACCEPTED 06:25:43 RMAppAttemptImpl - appattempt_…_0136_000001 State change from SCHEDULED to ALLOCATED_SAVING on event = CONTAINER_ALLOCATED 06:25:43 RMAppAttemptImpl - appattempt_…_0136_000001 State change from ALLOCATED to LAUNCHED on event = LAUNCHED
    Every “_SAVING” state is a write to the RM's state store, so a restarted RM can recover. The scheduler found room for the AM in the same second.
  3. Step 5: the NodeManager localizes and launches the AM

    docker logs lab8-worker2-1 | grep container_1792643020992_0136_01_000001
    06:25:43 ContainerImpl - Container container_…_0136_01_000001 transitioned from NEW to LOCALIZING 06:25:43 ContainerImpl - Container container_…_0136_01_000001 transitioned from LOCALIZING to SCHEDULED 06:25:43 ContainerImpl - Container container_…_0136_01_000001 transitioned from SCHEDULED to RUNNING
    LOCALIZING copies the job jar and job.xml from the HDFS staging folder to the worker's disk. Container _000001 of attempt _01 is always the ApplicationMaster.
  4. Step 6: the ApplicationMaster registers

    06:25:48 appattempt_…_000001 State change from LAUNCHED to RUNNING on event = REGISTERED
    06:25:48 application_…_0136 State change from ACCEPTED to RUNNING on event = ATTEMPT_REGISTERED
    +  5.0 s  state=RUNNING    final=UNDEFINED containers=1  allocatedMB=512
    The five seconds between LAUNCHED and REGISTERED are the MRAppMaster JVM starting and reading the job configuration. Only a registered AM may send allocate requests.
  5. Steps 7–8: the AM gets containers and runs tasks

    +  6.0 s  state=RUNNING    final=UNDEFINED containers=2  allocatedMB=1024
    + 27.1 s  state=RUNNING    final=UNDEFINED containers=1  allocatedMB=512
    + 28.1 s  state=RUNNING    final=UNDEFINED containers=2  allocatedMB=1024
    One second after registering, the AM held a second container: the single map task (one 140 MB split, Lesson 8.2). When the map finished at +27 s its container was released, and a reduce container arrived at +28 s. The RM logged no application state changes in this phase: tasks are the AM's business.
  6. Step 9: unregister, clean up, send the bill

    yarn application -status application_1792643020992_0136
    + 35.6 s state=FINISHED final=SUCCEEDED containers=1 allocatedMB=512 Application-Type : MAPREDUCE Queue : root.default State : FINISHED Final-State : SUCCEEDED AM Host : worker2 Aggregate Resource Allocation : 33224 MB-seconds, 64 vcore-seconds
    The AM unregistered at 06:26:19 (FINAL_SAVING, FINISHING); its container exited at 06:26:25 (EXITED_WITH_SUCCESS, DONE) and the app became FINISHED. 33,224 MB-seconds over about 36 s means roughly 920 MB held on average: the AM all the time, plus one task container most of the time.

Two ways it goes differently

# kill the second run 15 s after submitting it
yarn application -kill application_1792643020992_0137
# ask for a 3 GB ApplicationMaster on 2 GB NodeManagers
hadoop jar mr.jar ItemRevenue -D yarn.app.mapreduce.am.resource.mb=3072 …
+ 16.9 s state=KILLED final=KILLED containers=0 allocatedMB=0 diagnostics: Application application_1792643020992_0137 was killed by user hadoop at 172.22.0.2 Job job_1792643020992_0137 failed with state KILLED due to: Application … was killed by user hadoop at 172.22.0.2 InvalidResourceRequestException: Invalid resource request! Cannot allocate containers as requested resource is greater than maximum allowed allocation.

A kill releases every container at once and records who killed it and from where. An impossible request is rejected at submission, so no application ever reaches ACCEPTED.

An application stuck in ACCEPTED is not “almost running”. It means no container has been found for its ApplicationMaster yet: the queue is full, the queue's AM share (maximum-am-resource-percent) is used up, or no node is big enough. Check the queue in the RM UI before resubmitting, or you will just add another waiting application.

5 See it happen

From Enter to FINISHED

Six steps of our 36-second run: what the polling script printed, and what the daemons were doing.

One YARN application 6 steps
What you see hadoop@namenode : /tmp/mr $ hadoop jar mr.jar ItemRevenue …submitted application_…_0136 + 0.1 s ACCEPTED containers=1allocatedMB=512 + 5.0 s RUNNING containers=1(the AM has registered) + 6.0 s RUNNING containers=2allocatedMB=1024 +27.1 s RUNNING containers=1+28.1 s RUNNING containers=2 +35.6 s FINISHED final=SUCCEEDED33224 MB-seconds, 64 vcore-seconds myphatshala.in
Behind the screen client ResourceManager worker2 worker1 worker3 getNewApplication, stage jar in HDFS, submitApplication AM: LOCALIZINGsaved, ACCEPTED, AM container placed on worker2 AM running AM registers with the RM after 5 s of JVM start-up map taskallocate → container → AM calls startContainers reduce taskmap container released, reduce container started AM unregisters, exits; logs go to HDFS myphatshala.in
6 Real-world use

The lifecycle at work

MB-seconds→ ₹ per team

1. Chargeback in a shared bank cluster

A bank's platform team reads every finished application's MB-seconds and vcore-seconds from the RM and bills each department monthly for its share of the cluster.

RM apps API→MB-s per queue→Monthly bill
ACCEPTEDfor 20 min ⚠

2. Alerting on stuck jobs at a telecom operator

The nightly billing pipeline alerts if any application stays ACCEPTED for more than 10 minutes, a sign that its queue is full or an AM can't be placed.

Poll app states→ACCEPTED too long→Page on-call
_01_02

3. Surviving a lost node during a long ETL

An e-commerce company's 3-hour ETL lost the node running its ApplicationMaster. YARN started attempt 2, and the MapReduce AM recovered the completed tasks instead of starting from zero.

AM node dies→attempt 2 + recovery→Job continues
spark-submitdriver = AM

4. Spark and other engines on the same YARN

A Spark job in cluster mode follows the same lifecycle: its driver runs inside the ApplicationMaster container and its executors are ordinary YARN containers. Hive on Tez and Flink do the same.

spark-submit→AM + executors→Same states

5. Queue-time dashboards for a research cluster

A university HPC/Hadoop team plots “time from submit to RUNNING” per queue each day. Rising queue time tells them when students' assignment deadlines need a bigger share.

startedTime, launchTime→wait per queue→Capacity decisions

Simplified; organisations differ in tools and policies.

7 Run the code

Simulate the application and attempt state machines

A small model of YARN's RMApp and RMAppAttempt state machines. Each event moves an object to its next state, exactly as the lines in the ResourceManager log say. Run it and compare with our real log.

APP = {("NEW", "START"): "NEW_SAVING", ("NEW_SAVING", "APP_NEW_SAVED"): "SUBMITTED",
       ("SUBMITTED", "APP_ACCEPTED"): "ACCEPTED", ("ACCEPTED", "ATTEMPT_REGISTERED"): "RUNNING",
       ("RUNNING", "ATTEMPT_UNREGISTERED"): "FINAL_SAVING", ("FINAL_SAVING", "APP_UPDATE_SAVED"): "FINISHING",
       ("FINISHING", "ATTEMPT_FINISHED"): "FINISHED"}
ATTEMPT = {("NEW", "START"): "SUBMITTED", ("SUBMITTED", "ATTEMPT_ADDED"): "SCHEDULED",
           ("SCHEDULED", "CONTAINER_ALLOCATED"): "ALLOCATED_SAVING", ("ALLOCATED_SAVING", "ATTEMPT_NEW_SAVED"): "ALLOCATED",
           ("ALLOCATED", "LAUNCHED"): "LAUNCHED", ("LAUNCHED", "REGISTERED"): "RUNNING",
           ("RUNNING", "UNREGISTERED"): "FINAL_SAVING", ("FINAL_SAVING", "ATTEMPT_UPDATE_SAVED"): "FINISHING",
           ("FINISHING", "CONTAINER_FINISHED"): "FINISHED"}
state = {"app": "NEW", "attempt": "NEW"}

def fire(obj, event, time):
    table = APP if obj == "app" else ATTEMPT
    new = table[(state[obj], event)]          # an event that isn't allowed here raises KeyError
    print(f"{time}  {obj:7} {state[obj]:>16} -> {new:16} on {event}")
    state[obj] = new

for t, obj, ev in [("06:25:43", "app", "START"), ("06:25:43", "app", "APP_NEW_SAVED"), ("06:25:43", "app", "APP_ACCEPTED"),
                   ("06:25:43", "attempt", "START"), ("06:25:43", "attempt", "ATTEMPT_ADDED"),
                   ("06:25:43", "attempt", "CONTAINER_ALLOCATED"), ("06:25:43", "attempt", "ATTEMPT_NEW_SAVED"),
                   ("06:25:43", "attempt", "LAUNCHED"), ("06:25:48", "attempt", "REGISTERED"),
                   ("06:25:48", "app", "ATTEMPT_REGISTERED"), ("06:26:19", "attempt", "UNREGISTERED"),
                   ("06:26:19", "attempt", "ATTEMPT_UPDATE_SAVED"), ("06:26:19", "app", "ATTEMPT_UNREGISTERED"),
                   ("06:26:19", "app", "APP_UPDATE_SAVED"), ("06:26:25", "attempt", "CONTAINER_FINISHED"),
                   ("06:26:25", "app", "ATTEMPT_FINISHED")]:
    fire(obj, ev, t)
06:25:43 app NEW -> NEW_SAVING on START 06:25:43 app NEW_SAVING -> SUBMITTED on APP_NEW_SAVED 06:25:43 app SUBMITTED -> ACCEPTED on APP_ACCEPTED 06:25:43 attempt NEW -> SUBMITTED on START 06:25:43 attempt SUBMITTED -> SCHEDULED on ATTEMPT_ADDED 06:25:43 attempt SCHEDULED -> ALLOCATED_SAVING on CONTAINER_ALLOCATED 06:25:43 attempt ALLOCATED_SAVING -> ALLOCATED on ATTEMPT_NEW_SAVED 06:25:43 attempt ALLOCATED -> LAUNCHED on LAUNCHED 06:25:48 attempt LAUNCHED -> RUNNING on REGISTERED 06:25:48 app ACCEPTED -> RUNNING on ATTEMPT_REGISTERED 06:26:19 attempt RUNNING -> FINAL_SAVING on UNREGISTERED 06:26:19 attempt FINAL_SAVING -> FINISHING on ATTEMPT_UPDATE_SAVED 06:26:19 app RUNNING -> FINAL_SAVING on ATTEMPT_UNREGISTERED 06:26:19 app FINAL_SAVING -> FINISHING on APP_UPDATE_SAVED 06:26:25 attempt FINISHING -> FINISHED on CONTAINER_FINISHED 06:26:25 app FINISHING -> FINISHED on ATTEMPT_FINISHED
  • new = table[(state[obj], event)] # an event that isn't allowed here raises KeyErrorA state machine only accepts certain events in each state. YARN logs “Can't handle this event at current state” in that case.
  • ("06:25:43", "attempt", "LAUNCHED"), ("06:25:48", "attempt", "REGISTERED"),The 5-second gap: the MRAppMaster JVM starting up on worker2.
  • ("06:25:48", "app", "ATTEMPT_REGISTERED"), ("06:26:19", "attempt", "UNREGISTERED"),Between these two lines the RM logs nothing about the app: the AM runs its own tasks.

Run it on your PC: watch, kill and break an application

Use the Module 8 lab (Lesson 8.1). Copy app_states.py and lifecycle_lab.sh into the namenode container (docker cp … lab8-namenode-1:/tmp/mr/) and run the script there. It runs ItemRevenue on orders_x30.csv while polling the ResourceManager, kills a second run, and asks for an impossible container. The RM and NM logs are read from your PC with docker logs.

Poll the ResourceManager

# app_states.py: submit-and-watch. Polls the ResourceManager REST API every 0.2 s and prints every
# change of application state, plus how many containers the app holds. Usage: python3 app_states.py <app_id>
import json, sys, time, urllib.request
RM = "http://resourcemanager:8088/ws/v1/cluster/apps/"
app, t0, last = sys.argv[1], time.time(), None
while True:
    try:
        a = json.load(urllib.request.urlopen(RM + app))["app"]
    except Exception:
        time.sleep(0.2); continue
    now = (a["state"], a.get("finalStatus"), a.get("runningContainers", 0), a.get("allocatedMB", 0))
    if now != last:
        print(f"+{time.time() - t0:5.1f} s  state={now[0]:10} final={now[1]:9} containers={max(now[2], 0)}  allocatedMB={max(now[3], 0)}")
        last = now
    if a["state"] in ("FINISHED", "FAILED", "KILLED"):
        print("diagnostics:", (a.get("diagnostics") or "-")[:150])
        break
    time.sleep(0.2)

# Used by lifecycle_lab.sh:  python3 /tmp/mr/app_states.py application_<id>
== 1. a normal job submitted application_1792643020992_0136 + 0.1 s state=ACCEPTED final=UNDEFINED containers=1 allocatedMB=512 + 5.0 s state=RUNNING final=UNDEFINED containers=1 allocatedMB=512 + 6.0 s state=RUNNING final=UNDEFINED containers=2 allocatedMB=1024 + 27.1 s state=RUNNING final=UNDEFINED containers=1 allocatedMB=512 + 28.1 s state=RUNNING final=UNDEFINED containers=2 allocatedMB=1024 + 35.6 s state=FINISHED final=SUCCEEDED containers=1 allocatedMB=512 diagnostics: - -- the same app as YARN reports it afterwards Application-Type : MAPREDUCE Queue : root.default Start-Time : 1792650343192 Finish-Time : 1792650379385 Progress : 100% State : FINISHED Final-State : SUCCEEDED AM Host : worker2 Aggregate Resource Allocation : 33224 MB-seconds, 64 vcore-seconds Aggregate Resource Preempted : 0 MB-seconds, 0 vcore-seconds

Real output of the first part of lifecycle_lab.sh. The NEW, NEW_SAVING and SUBMITTED states pass in under 0.1 s, so the poller first sees ACCEPTED; the RM log below has them all.

Watch, kill, break

# lifecycle_lab.sh: watch a YARN application move through its states, then kill one
cd /tmp/mr
run_and_watch() {   # output dir, extra args
  hdfs dfs -rm -r -f -skipTrash $1 > /dev/null 2>&1
  hadoop jar mr.jar ItemRevenue $2 /user/hadoop/data/orders_x30.csv $1 > /tmp/lc.log 2>&1 &
  until app=$(grep -o "application_[0-9_]*" /tmp/lc.log | head -1) && [ -n "$app" ]; do sleep 0.1; done
  echo "submitted $app"
  python3 /tmp/mr/app_states.py $app
}
echo "== 1. a normal job"
run_and_watch /out/lc1 ""
wait
echo "-- the same app as YARN reports it afterwards"
yarn application -status $(grep -o "application_[0-9_]*" /tmp/lc.log | head -1) 2>/dev/null | grep -E "Application-Type|Queue|State|Final-State|Start-Time|Finish-Time|Progress|AM Host|Aggregate Resource" | sed 's/^\s*//'
echo "== 2. kill a job while it runs"
hdfs dfs -rm -r -f -skipTrash /out/lc2 > /dev/null 2>&1
hadoop jar mr.jar ItemRevenue /user/hadoop/data/orders_x30.csv /out/lc2 > /tmp/lc2.log 2>&1 &
until app=$(grep -o "application_[0-9_]*" /tmp/lc2.log | head -1) && [ -n "$app" ]; do sleep 0.1; done
python3 /tmp/mr/app_states.py $app &
sleep 15; yarn application -kill $app 2>&1 | grep -v INFO
wait
grep -E "Job .* failed|killed|KILLED" /tmp/lc2.log | sed -E 's/^[0-9-]+ [0-9:]+ INFO  //' | head -2
echo "== 3. ask for an AM bigger than any node (3 GB on 2 GB NodeManagers)"
hadoop jar mr.jar ItemRevenue -D yarn.app.mapreduce.am.resource.mb=3072 /user/hadoop/data/canteen_orders.csv /out/lc3 2>&1 | grep -E "Exception|Invalid" | head -2 | cut -c1-250

# Run:  docker exec lab8-namenode-1 bash /tmp/mr/lifecycle_lab.sh
== 1. a normal job submitted application_1792643020992_0136 + 0.1 s state=ACCEPTED final=UNDEFINED containers=1 allocatedMB=512 + 5.0 s state=RUNNING final=UNDEFINED containers=1 allocatedMB=512 + 6.0 s state=RUNNING final=UNDEFINED containers=2 allocatedMB=1024 + 27.1 s state=RUNNING final=UNDEFINED containers=1 allocatedMB=512 + 28.1 s state=RUNNING final=UNDEFINED containers=2 allocatedMB=1024 + 35.6 s state=FINISHED final=SUCCEEDED containers=1 allocatedMB=512 diagnostics: - -- the same app as YARN reports it afterwards Application-Type : MAPREDUCE Queue : root.default Start-Time : 1792650343192 Finish-Time : 1792650379385 Progress : 100% State : FINISHED Final-State : SUCCEEDED AM Host : worker2 Aggregate Resource Allocation : 33224 MB-seconds, 64 vcore-seconds Aggregate Resource Preempted : 0 MB-seconds, 0 vcore-seconds == 2. kill a job while it runs + 0.0 s state=ACCEPTED final=UNDEFINED containers=1 allocatedMB=512 + 4.1 s state=RUNNING final=UNDEFINED containers=1 allocatedMB=512 + 5.2 s state=RUNNING final=UNDEFINED containers=2 allocatedMB=1024 + 16.9 s state=KILLED final=KILLED containers=0 allocatedMB=0 diagnostics: Application application_1792643020992_0137 was killed by user hadoop at 172.22.0.2 Killing application application_1792643020992_0137 Job:1767 - Job job_1792643020992_0137 failed with state KILLED due to: Application application_1792643020992_0137 was killed by user hadoop at 172.22.0.2 == 3. ask for an AM bigger than any node (3 GB on 2 GB NodeManagers) Exception in thread "main" java.io.IOException: org.apache.hadoop.yarn.exceptions.InvalidResourceRequestException: Invalid resource request! Cannot allocate containers as requested resource is greater than maximum allowed allocation. Requested resour at org.apache.hadoop.yarn.server.resourcemanager.scheduler.SchedulerUtils.throwInvalidResourceException(SchedulerUtils.java:527)

Real output. The InvalidResourceRequestException line is cut at 250 characters by the script; the full message also names the requested and the maximum resources.

Every state change in the daemon logs

# In the mr-lab folder, after lifecycle_lab.sh (use your own application id):
app=application_1792643020992_0136; id=${app#application_}
docker logs lab8-resourcemanager-1 2>&1 | grep "$app\|appattempt_$id" | grep "State change"
docker logs lab8-worker2-1 2>&1 | grep "container_${id}_01_000001 transitioned"
# worker2 is the AM host shown by yarn application -status
$ docker logs lab8-resourcemanager-1 | grep application_1792643020992_0136 (state changes only) 06:25:43 RMAppImpl:921 - application_1792643020992_0136 State change from NEW to NEW_SAVING on event = START 06:25:43 RMAppImpl:921 - application_1792643020992_0136 State change from NEW_SAVING to SUBMITTED on event = APP_NEW_SAVED 06:25:43 RMAppImpl:921 - application_1792643020992_0136 State change from SUBMITTED to ACCEPTED on event = APP_ACCEPTED 06:25:43 RMAppAttemptImpl:930 - appattempt_1792643020992_0136_000001 State change from NEW to SUBMITTED on event = START 06:25:43 RMAppAttemptImpl:930 - appattempt_1792643020992_0136_000001 State change from SUBMITTED to SCHEDULED on event = ATTEMPT_ADDED 06:25:43 RMAppAttemptImpl:930 - appattempt_1792643020992_0136_000001 State change from SCHEDULED to ALLOCATED_SAVING on event = CONTAINER_ALLOCATED 06:25:43 RMAppAttemptImpl:930 - appattempt_1792643020992_0136_000001 State change from ALLOCATED_SAVING to ALLOCATED on event = ATTEMPT_NEW_SAVED 06:25:43 RMAppAttemptImpl:930 - appattempt_1792643020992_0136_000001 State change from ALLOCATED to LAUNCHED on event = LAUNCHED 06:25:48 RMAppAttemptImpl:930 - appattempt_1792643020992_0136_000001 State change from LAUNCHED to RUNNING on event = REGISTERED 06:25:48 RMAppImpl:921 - application_1792643020992_0136 State change from ACCEPTED to RUNNING on event = ATTEMPT_REGISTERED 06:26:19 RMAppAttemptImpl:930 - appattempt_1792643020992_0136_000001 State change from RUNNING to FINAL_SAVING on event = UNREGISTERED 06:26:19 RMAppAttemptImpl:930 - appattempt_1792643020992_0136_000001 State change from FINAL_SAVING to FINISHING on event = ATTEMPT_UPDATE_SAVED 06:26:19 RMAppImpl:921 - application_1792643020992_0136 State change from RUNNING to FINAL_SAVING on event = ATTEMPT_UNREGISTERED 06:26:19 RMAppImpl:921 - application_1792643020992_0136 State change from FINAL_SAVING to FINISHING on event = APP_UPDATE_SAVED 06:26:25 RMAppAttemptImpl:930 - appattempt_1792643020992_0136_000001 State change from FINISHING to FINISHED on event = CONTAINER_FINISHED 06:26:25 RMAppImpl:921 - application_1792643020992_0136 State change from FINISHING to FINISHED on event = ATTEMPT_FINISHED $ docker logs lab8-worker2-1 | grep container_1792643020992_0136_01_000001 (the AM container on its NodeManager) 06:25:43 ContainerImpl:2198 - Container container_1792643020992_0136_01_000001 transitioned from NEW to LOCALIZING 06:25:43 ContainerImpl:2198 - Container container_1792643020992_0136_01_000001 transitioned from LOCALIZING to SCHEDULED 06:25:43 ContainerImpl:2198 - Container container_1792643020992_0136_01_000001 transitioned from SCHEDULED to RUNNING 06:26:25 ContainerImpl:2198 - Container container_1792643020992_0136_01_000001 transitioned from RUNNING to EXITED_WITH_SUCCESS 06:26:25 ContainerImpl:2198 - Container container_1792643020992_0136_01_000001 transitioned from EXITED_WITH_SUCCESS to DONE

Real log lines, with the date and log level trimmed for width. Your AM may run on another worker; check “AM Host” first.

8 Line by line

A container's life on the NodeManager

The NodeManager keeps its own small state machine for each container. Follow the ApplicationMaster's container _000001 through the five transitions we saw in the worker2 log.

Worked examples: real problems, solved and traced in memory

Four lifecycle problems from different organisations: billing, waiting, retries and kills.

Example 1BeginnerChargeback · bank

Bill a department for its jobs

Problem statement

A bank charges departments ₹5 per GB-hour of YARN memory (an illustrative internal rate). The risk team ran 40,000 jobs last month, each about like ours: 33,224 MB-seconds. What is the bill?

Input
40,000 × 33,224 MB-s · ₹5 per GB-hour
Expected output
1,328,960,000 MB-s = 360.50 GB-hours -> Rs 1,803

Approach

  1. Total MB-seconds = jobs × MB-s per job.
  2. Convert: GB-hours = MB-s / 1024 / 3600.
  3. Multiply by the rate.

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 2IntermediateAccounting · lab run

Where did our 33,224 MB-seconds go?

Problem statement

Our app started at 1792650343192 ms and finished at 1792650379385 ms. The AM held 512 MB all the time. How many MB-seconds were the task containers, and how many seconds of 512 MB containers is that?

Input
start/finish in ms · total 33,224 MB-s · AM 512 MB
Expected output
elapsed 36.2 s, AM 18,531 MB-s, tasks 14,693 MB-s = 28.7 s of a 512 MB container

Approach

  1. Elapsed = finish − start.
  2. AM share = 512 × elapsed.
  3. Task share = total − AM share; divide by 512.

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 3IntermediateRetries · e-commerce

Will the sale-day ETL survive an AM crash?

Problem statement

An e-commerce ETL runs 3 hours. The node running its ApplicationMaster has a 5% chance of failing during that time; a new attempt lands on another node with the same 5% chance. With max-attempts = 1 vs 2, what is the chance the application FAILS because of AM crashes?

Input
p(AM node fails) = 0.05 per attempt
Expected output
max-attempts=1: fails 5.00% of the time
max-attempts=2: fails 0.25% of the time

Approach

  1. With 1 attempt, failure probability = p.
  2. With 2 attempts, both must fail: p × p.
  3. Compare.

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 4AdvancedOperations · telecom

Kill or wait?

Problem statement

At 02:00 a telecom's billing job is stuck: RUNNING for 3 h with 4 of 400 maps left, each retrying. The next job in the queue must finish by 06:00 and needs 3.5 h on the full queue. The stuck job holds 30% of the queue. Should on-call kill it now, or wait until 03:00?

Input
deadline 06:00 · next job 3.5 h at 100%, longer with less
Expected output
kill now, start 02:00 at 100%  done at 05:30 OK
start now beside it at 70%     done at 07:00 LATE
wait, start 03:00 at 100%      done at 06:30 LATE

Approach

  1. Runtime of the next job scales with the share it gets.
  2. Compute finish time if started at 02:00 with 70% vs at 03:00 with 100%.
  3. Compare with the deadline.

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.2 in one screen

  • Client: getNewApplication → stage jar/conf in HDFS → submitApplication (with the AM's ContainerLaunchContext).
  • App states: NEW → NEW_SAVING → SUBMITTED → ACCEPTED → RUNNING → FINAL_SAVING → FINISHING → FINISHED (or FAILED, KILLED).
  • Attempt states: … SCHEDULED → ALLOCATED → LAUNCHED → RUNNING (on REGISTERED) …; container: NEW → LOCALIZING → SCHEDULED → RUNNING → EXITED_WITH_SUCCESS → DONE.
  • Lab: ACCEPTED +0.1 s, RUNNING +5.0 s (AM JVM start), FINISHED +35.6 s; 33,224 MB-seconds, 64 vcore-seconds.
  • The AM registers, sends allocate heartbeats with locality preferences, and launches tasks with startContainers; tasks report to the AM.
  • An application can have several attempts (yarn.resourcemanager.am.max-attempts, default 2).
  • Kill: yarn application -kill frees all containers at once; too-big requests fail with InvalidResourceRequestException.

Interview and viva questions

What are the steps when a YARN application is submitted?

The client asks the ResourceManager for a new application ID, uploads the application's files (for MapReduce the jar, job.xml and splits) to a staging directory in HDFS, and submits an ApplicationSubmissionContext. The ResourceManager saves it, the scheduler admits it to a queue and allocates a container for the ApplicationMaster, and a NodeManager localizes the files and launches it. The ApplicationMaster registers, requests further containers through allocate calls, launches its tasks on NodeManagers, and unregisters with a final status when the work is done.

What is the difference between an application and an application attempt in YARN?

An application is the whole submission. An attempt is one run of its ApplicationMaster. If the ApplicationMaster fails, the ResourceManager can start a new attempt with a new AM container, up to yarn.resourcemanager.am.max-attempts (2 by default). Attempt IDs look like appattempt_<cluster timestamp>_<app number>_000001.

Why is my YARN application stuck in ACCEPTED?

ACCEPTED means the application is queued but its ApplicationMaster container has not been started. Common causes: the queue is at capacity, the queue's limit for ApplicationMasters (maximum-am-resource-percent) is reached, no NodeManager has enough free memory, or NodeManagers are unhealthy or lost. Check the queue and node pages in the ResourceManager UI and the application's diagnostics.

What is the difference between state and final status of a YARN application?

State is the ResourceManager's lifecycle state (for example RUNNING or FINISHED). Final status is the outcome reported by the application: SUCCEEDED, FAILED, KILLED or UNDEFINED while running. An application can be in state FINISHED with final status FAILED, for example a MapReduce job whose tasks failed.

What are MB-seconds in YARN?

The aggregate resource allocation of an application: for every container, its memory in MB multiplied by the seconds it was allocated, summed. vcore-seconds are the same for virtual cores. They appear in yarn application -status and the REST API and are used for chargeback and capacity planning.

Practice problems

Lifecycle exercises built on the real states and numbers from our lab.

P1Easy

Count the state changes

The RM log showed 7 application transitions and 10 attempt transitions; the NM log 5 for the AM container. How many in total?

Input
7 + 10 + 5
Expected output
22 state changes for one 36-second job
Show solution and explanation
print(7 + 10 + 5, "state changes for one 36-second job")
22 state changes for one 36-second job

How it works: 22 logged transitions, all without anyone typing a command after submit.

P2Easy

Time to register

The attempt was LAUNCHED at 06:25:43 and RUNNING at 06:25:48. How long did the AM take to start?

Input
two timestamps
Expected output
5 s
Show solution and explanation
from datetime import datetime
f = "%H:%M:%S"
print((datetime.strptime("06:25:48", f) - datetime.strptime("06:25:43", f)).seconds, "s")
5 s

How it works: 5 s: mostly JVM start-up and reading the job configuration.

P3Easy

Average memory held

33,224 MB-seconds over 36.2 seconds. What was the average memory held?

Input
33,224 MB-s · 36.2 s
Expected output
918 MB on average
Show solution and explanation
print(round(33224 / 36.2), "MB on average")
918 MB on average

How it works: About 918 MB: the 512 MB AM plus one 512 MB task container most of the time.

P4Medium

Parse a state line

Extract object, from-state, to-state and event from a real RM log line.

Input
one log line
Expected output
('application_1792643020992_0136', 'ACCEPTED', 'RUNNING', 'ATTEMPT_REGISTERED')
Show solution and explanation
import re
line = "06:25:48 RMAppImpl:921 - application_1792643020992_0136 State change from ACCEPTED to RUNNING on event = ATTEMPT_REGISTERED"
m = re.search(r"(\S+) State change from (\S+) to (\S+) on event = (\S+)", line)
print(m.groups())
('application_1792643020992_0136', 'ACCEPTED', 'RUNNING', 'ATTEMPT_REGISTERED')

How it works: The four parts you need to build a timeline of any application from the RM log.

P5Medium

Queue wait report

Given (startedTime, launchTime) in ms for three apps, print each app's wait for its AM in seconds.

Input
3 apps from the RM REST API
Expected output
0136 0.1 s
0137 0.1 s
0138 60.5 s
Show solution and explanation
apps = {"0136": (1792650343192, 1792650343300), "0137": (1792650380000, 1792650380050), "0138": (1792650400000, 1792650460500)}
for a, (s, l) in apps.items():
    print(a, f"{(l - s) / 1000:.1f} s")
0136 0.1 s 0137 0.1 s 0138 60.5 s

How it works: The first two started at once; the third waited a minute in ACCEPTED (illustrative times).

P6Medium

Allowed transitions

Using a transition table, check whether ACCEPTED → FINISHED is allowed directly.

Input
small table
Expected output
False
Show solution and explanation
allowed = {"ACCEPTED": {"RUNNING", "FAILED", "KILLED", "FINAL_SAVING"}, "RUNNING": {"FINAL_SAVING", "KILLED", "FAILED"}}
print("FINISHED" in allowed["ACCEPTED"])
False

How it works: False: an app must pass through RUNNING (or fail/be killed). In YARN an ACCEPTED app can also be killed.

P7Business problem

Cost of idle ApplicationMasters

A team submits 3,000 tiny jobs a day; each holds a 1,536 MB AM for 40 s. How many GB-hours a day are spent on AMs alone, and at ₹0.05 per GB-hour what does that cost per month?

Input
3,000 × 1,536 MB × 40 s
Expected output
50 GB-hours a day, Rs 75 a month
Show solution and explanation
gb_h = 3000 * 1536 * 40 / 1024 / 3600
print(f"{gb_h:.0f} GB-hours a day, Rs {gb_h * 0.05 * 30:,.0f} a month")
50 GB-hours a day, Rs 75 a month

How it works: About 50 GB-hours a day, ₹75 a month (illustrative), and the AMs also block queue slots. Batching jobs or uber mode for tiny jobs helps.

P8Advanced

Rebuild a timeline

From a list of (time, object, from, to) log entries, print how long each state lasted for the application.

Input
app transitions from the RM log
Expected output
NEW            0 s
NEW_SAVING     0 s
SUBMITTED      0 s
ACCEPTED       5 s
RUNNING       31 s
FINAL_SAVING   0 s
FINISHING      6 s
Show solution and explanation
from datetime import datetime
log = [("06:25:43", "NEW"), ("06:25:43", "NEW_SAVING"), ("06:25:43", "SUBMITTED"), ("06:25:43", "ACCEPTED"),
       ("06:25:48", "RUNNING"), ("06:26:19", "FINAL_SAVING"), ("06:26:19", "FINISHING"), ("06:26:25", "FINISHED")]
t = lambda s: datetime.strptime(s, "%H:%M:%S")
for (a, s1), (b, _) in zip(log, log[1:]):
    print(f"{s1:12} {(t(b) - t(a)).seconds:3d} s")
NEW 0 s NEW_SAVING 0 s SUBMITTED 0 s ACCEPTED 5 s RUNNING 31 s FINAL_SAVING 0 s FINISHING 6 s

How it works: ACCEPTED 5 s, RUNNING 31 s, FINISHING 6 s (waiting for the AM container to exit). The log has 1-second resolution, so the fast states show 0.

Real-world scenario questions (asked in company interviews)

The YARN application lifecycle is asked in Hadoop, Spark and platform-engineering interviews.

YARN · basicsFresher

“What happens when you run hadoop jar?”

The classic “walk me through it” question.

The interviewer is checking: the roles of client, RM, AM and NM.

Show a strong answer
  • The client gets an application ID, uploads the jar, configuration and splits to HDFS staging, and submits to the ResourceManager.
  • The RM allocates a container for the MRAppMaster; a NodeManager localizes the files and launches it; the AM registers.
  • The AM requests containers (preferring nodes with the data), launches map and reduce tasks via NodeManagers, tracks them, then unregisters; logs are aggregated.
Troubleshooting · queuesExperienced

Jobs sit in ACCEPTED for 30 minutes every morning

The cluster dashboard shows 40% free memory at the same time.

The interviewer is checking: AM limits and queue limits, not just total capacity.

Show a strong answer
  • Free memory may belong to other queues; the submitting queue can be at its capacity or user limit.
  • The queue's maximum-am-resource-percent may be reached by many small jobs, so no new AM can start.
  • Check queue usage in the RM scheduler page, raise the AM share or queue capacity, or batch small jobs.
Reliability · attemptsExperienced

“The node running the ApplicationMaster died. Is the job lost?”

A follow-up on fault tolerance.

The interviewer is checking: application attempts and recovery.

Show a strong answer
  • The RM notices the AM container is gone and starts a new attempt on another node, up to yarn.resourcemanager.am.max-attempts (default 2).
  • The MapReduce AM can recover completed tasks from the previous attempt's history; other frameworks may restart the work.
  • With RM HA and the state store, even an RM failover keeps the application.
Platform · costExperienced

Design chargeback for a shared Hadoop cluster

Five departments share one cluster and finance wants a monthly split.

The interviewer is checking: knowing where YARN usage data comes from.

Show a strong answer
  • Each finished application has memorySeconds and vcoreSeconds in the RM REST API (and the timeline/history servers).
  • Collect them per queue or user daily, convert to GB-hours and vcore-hours, and price both.
  • Reserve capacity costs separately; idle reserved capacity is a policy choice, not just usage.