liveBook · Manning

E-PVM scheduler

As explained in Build and Orchestrator in Go (From Scratch)

An E-PVM scheduler is a scheduling algorithm that combines heterogeneous resource usage into a cost and assigns work to the machine with the lowest resulting cost.

An E-PVM scheduler assigns each task to the candidate machine with the lowest marginal cost. It combines heterogeneous resource usage—primarily CPU and memory—into one cost, favoring balanced cluster load over simple rotation. section 10.8 section 10.8.1

A practical implementation separates scheduling into three responsibilities:

  • Filter candidates whose available disk cannot hold the task’s Docker image. section 10.8.2
  • Score every candidate by estimating the cost increase from adding the task, using CPU load, memory allocation, and task count. section 10.8.1 section 10.8.2
  • Pick the node with the smallest score, then assign the task through the scheduler interface. section 10.8.2 section 1.5.3

Switching the manager to E-PVM

Register an "epvm" case that constructs an Epvm scheduler, then call manager.New(workers, "epvm") instead of selecting "roundrobin". The rest of the manager can use the shared scheduler interface. section 10.10

Listing 10.19 Signature of the E-PVM scheduler’s Score
const (
    // LIEB square ice constant
    // https://en.wikipedia.org/wiki/Lieb%27s_square_ice_constant
    LIEB = 1.53960071783900203869
)
 
func (e *Epvm) Score(t task.Task, nodes []*node.Node) map[string]float64 {
    nodeScores := make(map[string]float64)
    maxJobs := 4.0
 
    for _, node := range nodes {
        cpuUsage := calculateCpuUsage(node)
        cpuLoad := calculateLoad(cpuUsage, math.Pow(2, 0.8))
 
        memoryAllocated := float64(node.Stats.MemUsedKb()) +
         float64(node.MemoryAllocated)
        memoryPercentAllocated := memoryAllocated / float64(node.Memory)
 
        newMemPercent := (calculateLoad(memoryAllocated +
         float64(t.Memory/1000), float64(node.Memory)))
 
        memCost := math.Pow(LIEB, newMemPercent) + math.Pow(LIEB,
         (float64(node.TaskCount+1))/maxJobs) -
         math.Pow(LIEB, memoryPercentAllocated) -
         math.Pow(LIEB, float64(node.TaskCount)/float64(maxJobs))
        cpuCost := math.Pow(LIEB, cpuLoad) +
         math.Pow(LIEB, (float64(node.TaskCount+1))/maxJobs) -
         math.Pow(LIEB, cpuLoad) -
         math.Pow(LIEB, float64(node.TaskCount)/float64(maxJobs))
 
        nodeScores[node.Name] = memCost + cpuCost
            nodeScores[node.Name] = marginalCost
        }
    }
    return nodeScores
}

Inspect the Score signature and implementation skeleton, including node score storage, the maxJobs tuning value, CPU load, and memory-cost inputs. section 10.8.2

How Build and Orchestrator in Go (From Scratch) covers it

How deep it goes
Implements candidate filtering, resource scoring, and minimum-cost selection, while deferring maxJobs tuning to production metrics.
Examples
Uses Go methods for candidate filtering, score calculation, minimum-cost selection, and manager configuration with the "epvm" name.
What it assumes
Assumes a scheduler interface, worker nodes, tasks, and resource statistics; teaches E-PVM after introducing round-robin scheduling.
What you can do after
Build it from scratch by implementing candidate filtering, marginal-cost scoring, minimum selection, and manager integration.
How it connects
Connects scheduler interfaces, round-robin scheduling, CPU utilization, memory utilization, disk capacity, and container orchestration.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Build and Orchestrator in Go (From Scratch). The section links point to the parts of the book it draws on.