Stranded resources and DRF
Simulate a modelNeeds Python
Problem
Machines have several resources and tasks need them in different proportions, so the resource a task uses most runs out first and the others are stranded: present but unusable. Dominant resource fairness (DRF) shares a multi-resource machine fairly by giving the next task to the tenant whose dominant share (its largest share of any resource) is smallest.
(a) Compute how many (1 CPU, 16 GB) tasks fit on a (32 CPU, 256 GB) machine and how much CPU is stranded. (b) Run the DRF example from Fair and capacity scheduling (9 CPU, 18 GB; tenant A tasks (1, 4), tenant B tasks (3, 1)) by repeatedly giving one task to the tenant with the smallest dominant share, until the next task does not fit.
The question. How much is stranded in (a), and how does DRF divide the machine in (b)?
Predict
Expect {'A': 3, 'B': 2} with 9 of 9 CPU and 14 of 18 GB in use, so 4 GB is stranded. For (a) the answer is 16 tasks and 16 CPU stranded.
Run it
1Install uv once. It fetches Python and the lab's packages for you.
Terminal curl -LsSf https://astral.sh/uv/install.sh | sh2Get the script. It is the whole lab: read it, then download it.
View the codeHide the code
stranded-resources-and-drf.py # /// script # requires-python = ">=3.10" # /// # Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT # https://thedataengineering.com/lab-code/LICENSE.txt """Stranded resources and DRF. (a) How many (1 CPU, 16 GB) tasks fit on a (32 CPU, 256 GB) machine, and how much CPU is stranded. (b) Dominant resource fairness on 9 CPU and 18 GB with tenant A's tasks needing (1 CPU, 4 GB) and tenant B's (3 CPU, 1 GB): one task at a time to the tenant with the smallest dominant share. """ import argparse, json p = argparse.ArgumentParser(description=__doc__.splitlines()[0]) p.add_argument("--json", help="Also write the results to this JSON file.") args = p.parse_args() machine, task = (32, 256), (1, 16) fit = min(machine[0] // task[0], machine[1] // task[1]); stranded = machine[0] - fit * task[0] cap = (9, 18); need = {"A": (1, 4), "B": (3, 1)}; used = {"A": [0, 0], "B": [0, 0]}; tasks = {"A": 0, "B": 0}; steps = [] def dom(t): return max(used[t][0] / cap[0], used[t][1] / cap[1]) while True: t = min(need, key=dom) # tenant with the smallest dominant share total = [used["A"][r] + used["B"][r] for r in (0, 1)] # the machine is shared: check what both tenants use if total[0] + need[t][0] > cap[0] or total[1] + need[t][1] > cap[1]: break # simple stop: the next task does not fit used[t][0] += need[t][0]; used[t][1] += need[t][1]; tasks[t] += 1 steps.append({"step": len(steps) + 1, "tenant": t, "a_dominant": dom("A"), "b_dominant": dom("B")}) cpu = used["A"][0] + used["B"][0]; mem = used["A"][1] + used["B"][1] summary = {"fit": fit, "stranded_cpu": stranded, "tasks_a": tasks["A"], "tasks_b": tasks["B"], "cpu_used": cpu, "mem_used": mem, "mem_stranded": cap[1] - mem, "cpu_stranded": cap[0] - cpu} print(f"(a) {fit} tasks fit; {stranded} of {machine[0]} CPUs stranded") print(f"(b) DRF: {tasks}; CPU {cpu} of {cap[0]}, memory {mem} of {cap[1]} GB ({cap[1] - mem} GB stranded)") if args.json: with open(args.json, "w") as f: json.dump({"summary": summary, "tables": {"steps": steps}}, f)3Run it in the folder you saved it to, with the defaults first. Then change a knob from the table below and run it again.
Terminal uv run stranded-resources-and-drf.py uv run stranded-resources-and-drf.py --help # every knob
Our run
DRF, one task at a time
| Step | Task to | A's dominant share | B's dominant share |
|---|---|---|---|
| 1 | A | 0.222 | 0 |
| 2 | B | 0.222 | 0.333 |
| 3 | A | 0.444 | 0.333 |
| 4 | B | 0.444 | 0.667 |
| 5 | A | 0.667 | 0.667 |
(a) 16 tasks fit; 16 of 32 CPUs stranded
(b) DRF: {'A': 3, 'B': 2}; CPU 9 of 9, memory 14 of 18 GB (4 GB stranded)What we found
(a) 16 tasks fit, stranding 16 of 32 CPUs. (b) DRF gave A 3 tasks and B 2, using 9 of 9 CPUs and 14 of 18 GB, with 4 GB stranded, as expected. The chapter's code, as printed, checks each tenant's usage against the whole machine rather than the two tenants' total, and keeps allocating past capacity; the script checks the total.
Read it
Stranding is a packing problem, not a fairness one: DRF divides fairly what fits, and whatever no task's shape can use is left over. Offering a few task shapes that complement each other (memory-heavy and CPU-heavy) recovers more than any fairness rule.