Skip to main content
Version: v1.9 🚧

Queue Policies and Tuning

Introduction​

Koord-Queue orders work at two levels. Across queues, the Priority plugin sorts the queues by Queue.spec.priority, so a queue with a higher priority is served before a queue with a lower one. Within a queue, the ordering and the behaviour under contention are determined by the queue policy, which is Queue.spec.queuePolicy.

This document describes the three policies that can be selected, the exact ordering rules that they apply, the annotations that tune their behaviour, and the settings that the components accept but do not currently evaluate.

Policy Overview​

PolicyImplementationOrdering within the queuePreemptionTuning annotations that the policy evaluates
PriorityShared implementation of Priority and BlockPriority descending, then first attempt timestamp ascendingSupportedwait-for-pods-running, enable-queueunit-preemption, max-depth
BlockShared implementation of Priority and BlockPriority descending, then first attempt timestamp ascendingSupportedwait-for-pods-running, enable-queueunit-preemption, max-depth
IntelligentDual sub-queue implementationHigh priority sub-queue first, each sub-queue ordered by priority then timestampNot supportedpriority-threshold only

The last column omits the common koord-queue/ prefix of the tuning annotations; the full keys are listed under Tuning Annotations.

Two further policy names exist in the code but cannot be used. FIFO is declared as a constant of the Queue API, and Round is accepted by the policy matcher of the ElasticQuotaV2 plugin, yet neither is registered with the queue factory. Selecting one of them makes the queue construction fail, which is reported as a Warning event with the reason AddQueueFail on the Queue, and the queue does not serve any job.

Ordering Rules​

Both levels of ordering are implemented by the Priority plugin.

ComparisonRule
Between two queuesThe queue with the higher spec.priority comes first. An unset priority counts as 0.
Between two QueueUnits of a queueThe unit with the higher spec.priority comes first. When the priorities are equal, the unit whose first scheduling attempt happened earlier comes first. An unset priority counts as 0.

The priority of a QueueUnit is derived from the job by the job extension, which reads the priority class name and the priority value from the pod template of the job, and it is updated when the job changes. It can also be set directly on the QueueUnit.

Priority Policy​

Priority is the default policy. The queue keeps its units in a sorted list and hands them to the scheduler in order. When no unit can be scheduled, the behaviour depends on whether any quota is currently recorded as blocked:

  • If at least one quota is blocked, the blocked bookkeeping is reset and the queue retries immediately.
  • Otherwise the queue waits for an event, with a timer of one minute that wakes it up so that throttling cannot stall it indefinitely.

Units whose quota is not blocked are retried in turn, so a single unit that does not fit does not prevent the units behind it from being considered.

Block Policy​

Block uses the same ordering and the same implementation as Priority, and differs in how the queue reacts to contention. It maintains a per-quota blocked set: when a unit cannot be scheduled and the resource situation did not change during the scheduling cycle, the quota of that unit is marked as blocked, and subsequent scans skip the units of blocked quotas instead of retrying them one by one. A quota leaves the blocked set when a new unit for it is added, or when a unit of that quota is reserved or dequeued.

Three timers govern the behaviour:

TimerDurationPurpose
Wake-up timer while blocked30 secondsRe-evaluates the queue when no event arrived.
Stale state reset3 minutesClears the blocked set when nothing has been scheduled for three minutes, which prevents a queue from hanging on stale bookkeeping.
Same unit throttle5 secondsPrevents the same unit from being picked twice within five seconds.

Block is the conservative choice: it avoids repeated attempts against a quota that is known to be exhausted, at the cost of a coarser reaction to small resource changes.

Intelligent Policy​

Intelligent splits the units of a queue into two sub-queues around a priority threshold:

  • Units with a priority greater than or equal to the threshold are placed in the high priority sub-queue.
  • Units with a priority below the threshold are placed in the low priority sub-queue.

Both sub-queues are ordered by priority and then by timestamp. The high priority sub-queue is served first. The two sub-queues differ in their retry behaviour: when a high priority unit cannot be scheduled, the same unit is retried, which guarantees that a critical job is not bypassed; when a low priority unit cannot be scheduled, the queue advances to the next one, which yields a round-robin behaviour and prevents a single large batch job from starving the rest of the queue.

The threshold defaults to 4 and is configured with the annotation koord-queue/priority-threshold on the Queue.

Intelligent does not implement preemption. A preemption attempt on such a queue is rejected with an error and no victim is marked, see Queue-Level Preemption.

