Skip to content
About
Main content

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

Under DRF, how much memory is left stranded on the 9-CPU, 18 GB machine?

Run it

  1. 1Install uv once. It fetches Python and the lab's packages for you.

    Terminal
    curl -LsSf https://astral.sh/uv/install.sh | sh
  2. 2Get the script. It is the whole lab: read it, then download it.

    stranded-resources-and-drf.py38 linesDownload
    View 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)
    
  3. 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

DRF, one task at a time
StepTask toA's dominant shareB's dominant share
1A0.2220
2B0.2220.333
3A0.4440.333
4B0.4440.667
5A0.6670.667
Reference runApple M2 · 8 cores · 8 GB
Output · uv run stranded-resources-and-drf.py
(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)
macOS 26.6.2 · Python 3.13.3 · run 2026-10-20

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.