Hedger Lai created YUNIKORN-3450:
------------------------------------

             Summary: [Core] False preemption shortfall aborts queue preemption 
and starves large tasks despite sufficient guaranteed quota
                 Key: YUNIKORN-3450
                 URL: https://issues.apache.org/jira/browse/YUNIKORN-3450
             Project: Apache YuniKorn
          Issue Type: Bug
    Affects Versions: 1.6.0
            Reporter: Hedger Lai
            Assignee: Hedger Lai


h2. Relationship to YUNIKORN-3449
This issue is a companion/follow-up to YUNIKORN-3449:
* *YUNIKORN-3449* fixes the *Node physical capacity* dimension (where the 
node's existing available capacity was ignored during shortfall verification).
* *This issue* fixes the *Queue quota* dimension (where the ask queue's 
existing remaining guaranteed headroom is ignored during shortfall 
verification).
Together, these two issues resolve the false preemption shortfall defects in 
{{TryPreemption()}}.

h2. 1. The Symptom
In {{Preemptor.TryPreemption()}}, preemption for large tasks *falsely aborts 
with PreemptionShortfall and starves indefinitely*, even when preempting 
available victims has *already freed enough quota to bring the ask queue under 
its Guaranteed limit*.

h2. 2. Root Cause
In {{pkg/scheduler/objects/preemption.go}}:
{code:go}
for k, victimVal := range victimsTotalResource.Resources {
    if needVal, ok := p.ask.GetAllocatedResource().Resources[k]; ok && 
victimVal < needVal {
        hasShortfall = true
        break
    }
}
{code}
This check crudely demands {{victimVal >= needVal}} (victims alone must equal 
the entire ask demand).
It *completely ignores the ask queue's existing remaining guaranteed headroom*.

*Real-World Example (Pure extraVictims Scenario):*
* The target node is completely idle and has sufficient physical space (no node 
victims needed). Preemption is triggered purely to satisfy queue limits via 
{{calculateAdditionalVictims()}}.
* Parent Queue Max = 10. Currently used = 5. *Remaining Headroom = 5*.
* A large Ask needs 10.
* *Net Queue Deficit* = {{5 (used) + 10 (ask) - 10 (max) = 5}}.
* Upstream correctly preempts 1 victim (5 vcores), freeing enough quota so {{0 
(used) + 10 (ask) = 10 <= 10}}.
* But downstream checks {{victimVal (5) < needVal (10)}}, falsely triggers 
{{PreemptionShortfall}}, and *aborts preemption!*

h2. 3. Deterministic Reproduction Unit Test
This bug reproduces deterministically in {{preemption_test.go}} (0.01s):

{code:go}
func TestTryPreemption_QueueResidualShortfallDeficit(t *testing.T) {
        appQueueMapping := NewAppQueueMapping()
        node1 := newNode(nodeID1, map[string]resources.Quantity{"first": 30})
        node2 := newNode(nodeID2, map[string]resources.Quantity{"first": 30})
        iterator := getNodeIteratorFn(node1, node2)
        rootQ, err := createRootQueue(map[string]string{"first": "60"})
        assert.NilError(t, err)

        // Parent Max = 10
        parentQ, err := createManagedQueueGuaranteed(rootQ, "parent", true, 
map[string]string{"first": "10"}, nil, appQueueMapping)
        assert.NilError(t, err)
        _, err = createManagedQueueGuaranteed(parentQ, "child1", false, nil, 
nil, appQueueMapping)
        assert.NilError(t, err)
        childQ2, err := createManagedQueueGuaranteed(parentQ, "child2", false, 
nil, map[string]string{"first": "20"}, appQueueMapping)
        assert.NilError(t, err)
        childQ3, err := createManagedQueueGuaranteed(parentQ, "child3", false, 
nil, nil, appQueueMapping)
        assert.NilError(t, err)

        // Victim alloc4 uses 5 on node2. Parent allocated = 5. Headroom = 10 - 
5 = 5.
        app3 := newApplication(appID3, "default", "root.parent.child3")
        app3.SetQueue(childQ3)
        childQ3.AddApplication(app3)
        appQueueMapping.AddAppQueueMapping(app3.ApplicationID, childQ3)
        ask4 := newAllocationAsk("alloc4", appID3, 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5}))
        assert.NilError(t, app3.AddAllocationAsk(ask4))
        alloc4 := newAllocationWithKey("alloc4", appID3, nodeID2, 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 5}))
        app3.AddAllocation(alloc4)
        assert.Check(t, node2.TryAddAllocation(alloc4), "node2 alloc4 failed")
        assert.NilError(t, 
childQ3.TryIncAllocatedResource(ask4.GetAllocatedResource()))

        // Preemptor ask needs 10. Deficit is 5. Preempting alloc4 (5) 
satisfies queue quota completely (0 + 10 <= 10).
        app2, ask3, err := creatApp2(childQ2, 
map[string]resources.Quantity{"first": 10}, "alloc3", appQueueMapping)
        assert.NilError(t, err)

        headRoom := 
resources.NewResourceFromMap(map[string]resources.Quantity{"first": 20})
        preemptor := NewPreemptor(app2, headRoom, 30*time.Second, ask3, 
iterator(), false)

        feasibleNodes := map[string]int{nodeID2: 1}
        plugin := mock.NewPreemptionPredicatePlugin(nil, feasibleNodes, false, 
false)
        plugins.RegisterSchedulerPlugin(plugin)
        defer plugins.UnregisterSchedulerPlugins()

        result, ok := preemptor.TryPreemption()
        assert.Assert(t, ok, "preemption should succeed: queue deficit is 5, 
victim is 5")
        assert.Assert(t, result != nil, "expected non-nil result")
        assert.Equal(t, "alloc3", result.Request.GetAllocationKey())
}
{code}

h2. 4. Terminal Failure Output (Evidence)
{code:text}
=== RUN   TestTryPreemption_QueueResidualShortfallDeficit
    preemption_test.go:2567: assertion failed: ok is false: preemption should 
succeed: queue deficit is 5, victim is 5
--- FAIL: TestTryPreemption_QueueResidualShortfallDeficit (0.01s)
FAIL
{code}

h2. 5. Proposed Solution
Update the queue preemption shortfall check to evaluate the net residual 
deficit:
Verify whether {{remainingGuaranteed + victimsTotalResource >= ask}}, rather 
than requiring {{victimsTotalResource >= ask}}.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to