Selecting and Changing a Policy​

For a queue that is created automatically from an ElasticQuota, the policy is taken from the label koord-queue/queue-policy. A value that is not one of Priority, Block, Round or Intelligent is ignored, and the policy defaults to Priority.

apiVersion: scheduling.sigs.k8s.io/v1alpha1
kind: ElasticQuota
metadata:
name: team-a
labels:
koord-queue/queue-policy: Block
spec:
min:
cpu: "4"
max:
cpu: "8"

For a queue that is maintained manually, the policy is set in spec.queuePolicy of the Queue:

apiVersion: scheduling.x-k8s.io/v1alpha1
kind: Queue
metadata:
name: team-a
namespace: koord-queue
annotations:
koord-queue/priority-threshold: "10"
spec:
queuePolicy: Intelligent
priority: 1000

The tuning annotations belong to metadata.annotations. Queue.spec declares admissionChecks, priority, priorityClassName and queuePolicy only, and an annotation that is nested under spec is discarded by the API server without an error.

A policy that is written directly to an automatically created Queue is reverted at the next reconciliation of the ElasticQuota, see ElasticQuota and Queue Mapping.

Policy changes are applied at runtime, without a restart and without losing the units that are waiting:

  • A change between Priority and Block is applied in place, because both policies share one implementation. Only the blocking behaviour switches.
  • A change to or from Intelligent replaces the implementation. The existing units are carried over to the new implementation and re-added, so no unit is dropped and no unit is re-created.

Tuning Annotations​

The following annotations are read from the Queue object and all carry the koord-queue/ prefix. For a queue that is created automatically they can be set on the ElasticQuota instead, from where the reconciliation copies them to the Queue, see ElasticQuota and Queue Mapping.

AnnotationApplies toDefaultEffect
koord-queue/wait-for-pods-runningPriority, Blockabsent, that is disabledKeeps admitted units whose pods are not running yet in the assumed set, so the queue waits for the pods of an admitted job before releasing the next one, and so those units can be considered by preemption.
koord-queue/enable-queueunit-preemptionPriority, Blockabsent, that is disabledEnables the preemption path that runs after the quota filter fails.
koord-queue/max-depthPriority, Block-1, unlimitedLimits how deep the queue scans and schedules. -1 disables the limit.
koord-queue/priority-thresholdIntelligent4Priority threshold that separates the high priority sub-queue from the low priority sub-queue.
koord-queue/queue-items-refresh-intervalAll policies15sRefresh interval of the published queue order in Queue.status.queueItemDetails. The value is a Go duration, for example 30s or 2m.
koord-queue/disable-show-queue-itemsAll policiesabsent, that is publishing enabledStops the periodic task that refreshes Queue.status.queueItemDetails. The value that was published last is left in place. The presence of the key is what matters, not its value.
koord-queue/queue-argsAccepted by all policiesemptyParsed as a YAML map of strings and passed to the queue implementation. The current implementations do not read any argument, so the annotation has no effect.

The Intelligent policy evaluates koord-queue/priority-threshold only. Setting koord-queue/wait-for-pods-running, koord-queue/enable-queueunit-preemption or koord-queue/max-depth on an Intelligent queue has no effect, so a scan depth limit requires Priority or Block.

Annotations are re-read whenever the Queue object changes, which means that tuning takes effect without a restart of the component.

Publishing the Queue Order​

Each queue publishes the order in which it would serve its units in Queue.status.queueItemDetails. The value is a map from a queue kind to an ordered list of items; the key is active for all three policies. Every item carries the namespace, the name, the priority and the one-based position of the unit. Only the units that are waiting in the queue are listed, so an idle queue publishes an empty value. The list is refreshed by a periodic task, every fifteen seconds by default.

$ kubectl -n koord-queue get queue team-a -o jsonpath='{.status.queueItemDetails}' | jq .
{
"active": [
{ "name": "job-a", "namespace": "default", "priority": 100, "position": 1 },
{ "name": "job-b", "namespace": "default", "priority": 10, "position": 2 }
]
}

For a queue with the Intelligent policy, the published list contains the units of the high priority sub-queue first, followed by the units of the low priority sub-queue, with positions numbered continuously across both.

Publication can be turned off with koord-queue/disable-show-queue-items on queues that hold a very large number of units, and its interval can be raised with koord-queue/queue-items-refresh-interval to reduce the write load on the API server. Turning publication off stops the refresh only; it does not clear the field, so the previously published order stays visible until publication resumes.

Settings That Are Accepted but Not Evaluated​

The components accept several settings that no decision currently consults. They are listed here so that they are not mistaken for working knobs.

SettingWhere it is acceptedStatus
--enableParentLimitkoord-queueParsed and logged at start-up. No decision reads it, and the queue method that would apply a parent based limit is a no-op. Parent quotas are enforced through the hierarchical usage check instead, see ElasticQuota and Queue Mapping.
--default-queue-policykoord-queueRegistered as a flag and initialised from the environment variables StrictPriority and StrictConsistency, but its value is never read. The effective default policy comes from the reconciliation of the ElasticQuota and is Priority.
--defaultPreemptiblekoord-queueRegistered as a flag. Victim selection in the queue level preemption path is based on priority and on whether resources can be reclaimed, not on a preemptible attribute, and no preemptible label is read.
--podInitialBackoffSeconds, --podMaxBackoffSecondskoord-queueUsed to populate the argument map of a queue, which the implementations do not read.
koord-queue/queue-argsQueue annotationParsed into the same argument map, with the same outcome.
koord-queue/wait-for-pods-running, koord-queue/enable-queueunit-preemption and koord-queue/max-depth on an Intelligent queueElasticQuota or Queue annotationStored on the Queue, but not read by the Intelligent implementation.

Choosing a Policy​

SituationRecommendation
General purpose queue with mixed prioritiesPriority. It retries units in turn and reacts quickly to freed resources.
Quota is regularly exhausted and repeated attempts against it are wastefulBlock. It stops retrying a quota that is known to be exhausted and reacts on events and timers instead.
A queue holds both critical jobs and many long-running batch jobsIntelligent. Critical jobs at or above the threshold are retried until they fit, while batch jobs below the threshold are served round-robin.
Preemption of admitted jobs is requiredPriority or Block. Intelligent does not implement preemption.
A scan depth limit is requiredPriority or Block, with koord-queue/max-depth.

Troubleshooting​

SymptomCauseAction
AddQueueFail event on a QueueThe policy is not registered, which is the case for FIFO and Round.Use Priority, Block or Intelligent.
The queue serves no job at allThe queue failed to be constructed, or no unit resolves to it.Check the events of the Queue and the QueueNotFound events of the units, see ElasticQuota and Queue Mapping.
A Block queue reacts slowly to freed resourcesBlocked quotas are re-evaluated on events and on the 30 second timer, and stale state is cleared after three minutes.This is the intended behaviour. Use Priority when reaction speed matters more than attempt count.
The threshold of an Intelligent queue does not changeThe annotation was written under a key other than koord-queue/priority-threshold, or was set on the job rather than on the ElasticQuota or the Queue.Set koord-queue/priority-threshold on the ElasticQuota, or on the Queue.
An Intelligent queue never preemptsPreemption is not implemented for that policy.Use Priority or Block for queues that require preemption.
max-depth has no effectThe queue uses the Intelligent policy, which does not read koord-queue/max-depth.Use Priority or Block.
The published order in status.queueItemDetails is stalePublication was disabled, which stops the refresh but keeps the last value, or the refresh interval is large.Remove koord-queue/disable-show-queue-items or lower koord-queue/queue-items-refresh-interval.
The published order in status.queueItemDetails is emptyNo unit is waiting in the queue; dequeued units are not listed.Expected behaviour. Inspect the QueueUnit objects of the queue instead.

Reference​

ConcernLocation in koord-queue
Ordering of queues and of unitspkg/framework/plugins/priority/priority.go
Priority and Block implementation, blocked quota bookkeeping, timerspkg/queue/queuepolicies/schedulingqueuev2/schedulingqueuev2.go
Intelligent implementation, threshold and retry behaviourpkg/queue/queuepolicies/intelligentqueue/intelligentqueue.go, pkg/queue/queuepolicies/intelligentqueue/methods.go
Annotation constantspkg/queue/queuepolicies/schedulingqueuev2/schedulingqueuev2.go, pkg/queue/queuepolicies/basequeue/basequeue.go, pkg/queue/queuepolicies/types.go
Policy registration and constructionpkg/queue/factory.go, pkg/queue/types.go
Publication of the queue orderpkg/queue/types.go, function sync, and the SortedList method of each implementation
Verification suitespkg/test/integration/schedulingqueuev2, pkg/test/integration/intelligentqueue

What's Next​