Repository: armadaproject/armada
Pull request #3187 (2023-12-11 10:42:12)

Add support for home-away preemption
# Summary

This PR adds initial support for "home-away" preemption, which is a way of reserving nodes for certain classes of jobs (e.g., "whale" jobs, which request an entire node at a time) while still allowing other jobs to be scheduled on these nodes at a lower priority. This allows cluster operators to boost workloads that are not well served by the scheduling algorithm without unnecessarily reducing cluster utilization.

# Implementation

Nodes are reserved for certain classes of jobs by tainting them, which means that jobs need to have the corresponding tolerations in order to be scheduled on these nodes. On `master`, tolerations can be set for individual jobs by populating the `Tolerations` field of `PodRequirements`, and for entire classes of job by populating these fields of `SchedulingConfig`:

https://github.com/armadaproject/armada/blob/7a10e03e6f805c5a9bd782df4375e372685a0999/internal/armada/configuration/types.go#L161-L166

This PR adds a third possibility: the `PriorityClass` type gains the field `AwayNodeTypes`, which represents the sets of nodes that jobs of this priority class can be scheduled on as "away" jobs (i.e., at a lower priority than "home" jobs that the nodes have been reserved for); each set of nodes ("node type") is defined by a set of taints.

When it encounters a job with such a priority class, the scheduler first tries to schedule the job as a home job (i.e., it schedules it like it normally would); if that fails, it then tries to schedule on each away node type described by `AwayNodeTypes`, adding the tolerations corresponding to the taints defining each of these nodes types in turn. Because away jobs are scheduled at a lower priority than home jobs, the scheduler will prefer to schedule home jobs whenever it can; in particular, away jobs are preempted by home jobs through the existing mechanism of urgency-based preemption.

dave-gantenbein: Love the thorough description here! Can we consider adding some of this clarification to the scheduler section of the docs?
zuqq: > Love the thorough description here! Can we consider adding some of this clarification to the scheduler section of the docs?

Noted; I have a couple of follow-up changes coming.

Patch:
diff --git a/internal/armada/configuration/types.go b/internal/armada/configuration/types.go
index f6b6ec98..b62f153d 100644
--- a/internal/armada/configuration/types.go
+++ b/internal/armada/configuration/types.go
@@ -5,7 +5,6 @@ import (
 
 	"github.com/apache/pulsar-client-go/pulsar"
 	"github.com/go-redis/redis"
-	"golang.org/x/exp/slices"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 
@@ -206,6 +205,9 @@ type SchedulingConfig struct {
 	//
 	// Applies only to the new scheduler.
 	IndexedTaints []string
+	// WellKnownNodeTypes defines a set of well-known node types; these are used
+	// to define "home" and "away" nodes for a given priority class.
+	WellKnownNodeTypes []WellKnownNodeType
 	// Default value of GangNodeUniformityLabelAnnotation if none is provided.
 	DefaultGangNodeUniformityLabel string
 	// Kubernetes pods may specify a termination grace period.
@@ -271,6 +273,15 @@ type IndexedResource struct {
 	Resolution resource.Quantity
 }
 
+// A WellKnownNodeType defines a set of nodes; see AwayNodeType.
+type WellKnownNodeType struct {
+	// Name is the unique identifier for this node type.
+	Name string
+	// Taints is the set of taints that characterizes this node type; a node is
+	// part of this node type if and only if it has all of these taints.
+	Taints []v1.Taint
+}
+
 // NewSchedulerConfig stores config for the new Pulsar-based scheduler.
 // This scheduler will eventually replace the current scheduler.
 type NewSchedulerConfig struct {
@@ -308,31 +319,6 @@ type PreemptionConfig struct {
 	PriorityClassNameOverride *string
 }
 
-func (p PreemptionConfig) PriorityByPriorityClassName() map[string]int32 {
-	return PriorityByPriorityClassName(p.PriorityClasses)
-}
-
-func PriorityByPriorityClassName(priorityClasses map[string]types.PriorityClass) map[string]int32 {
-	rv := make(map[string]int32, len(priorityClasses))
-	for name, pc := range priorityClasses {
-		rv[name] = pc.Priority
-	}
-	return rv
-}
-
-func (p PreemptionConfig) AllowedPriorities() []int32 {
-	return AllowedPriorities(p.PriorityClasses)
-}
-
-func AllowedPriorities(priorityClasses map[string]types.PriorityClass) []int32 {
-	rv := make([]int32, 0, len(priorityClasses))
-	for _, v := range priorityClasses {
-		rv = append(rv, v.Priority)
-	}
-	slices.Sort(rv)
-	return slices.Compact(rv)
-}
-
 type LeaseSettings struct {
 	ExpireAfter        time.Duration
 	ExpiryLoopInterval time.Duration
diff --git a/internal/armada/server/lease.go b/internal/armada/server/lease.go
index 27a40e1a..b84f4e45 100644
--- a/internal/armada/server/lease.go
+++ b/internal/armada/server/lease.go
@@ -9,7 +9,7 @@ import (
 	"time"
 
 	"github.com/apache/pulsar-client-go/pulsar"
-	"github.com/gogo/protobuf/types"
+	prototypes "github.com/gogo/protobuf/types"
 	"github.com/hashicorp/go-multierror"
 	pool "github.com/jolestar/go-commons-pool"
 	"github.com/pkg/errors"
@@ -35,6 +35,7 @@ import (
 	armadaresource "github.com/armadaproject/armada/internal/common/resource"
 	"github.com/armadaproject/armada/internal/common/schedulers"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
+	"github.com/armadaproject/armada/internal/common/types"
 	"github.com/armadaproject/armada/internal/common/util"
 	"github.com/armadaproject/armada/internal/scheduler"
 	schedulerconstraints "github.com/armadaproject/armada/internal/scheduler/constraints"
@@ -283,7 +284,7 @@ func (q *AggregatedQueueServer) getJobs(ctx *armadacontext.Context, req *api.Str
 	}
 
 	// Collect all allowed priorities.
-	allowedPriorities := q.schedulingConfig.Preemption.AllowedPriorities()
+	allowedPriorities := types.AllowedPriorities(q.schedulingConfig.Preemption.PriorityClasses)
 	if len(allowedPriorities) == 0 {
 		return nil, errors.WithStack(&armadaerrors.ErrInvalidArgument{
 			Name:    "PriorityClasses",
@@ -324,6 +325,7 @@ func (q *AggregatedQueueServer) getJobs(ctx *armadacontext.Context, req *api.Str
 		q.schedulingConfig.IndexedResources,
 		q.schedulingConfig.IndexedTaints,
 		q.schedulingConfig.IndexedNodeLabels,
+		q.schedulingConfig.WellKnownNodeTypes,
 	)
 	if err != nil {
 		return nil, err
@@ -529,7 +531,7 @@ func (q *AggregatedQueueServer) getJobs(ctx *armadacontext.Context, req *api.Str
 		schedulerobjects.ResourceList{Resources: req.MinimumJobSize},
 		q.schedulingConfig,
 	)
-	sch := scheduler.NewPreemptingQueueScheduler(
+	sch, err := scheduler.NewPreemptingQueueScheduler(
 		sctx,
 		constraints,
 		q.schedulingConfig.Preemption.NodeEvictionProbability,
@@ -543,6 +545,9 @@ func (q *AggregatedQueueServer) getJobs(ctx *armadacontext.Context, req *api.Str
 		jobIdsByGangId,
 		gangIdByJobId,
 	)
+	if err != nil {
+		return nil, err
+	}
 	if q.schedulingConfig.AlwaysAttemptScheduling {
 		sch.SkipUnsuccessfulSchedulingKeyCheck()
 	}
@@ -897,7 +902,7 @@ func (q *AggregatedQueueServer) RenewLease(grpcCtx context.Context, request *api
 	return &api.IdList{Ids: renewed}, e
 }
 
-func (q *AggregatedQueueServer) ReturnLease(grpcCtx context.Context, request *api.ReturnLeaseRequest) (*types.Empty, error) {
+func (q *AggregatedQueueServer) ReturnLease(grpcCtx context.Context, request *api.ReturnLeaseRequest) (*prototypes.Empty, error) {
 	ctx := armadacontext.FromGrpcCtx(grpcCtx)
 	if err := q.authorizer.AuthorizeAction(ctx, permissions.ExecuteJobs); err != nil {
 		return nil, status.Errorf(codes.PermissionDenied, err.Error())
@@ -930,7 +935,7 @@ func (q *AggregatedQueueServer) ReturnLease(grpcCtx context.Context, request *ap
 			return nil, err
 		}
 
-		return &types.Empty{}, nil
+		return &prototypes.Empty{}, nil
 	}
 
 	if request.AvoidNodeLabels != nil && len(request.AvoidNodeLabels.Entries) > 0 {
@@ -948,7 +953,7 @@ func (q *AggregatedQueueServer) ReturnLease(grpcCtx context.Context, request *ap
 		}
 	}
 
-	return &types.Empty{}, nil
+	return &prototypes.Empty{}, nil
 }
 
 func (q *AggregatedQueueServer) addAvoidNodeAffinity(
diff --git a/internal/common/types/scheduling.go b/internal/common/types/scheduling.go
index 52db9227..ff432001 100644
--- a/internal/common/types/scheduling.go
+++ b/internal/common/types/scheduling.go
@@ -1,6 +1,19 @@
 package types
 
-import "golang.org/x/exp/maps"
+import (
+	"golang.org/x/exp/maps"
+	"golang.org/x/exp/slices"
+)
+
+type AwayNodeType struct {
+	// Priority is the priority class priority that the scheduler should use
+	// when scheduling "away" jobs of this priority class on the the node type
+	// referenced by WellKnownNodeTypeName; it overrides the Priority field of
+	// PriorityClass.
+	Priority int32
+	// WellKnownNodeTypeName is the Name of the WellKnownNodeType in question.
+	WellKnownNodeTypeName string
+}
 
 type PriorityClass struct {
 	Priority int32
@@ -12,6 +25,12 @@ type PriorityClass struct {
 	// Per-pool override of MaximumResourceFractionPerQueue.
 	// If missing for a particular pool, MaximumResourceFractionPerQueue is used instead for that pool.
 	MaximumResourceFractionPerQueueByPool map[string]map[string]float64
+	// AwayNodeTypes is the set of node types that jobs of this priority class
+	// can be scheduled on as "away" jobs (i.e., with reduced priority).
+	//
+	// The scheduler first tries to schedule jobs of this priority class as
+	// "home" jobs, and then tries the elements of this slice in order.
+	AwayNodeTypes []AwayNodeType
 }
 
 func (priorityClass PriorityClass) Equal(other PriorityClass) bool {
@@ -34,3 +53,15 @@ func (priorityClass PriorityClass) Equal(other PriorityClass) bool {
 	}
 	return true
 }
+
+func AllowedPriorities(priorityClasses map[string]PriorityClass) []int32 {
+	allowedPriorities := make([]int32, 0, len(priorityClasses))
+	for _, priorityClass := range priorityClasses {
+		allowedPriorities = append(allowedPriorities, priorityClass.Priority)
+		for _, awayNodeType := range priorityClass.AwayNodeTypes {
+			allowedPriorities = append(allowedPriorities, awayNodeType.Priority)
+		}
+	}
+	slices.Sort(allowedPriorities)
+	return slices.Compact(allowedPriorities)
+}
diff --git a/internal/scheduler/constraints/constraints.go b/internal/scheduler/constraints/constraints.go
index b859d71b..45bfd3da 100644
--- a/internal/scheduler/constraints/constraints.go
+++ b/internal/scheduler/constraints/constraints.go
@@ -66,8 +66,7 @@ type SchedulingConstraints struct {
 
 // PriorityClassSchedulingConstraints contains scheduling constraints that apply to jobs of a specific priority class.
 type PriorityClassSchedulingConstraints struct {
-	PriorityClassName     string
-	PriorityClassPriority int32
+	PriorityClassName string
 	// Limits total resources allocated to jobs of this priority class per queue.
 	MaximumResourcesPerQueue schedulerobjects.ResourceList
 }
@@ -87,7 +86,6 @@ func SchedulingConstraintsFromSchedulingConfig(
 		}
 		priorityClassSchedulingConstraintsByPriorityClassName[name] = PriorityClassSchedulingConstraints{
 			PriorityClassName:        name,
-			PriorityClassPriority:    priorityClass.Priority,
 			MaximumResourcesPerQueue: absoluteFromRelativeLimits(totalResources, maximumResourceFractionPerQueue),
 		}
 	}
diff --git a/internal/scheduler/context/context.go b/internal/scheduler/context/context.go
index 5e05dc56..79d67c2a 100644
--- a/internal/scheduler/context/context.go
+++ b/internal/scheduler/context/context.go
@@ -623,8 +623,6 @@ type JobSchedulingContext struct {
 	AdditionalNodeSelectors map[string]string
 	// Tolerations to consider in addition to those included with the PodRequirements.
 	// These are added as part of scheduling to expand the set of nodes a job can be scheduled on.
-	//
-	// These are currently unused.
 	AdditionalTolerations []v1.Toleration
 	// Reason for why the job could not be scheduled.
 	// Empty if the job was scheduled successfully.
@@ -738,12 +736,10 @@ type PodSchedulingContext struct {
 	Created time.Time
 	// ID of the node that the pod was assigned to, or empty.
 	NodeId string
-	// Score indicates how well the pod fits on the selected node.
-	Score int
+	// Priority at which this pod was scheduled.
+	ScheduledAtPriority int32
 	// Maximum priority that this pod preempted other pods at.
 	PreemptedAtPriority int32
-	// Node types on which this pod could be scheduled.
-	MatchingNodeTypes []*schedulerobjects.NodeType
 	// Total number of nodes in the cluster when trying to schedule.
 	NumNodes int
 	// Number of nodes excluded by reason.
diff --git a/internal/scheduler/database/migrations/003_add_runs_scheduled_at_priority.up.sql b/internal/scheduler/database/migrations/003_add_runs_scheduled_at_priority.up.sql
new file mode 100644
index 00000000..deedd72e
--- /dev/null
+++ b/internal/scheduler/database/migrations/003_add_runs_scheduled_at_priority.up.sql
@@ -0,0 +1,1 @@
+ALTER TABLE runs ADD COLUMN scheduled_at_priority int;
diff --git a/internal/scheduler/database/models.go b/internal/scheduler/database/models.go
index a5db3550..5143b04e 100644
--- a/internal/scheduler/database/models.go
+++ b/internal/scheduler/database/models.go
@@ -74,4 +74,5 @@ type Run struct {
 	PendingTimestamp    *time.Time `db:"pending_timestamp"`
 	RunningTimestamp    *time.Time `db:"running_timestamp"`
 	TerminatedTimestamp *time.Time `db:"terminated_timestamp"`
+	ScheduledAtPriority *int32     `db:"scheduled_at_priority"`
 }
diff --git a/internal/scheduler/database/query.sql.go b/internal/scheduler/database/query.sql.go
index 2ef1487e..941e7ba7 100644
--- a/internal/scheduler/database/query.sql.go
+++ b/internal/scheduler/database/query.sql.go
@@ -427,7 +427,7 @@ func (q *Queries) SelectNewJobs(ctx context.Context, arg SelectNewJobsParams) ([
 }
 
 const selectNewRuns = `-- name: SelectNewRuns :many
-SELECT run_id, job_id, created, job_set, executor, node, cancelled, running, succeeded, failed, returned, run_attempted, serial, last_modified, leased_timestamp, pending_timestamp, running_timestamp, terminated_timestamp FROM runs WHERE serial > $1 ORDER BY serial LIMIT $2
+SELECT run_id, job_id, created, job_set, executor, node, cancelled, running, succeeded, failed, returned, run_attempted, serial, last_modified, leased_timestamp, pending_timestamp, running_timestamp, terminated_timestamp, scheduled_at_priority FROM runs WHERE serial > $1 ORDER BY serial LIMIT $2
 `
 
 type SelectNewRunsParams struct {
@@ -463,6 +463,7 @@ func (q *Queries) SelectNewRuns(ctx context.Context, arg SelectNewRunsParams) ([
 			&i.PendingTimestamp,
 			&i.RunningTimestamp,
 			&i.TerminatedTimestamp,
+			&i.ScheduledAtPriority,
 		); err != nil {
 			return nil, err
 		}
@@ -475,7 +476,7 @@ func (q *Queries) SelectNewRuns(ctx context.Context, arg SelectNewRunsParams) ([
 }
 
 const selectNewRunsForJobs = `-- name: SelectNewRunsForJobs :many
-SELECT run_id, job_id, created, job_set, executor, node, cancelled, running, succeeded, failed, returned, run_attempted, serial, last_modified, leased_timestamp, pending_timestamp, running_timestamp, terminated_timestamp FROM runs WHERE serial > $1 AND job_id = ANY($2::text[]) ORDER BY serial
+SELECT run_id, job_id, created, job_set, executor, node, cancelled, running, succeeded, failed, returned, run_attempted, serial, last_modified, leased_timestamp, pending_timestamp, running_timestamp, terminated_timestamp, scheduled_at_priority FROM runs WHERE serial > $1 AND job_id = ANY($2::text[]) ORDER BY serial
 `
 
 type SelectNewRunsForJobsParams struct {
@@ -511,6 +512,7 @@ func (q *Queries) SelectNewRunsForJobs(ctx context.Context, arg SelectNewRunsFor
 			&i.PendingTimestamp,
 			&i.RunningTimestamp,
 			&i.TerminatedTimestamp,
+			&i.ScheduledAtPriority,
 		); err != nil {
 			return nil, err
 		}
diff --git a/internal/scheduler/interfaces/interfaces.go b/internal/scheduler/interfaces/interfaces.go
index de027ecd..74a32c17 100644
--- a/internal/scheduler/interfaces/interfaces.go
+++ b/internal/scheduler/interfaces/interfaces.go
@@ -19,6 +19,7 @@ type LegacySchedulerJob interface {
 	GetAnnotations() map[string]string
 	GetPodRequirements(priorityClasses map[string]types.PriorityClass) *schedulerobjects.PodRequirements
 	GetPriorityClassName() string
+	GetScheduledAtPriority() (int32, bool)
 	GetNodeSelector() map[string]string
 	GetAffinity() *v1.Affinity
 	GetTolerations() []v1.Toleration
@@ -36,6 +37,17 @@ type LegacySchedulerJob interface {
 	SchedulingOrderCompare(other LegacySchedulerJob) int
 }
 
+func PriorityClassFromLegacySchedulerJob(priorityClasses map[string]types.PriorityClass, defaultPriorityClassName string, job LegacySchedulerJob) types.PriorityClass {
+	priorityClassName := job.GetPriorityClassName()
+	if priorityClass, ok := priorityClasses[priorityClassName]; ok {
+		return priorityClass
+	}
+	// We could return (types.PriorityClass{}, false) here, but then callers
+	// might handle this situation in different ways; return the default
+	// priority class in order to enforce uniformity.
+	return priorityClasses[defaultPriorityClassName]
+}
+
 func SchedulingKeyFromLegacySchedulerJob(skg *schedulerobjects.SchedulingKeyGenerator, job LegacySchedulerJob) schedulerobjects.SchedulingKey {
 	return skg.Key(
 		job.GetNodeSelector(),
diff --git a/internal/scheduler/jobdb/job.go b/internal/scheduler/jobdb/job.go
index d6882c47..11927c33 100644
--- a/internal/scheduler/jobdb/job.go
+++ b/internal/scheduler/jobdb/job.go
@@ -252,6 +252,18 @@ func (job *Job) GetPriorityClassName() string {
 	return job.JobSchedulingInfo().PriorityClassName
 }
 
+func (job *Job) GetScheduledAtPriority() (int32, bool) {
+	run := job.LatestRun()
+	if run == nil {
+		return -1, false
+	}
+	scheduledAtPriority := run.ScheduledAtPriority()
+	if scheduledAtPriority == nil {
+		return -1, false
+	}
+	return *scheduledAtPriority, true
+}
+
 // Needed for compatibility with interfaces.LegacySchedulerJob
 func (job *Job) GetNodeSelector() map[string]string {
 	if req := job.PodRequirements(); req != nil {
@@ -399,14 +411,15 @@ func (job *Job) HasRuns() bool {
 }
 
 // WithNewRun creates a copy of the job with a new run on the given executor.
-func (job *Job) WithNewRun(executor string, nodeId, nodeName string) *Job {
+func (job *Job) WithNewRun(executor string, nodeId, nodeName string, scheduledAtPriority int32) *Job {
 	run := &JobRun{
-		id:       uuid.New(),
-		jobId:    job.id,
-		created:  time.Now().UnixNano(),
-		executor: executor,
-		nodeId:   nodeId,
-		nodeName: nodeName,
+		id:                  uuid.New(),
+		jobId:               job.id,
+		created:             time.Now().UnixNano(),
+		executor:            executor,
+		nodeId:              nodeId,
+		nodeName:            nodeName,
+		scheduledAtPriority: &scheduledAtPriority,
 	}
 	return job.WithUpdatedRun(run)
 }
diff --git a/internal/scheduler/jobdb/job_run.go b/internal/scheduler/jobdb/job_run.go
index a9faee39..bbe7b363 100644
--- a/internal/scheduler/jobdb/job_run.go
+++ b/internal/scheduler/jobdb/job_run.go
@@ -20,6 +20,8 @@ type JobRun struct {
 	// The name of the node this run has been leased to.
 	// Identifies the node within the target executor cluster.
 	nodeName string
+	// Priority class priority that this job was scheduled at.
+	scheduledAtPriority *int32
 	// True if the job has been reported as running by the executor.
 	running bool
 	// True if the job has been reported as succeeded by the executor.
@@ -49,8 +51,9 @@ func (run *JobRun) Equal(other *JobRun) bool {
 
 func MinimalRun(id uuid.UUID, creationTime int64) *JobRun {
 	return &JobRun{
-		id:      id,
-		created: creationTime,
+		id:                  id,
+		created:             creationTime,
+		scheduledAtPriority: nil,
 	}
 }
 
@@ -62,6 +65,7 @@ func (jobDb *JobDb) CreateRun(
 	executor string,
 	nodeId string,
 	nodeName string,
+	scheduledAtPriority *int32,
 	running bool,
 	succeeded bool,
 	failed bool,
@@ -70,18 +74,19 @@ func (jobDb *JobDb) CreateRun(
 	runAttempted bool,
 ) *JobRun {
 	return &JobRun{
-		id:           id,
-		jobId:        jobId,
-		created:      creationTime,
-		executor:     jobDb.stringInterner.Intern(executor),
-		nodeId:       jobDb.stringInterner.Intern(nodeId),
-		nodeName:     jobDb.stringInterner.Intern(nodeName),
-		running:      running,
-		succeeded:    succeeded,
-		failed:       failed,
-		cancelled:    cancelled,
-		returned:     returned,
-		runAttempted: runAttempted,
+		id:                  id,
+		jobId:               jobId,
+		created:             creationTime,
+		executor:            jobDb.stringInterner.Intern(executor),
+		nodeId:              jobDb.stringInterner.Intern(nodeId),
+		nodeName:            jobDb.stringInterner.Intern(nodeName),
+		scheduledAtPriority: scheduledAtPriority,
+		running:             running,
+		succeeded:           succeeded,
+		failed:              failed,
+		cancelled:           cancelled,
+		returned:            returned,
+		runAttempted:        runAttempted,
 	}
 }
 
@@ -110,6 +115,10 @@ func (run *JobRun) NodeName() string {
 	return run.nodeName
 }
 
+func (run *JobRun) ScheduledAtPriority() *int32 {
+	return run.scheduledAtPriority
+}
+
 // Succeeded Returns true if the executor has reported the job run as successful
 func (run *JobRun) Succeeded() bool {
 	return run.succeeded
diff --git a/internal/scheduler/jobdb/reconciliation.go b/internal/scheduler/jobdb/reconciliation.go
index 795b192a..084a3843 100644
--- a/internal/scheduler/jobdb/reconciliation.go
+++ b/internal/scheduler/jobdb/reconciliation.go
@@ -227,6 +227,7 @@ func (jobDb *JobDb) schedulerRunFromDatabaseRun(dbRun *database.Run) *JobRun {
 		dbRun.Executor,
 		nodeId,
 		dbRun.Node,
+		dbRun.ScheduledAtPriority,
 		dbRun.Running,
 		dbRun.Succeeded,
 		dbRun.Failed,
diff --git a/internal/scheduler/nodedb/nodedb.go b/internal/scheduler/nodedb/nodedb.go
index 67b8ee3e..22b16a65 100644
--- a/internal/scheduler/nodedb/nodedb.go
+++ b/internal/scheduler/nodedb/nodedb.go
@@ -18,7 +18,6 @@ import (
 	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadaerrors"
 	armadamaps "github.com/armadaproject/armada/internal/common/maps"
-	armadaslices "github.com/armadaproject/armada/internal/common/slices"
 	"github.com/armadaproject/armada/internal/common/types"
 	"github.com/armadaproject/armada/internal/common/util"
 	schedulerconfig "github.com/armadaproject/armada/internal/scheduler/configuration"
@@ -174,7 +173,12 @@ func (nodeDb *NodeDb) CreateAndInsertWithApiJobsWithTxn(txn *memdb.Txn, jobs []*
 		return err
 	}
 	for _, job := range jobs {
-		if err := bindJobToNodeInPlace(nodeDb.priorityClasses, job, entry); err != nil {
+		priority, ok := job.GetScheduledAtPriority()
+		if !ok {
+			priorityClass := interfaces.PriorityClassFromLegacySchedulerJob(nodeDb.priorityClasses, nodeDb.defaultPriorityClass, job)
+			priority = priorityClass.Priority
+		}
+		if err := nodeDb.bindJobToNodeInPlace(entry, job, priority); err != nil {
 			return err
 		}
 	}
@@ -190,7 +194,12 @@ func (nodeDb *NodeDb) CreateAndInsertWithJobDbJobsWithTxn(txn *memdb.Txn, jobs [
 		return err
 	}
 	for _, job := range jobs {
-		if err := bindJobToNodeInPlace(nodeDb.priorityClasses, job, entry); err != nil {
+		priority, ok := job.GetScheduledAtPriority()
+		if !ok {
+			priorityClass := interfaces.PriorityClassFromLegacySchedulerJob(nodeDb.priorityClasses, nodeDb.defaultPriorityClass, job)
+			priority = priorityClass.Priority
+		}
+		if err := nodeDb.bindJobToNodeInPlace(entry, job, priority); err != nil {
 			return err
 		}
 	}
@@ -230,12 +239,11 @@ type NodeDb struct {
 	// Because the number of database indices scales linearly with the number of distinct priorities,
 	// the efficiency of the NodeDb relies on the number of distinct priorities being small.
 	priorityClasses map[string]types.PriorityClass
-	// Prioritiy class priorities in increasing order.
-	priorityClassPriorities []int32
-	// Job priorities supported by the NodeDb. Composed of priority class priorities and nodeDb-internal priorities.
-	// In particular, if a job has priority class priority p, nodeDb tries to schedule that job at priority
-	// nodeDbPriorities[0], ..., nodeDbPriorities[i],
-	// for all i such that nodeDbPriorities[i] <= the priority of the job.
+	// defaultPriorityClass is the name of the default priority class; it is
+	// used for jobs that specify a priority class that does not appear in
+	// priorityClasses, for example because it was deleted.
+	defaultPriorityClass string
+	// Priority class priorities and NodeDb-internal priority, in increasing order.
 	nodeDbPriorities []int32
 	// Resources, e.g., "cpu", "memory", and "nvidia.com/gpu",
 	// for which indexes are created to enable efficient lookup.
@@ -279,36 +287,39 @@ type NodeDb struct {
 	// Set of node types. Populated automatically as nodes are inserted.
 	// Node types are not cleaned up if all nodes of that type are removed from the NodeDb.
 	nodeTypes map[uint64]*schedulerobjects.NodeType
+
+	wellKnownNodeTypes map[string]*configuration.WellKnownNodeType
+
 	// Map from podRequirementsNotMetReason Sum64() to the string representation of that reason.
 	// Used to avoid allocs.
 	podRequirementsNotMetReasonStringCache map[uint64]string
 
 	// If true, use experimental preemption strategy.
 	enableNewPreemptionStrategy bool
+
+	// Map from job ID to the priority class priority at which the job was scheduled.
+	//
+	// As of 30/11/2023, we never remove anything from this map: entries need to
+	// persist for the entire scheduling round (e.g., a job may be scheduled,
+	// then evicted, and then scheduled again during a single scheduling round;
+	// this means that the "evict job from node" method is not allowed to
+	// delete entries from scheduledAtPriorityByJobId in general) and every
+	// scheduling round uses a fresh NodeDb.
+	scheduledAtPriorityByJobId map[string]int32
 }
 
 func NewNodeDb(
 	priorityClasses map[string]types.PriorityClass,
 	maxExtraNodesToConsider uint,
 	indexedResources []configuration.IndexedResource,
-	indexedTaints,
+	indexedTaints []string,
 	indexedNodeLabels []string,
+	wellKnownNodeTypes []configuration.WellKnownNodeType,
 ) (*NodeDb, error) {
-	if len(indexedResources) == 0 {
-		return nil, errors.WithStack(&armadaerrors.ErrInvalidArgument{
-			Name:    "indexedResources",
-			Value:   indexedResources,
-			Message: "there must be at least one index resource",
-		})
-	}
+	nodeDbPriorities := []int32{evictedPriority}
+	nodeDbPriorities = append(nodeDbPriorities, types.AllowedPriorities(priorityClasses)...)
+
 	indexedResourceNames := util.Map(indexedResources, func(v configuration.IndexedResource) string { return v.Name })
-	allowedPriorities := make(map[int32]bool, len(priorityClasses))
-	for _, pc := range priorityClasses {
-		allowedPriorities[pc.Priority] = true
-	}
-	priorityClassPriorities := maps.Keys(allowedPriorities)
-	slices.Sort(priorityClassPriorities)
-	nodeDbPriorities := armadaslices.Concatenate([]int32{evictedPriority}, priorityClassPriorities)
 	schema, indexNameByPriority := nodeDbSchema(nodeDbPriorities, indexedResourceNames)
 	db, err := memdb.NewMemDB(schema)
 	if err != nil {
@@ -340,10 +351,8 @@ func NewNodeDb(
 		}
 		return rv
 	}
-
-	return &NodeDb{
+	nodeDb := NodeDb{
 		priorityClasses:         priorityClasses,
-		priorityClassPriorities: priorityClassPriorities,
 		nodeDbPriorities:        nodeDbPriorities,
 		maxExtraNodesToConsider: maxExtraNodesToConsider,
 		indexedResources:        indexedResourceNames,
@@ -357,12 +366,22 @@ func NewNodeDb(
 		indexedNodeLabels:      mapFromSlice(indexedNodeLabels),
 		indexedNodeLabelValues: indexedNodeLabelValues,
 		nodeTypes:              make(map[uint64]*schedulerobjects.NodeType),
+		wellKnownNodeTypes:     make(map[string]*configuration.WellKnownNodeType),
 		numNodesByNodeType:     make(map[uint64]int),
 		totalResources:         schedulerobjects.ResourceList{Resources: make(map[string]resource.Quantity)},
 		db:                     db,
 		// Set the initial capacity (somewhat arbitrarily) to 128 reasons.
 		podRequirementsNotMetReasonStringCache: make(map[uint64]string, 128),
-	}, nil
+
+		scheduledAtPriorityByJobId: make(map[string]int32),
+	}
+
+	for _, wellKnownNodeType := range wellKnownNodeTypes {
+		wellKnownNodeType := wellKnownNodeType
+		nodeDb.wellKnownNodeTypes[wellKnownNodeType.Name] = &wellKnownNodeType
+	}
+
+	return &nodeDb, nil
 }
 
 // Reset clears out data specific to one scheduling round to prepare for a new scheduling round.
@@ -387,10 +406,15 @@ func (nodeDb *NodeDb) EnableNewPreemptionStrategy() {
 	nodeDb.enableNewPreemptionStrategy = true
 }
 
+func (nodeDb *NodeDb) GetScheduledAtPriority(jobId string) (int32, bool) {
+	priority, ok := nodeDb.scheduledAtPriorityByJobId[jobId]
+	return priority, ok
+}
+
 func (nodeDb *NodeDb) String() string {
 	var sb strings.Builder
 	w := tabwriter.NewWriter(&sb, 1, 1, 1, ' ', 0)
-	fmt.Fprintf(w, "Priorities:\t%v\n", configuration.AllowedPriorities(nodeDb.priorityClasses))
+	fmt.Fprintf(w, "Priorities:\t%v\n", nodeDb.nodeDbPriorities)
 	fmt.Fprintf(w, "Indexed resources:\t%v\n", nodeDb.indexedResources)
 	fmt.Fprintf(w, "Indexed taints:\t%v\n", maps.Keys(nodeDb.indexedTaints))
 	fmt.Fprintf(w, "Indexed node labels:\t%v\n", maps.Keys(nodeDb.indexedNodeLabels))
@@ -534,7 +558,7 @@ func (nodeDb *NodeDb) ScheduleManyWithTxn(txn *memdb.Txn, jctxs []*schedulercont
 		}
 
 		// If we found a node for this pod, bind it and continue to the next pod.
-		if node, err := bindJobToNode(nodeDb.priorityClasses, jctx.Job, node); err != nil {
+		if node, err := nodeDb.bindJobToNode(node, jctx.Job, jctx.PodSchedulingContext.ScheduledAtPriority); err != nil {
 			return false, err
 		} else {
 			if err := nodeDb.UpsertWithTxn(txn, node); err != nil {
@@ -571,19 +595,14 @@ func deleteEvictedJobSchedulingContextIfExistsWithTxn(txn *memdb.Txn, jobId stri
 func (nodeDb *NodeDb) SelectNodeForJobWithTxn(txn *memdb.Txn, jctx *schedulercontext.JobSchedulingContext) (*Node, error) {
 	req := jctx.PodRequirements
 
-	// Collect all node types that could potentially schedule the pod.
-	matchingNodeTypes, numExcludedNodesByReason, err := nodeDb.NodeTypesMatchingJob(jctx)
-	if err != nil {
-		return nil, err
-	}
+	priorityClass := interfaces.PriorityClassFromLegacySchedulerJob(nodeDb.priorityClasses, nodeDb.defaultPriorityClass, jctx.Job)
 
 	pctx := &schedulercontext.PodSchedulingContext{
-		Created:             time.Now(),
-		PreemptedAtPriority: MinPriority,
-		MatchingNodeTypes:   matchingNodeTypes,
-		NumNodes:            nodeDb.numNodes,
-		// TODO: This clone looks unnecessary.
-		NumExcludedNodesByReason: maps.Clone(numExcludedNodesByReason),
+		Created:                  time.Now(),
+		ScheduledAtPriority:      -1,
+		PreemptedAtPriority:      MinPriority,
+		NumNodes:                 nodeDb.numNodes,
+		NumExcludedNodesByReason: make(map[string]int),
 	}
 	jctx.PodSchedulingContext = pctx
 
@@ -607,7 +626,15 @@ func (nodeDb *NodeDb) SelectNodeForJobWithTxn(txn *memdb.Txn, jctx *schedulercon
 		if it, err := txn.Get("nodes", "id", nodeId); err != nil {
 			return nil, errors.WithStack(err)
 		} else {
-			if node, err := nodeDb.selectNodeForPodWithIt(it, jctx, req.Priority, true); err != nil {
+			priority, ok := nodeDb.GetScheduledAtPriority(jctx.JobId)
+			if !ok {
+				// We only get here if the node ID label was set by the user
+				// (instead of the scheduler); home-away preemption is ignored
+				// in that case.
+				priority = req.Priority
+			}
+			pctx.ScheduledAtPriority = priority
+			if node, err := nodeDb.selectNodeForPodWithItAtPriority(it, jctx, priority, true); err != nil {
 				return nil, err
 			} else {
 				return node, nil
@@ -615,9 +642,70 @@ func (nodeDb *NodeDb) SelectNodeForJobWithTxn(txn *memdb.Txn, jctx *schedulercon
 		}
 	}
 
+	node, err := nodeDb.selectNodeForJobWithTxnAtPriority(txn, jctx, req.Priority)
+	if err != nil {
+		return nil, err
+	}
+	if node != nil {
+		return node, nil
+	}
+
+	for _, awayNodeType := range priorityClass.AwayNodeTypes {
+		node, err := nodeDb.selectNodeForJobWithTxnAndAwayNodeType(txn, jctx, awayNodeType)
+		if err != nil {
+			return nil, err
+		}
+		if node != nil {
+			return node, nil
+		}
+	}
+
+	return nil, nil
+}
+
+func (nodeDb *NodeDb) selectNodeForJobWithTxnAndAwayNodeType(
+	txn *memdb.Txn,
+	jctx *schedulercontext.JobSchedulingContext,
+	awayNodeType types.AwayNodeType,
+) (*Node, error) {
+	// Save the number of additional tolerations that the job originally had; we
+	// use this value to restore the slice of additional toleration at the end
+	// of each loop iteration.
+	numAdditionalTolerations := len(jctx.AdditionalTolerations)
+	defer func() {
+		jctx.AdditionalTolerations = jctx.AdditionalTolerations[:numAdditionalTolerations]
+	}()
+
+	wellKnownNodeType, ok := nodeDb.wellKnownNodeTypes[awayNodeType.WellKnownNodeTypeName]
+	if !ok {
+		return nil, fmt.Errorf("unknown well-known node type %s; must be in %v", awayNodeType.WellKnownNodeTypeName, nodeDb.wellKnownNodeTypes)
+	}
+
+	for _, taint := range wellKnownNodeType.Taints {
+		jctx.AdditionalTolerations = append(jctx.AdditionalTolerations, v1.Toleration{Key: taint.Key, Value: taint.Value, Effect: taint.Effect})
+	}
+
+	return nodeDb.selectNodeForJobWithTxnAtPriority(txn, jctx, awayNodeType.Priority)
+}
+
+func (nodeDb *NodeDb) selectNodeForJobWithTxnAtPriority(
+	txn *memdb.Txn,
+	jctx *schedulercontext.JobSchedulingContext,
+	priority int32,
+) (*Node, error) {
+	req := jctx.PodRequirements
+
+	matchingNodeTypeIds, numExcludedNodesByReason, err := nodeDb.NodeTypesMatchingJob(jctx)
+	if err != nil {
+		return nil, err
+	}
+
+	pctx := jctx.PodSchedulingContext
+	pctx.ScheduledAtPriority = priority
+
 	// Try scheduling at evictedPriority. If this succeeds, no preemption is necessary.
 	pctx.NumExcludedNodesByReason = maps.Clone(numExcludedNodesByReason)
-	if node, err := nodeDb.selectNodeForPodAtPriority(txn, jctx, evictedPriority, jctx.PodRequirements); err != nil {
+	if node, err := nodeDb.selectNodeForPodAtPriority(txn, jctx, matchingNodeTypeIds, evictedPriority); err != nil {
 		return nil, err
 	} else if err := assertPodSchedulingContextNode(pctx, node); err != nil {
 		return nil, err
@@ -628,7 +716,7 @@ func (nodeDb *NodeDb) SelectNodeForJobWithTxn(txn *memdb.Txn, jctx *schedulercon
 	// Try scheduling at the job priority. If this fails, scheduling is impossible and we return.
 	// This is an optimisation to avoid looking for preemption targets for unschedulable jobs.
 	pctx.NumExcludedNodesByReason = maps.Clone(numExcludedNodesByReason)
-	if node, err := nodeDb.selectNodeForPodAtPriority(txn, jctx, jctx.PodRequirements.Priority, jctx.PodRequirements); err != nil {
+	if node, err := nodeDb.selectNodeForPodAtPriority(txn, jctx, matchingNodeTypeIds, req.Priority); err != nil {
 		return nil, err
 	} else if err := assertPodSchedulingContextNode(pctx, node); err != nil {
 		return nil, err
@@ -636,7 +724,6 @@ func (nodeDb *NodeDb) SelectNodeForJobWithTxn(txn *memdb.Txn, jctx *schedulercon
 		return nil, nil
 	}
 	pctx.NodeId = ""
-	pctx.Score = 0
 	pctx.PreemptedAtPriority = MinPriority
 
 	// Schedule by preventing evicted jobs from being re-scheduled.
@@ -651,12 +738,11 @@ func (nodeDb *NodeDb) SelectNodeForJobWithTxn(txn *memdb.Txn, jctx *schedulercon
 		}
 	}
 	pctx.NodeId = ""
-	pctx.Score = 0
 	pctx.PreemptedAtPriority = MinPriority
 
 	// Schedule by kicking off jobs currently bound to a node.
 	// This method does not respect fairness when choosing on which node to schedule the job.
-	if node, err := nodeDb.selectNodeForJobWithUrgencyPreemption(txn, jctx); err != nil {
+	if node, err := nodeDb.selectNodeForJobWithUrgencyPreemption(txn, jctx, matchingNodeTypeIds); err != nil {
 		return nil, err
 	} else if err := assertPodSchedulingContextNode(pctx, node); err != nil {
 		return nil, err
@@ -684,12 +770,17 @@ func assertPodSchedulingContextNode(pctx *schedulercontext.PodSchedulingContext,
 func (nodeDb *NodeDb) selectNodeForJobWithUrgencyPreemption(
 	txn *memdb.Txn,
 	jctx *schedulercontext.JobSchedulingContext,
+	matchingNodeTypeIds []uint64,
 ) (*Node, error) {
-	pctx := jctx.PodSchedulingContext
 	req := jctx.PodRequirements
+	pctx := jctx.PodSchedulingContext
 	numExcludedNodesByReason := pctx.NumExcludedNodesByReason
-	// TODO: This doesn't need to include the evictedPriority now.
-	for _, priority := range nodeDb.priorityClassPriorities {
+	for _, priority := range nodeDb.nodeDbPriorities {
+		if priority == evictedPriority {
+			// We already tried scheduling at evictedPriority above.
+			continue
+		}
+
 		if priority > req.Priority {
 			break
 		}
@@ -699,7 +790,7 @@ func (nodeDb *NodeDb) selectNodeForJobWithUrgencyPreemption(
 		pctx.NumExcludedNodesByReason = maps.Clone(numExcludedNodesByReason)
 
 		// Try to find a node at this priority.
-		if node, err := nodeDb.selectNodeForPodAtPriority(txn, jctx, priority, req); err != nil {
+		if node, err := nodeDb.selectNodeForPodAtPriority(txn, jctx, matchingNodeTypeIds, priority); err != nil {
 			return nil, err
 		} else if err := assertPodSchedulingContextNode(pctx, node); err != nil {
 			return nil, err
@@ -713,13 +804,10 @@ func (nodeDb *NodeDb) selectNodeForJobWithUrgencyPreemption(
 func (nodeDb *NodeDb) selectNodeForPodAtPriority(
 	txn *memdb.Txn,
 	jctx *schedulercontext.JobSchedulingContext,
+	matchingNodeTypeIds []uint64,
 	priority int32,
-	req *schedulerobjects.PodRequirements,
 ) (*Node, error) {
-	nodeTypeIds := make([]uint64, len(jctx.PodSchedulingContext.MatchingNodeTypes))
-	for i, nodeType := range jctx.PodSchedulingContext.MatchingNodeTypes {
-		nodeTypeIds[i] = nodeType.Id
-	}
+	req := jctx.PodRequirements
 
 	indexResourceRequests := make([]resource.Quantity, len(nodeDb.indexedResources))
 	for i, t := range nodeDb.indexedResources {
@@ -731,7 +819,7 @@ func (nodeDb *NodeDb) selectNodeForPodAtPriority(
 	}
 	it, err := NewNodeTypesIterator(
 		txn,
-		nodeTypeIds,
+		matchingNodeTypeIds,
 		indexName,
 		priority,
 		nodeDb.indexedResources,
@@ -742,7 +830,7 @@ func (nodeDb *NodeDb) selectNodeForPodAtPriority(
 		return nil, err
 	}
 
-	if node, err := nodeDb.selectNodeForPodWithIt(it, jctx, priority, false); err != nil {
+	if node, err := nodeDb.selectNodeForPodWithItAtPriority(it, jctx, priority, false); err != nil {
 		return nil, err
 	} else if node != nil {
 		return node, nil
@@ -751,7 +839,7 @@ func (nodeDb *NodeDb) selectNodeForPodAtPriority(
 	return nil, nil
 }
 
-func (nodeDb *NodeDb) selectNodeForPodWithIt(
+func (nodeDb *NodeDb) selectNodeForPodWithItAtPriority(
 	it memdb.ResultIterator,
 	jctx *schedulercontext.JobSchedulingContext,
 	priority int32,
@@ -802,7 +890,6 @@ func (nodeDb *NodeDb) selectNodeForPodWithIt(
 
 	if selectedNode != nil {
 		jctx.PodSchedulingContext.NodeId = selectedNode.Id
-		jctx.PodSchedulingContext.Score = selectedNodeScore
 		jctx.PodSchedulingContext.PreemptedAtPriority = priority
 	}
 	return selectedNode, nil
@@ -815,6 +902,7 @@ func (nodeDb *NodeDb) selectNodeForPodWithIt(
 // from being re-scheduled the jobs that would be scheduled last.
 func (nodeDb *NodeDb) selectNodeForJobWithFairPreemption(txn *memdb.Txn, jctx *schedulercontext.JobSchedulingContext) (*Node, error) {
 	pctx := jctx.PodSchedulingContext
+
 	var selectedNode *Node
 	nodesById := make(map[string]*Node)
 	evictedJobSchedulingContextsByNodeId := make(map[string][]*EvictedJobSchedulingContext)
@@ -837,7 +925,7 @@ func (nodeDb *NodeDb) selectNodeForJobWithFairPreemption(txn *memdb.Txn, jctx *s
 				return nil, errors.WithStack(err)
 			}
 		}
-		node, err = UnbindJobFromNode(nodeDb.priorityClasses, evictedJctx.Job, node)
+		node, err = nodeDb.UnbindJobFromNode(nodeDb.priorityClasses, evictedJctx.Job, node)
 		if err != nil {
 			return nil, err
 		}
@@ -876,16 +964,16 @@ func (nodeDb *NodeDb) selectNodeForJobWithFairPreemption(txn *memdb.Txn, jctx *s
 }
 
 // bindJobToNode returns a copy of node with job bound to it.
-func bindJobToNode(priorityClasses map[string]types.PriorityClass, job interfaces.LegacySchedulerJob, node *Node) (*Node, error) {
+func (nodeDb *NodeDb) bindJobToNode(node *Node, job interfaces.LegacySchedulerJob, priority int32) (*Node, error) {
 	node = node.UnsafeCopy()
-	if err := bindJobToNodeInPlace(priorityClasses, job, node); err != nil {
+	if err := nodeDb.bindJobToNodeInPlace(node, job, priority); err != nil {
 		return nil, err
 	}
 	return node, nil
 }
 
 // bindJobToNodeInPlace is like bindJobToNode, but doesn't make a copy of node.
-func bindJobToNodeInPlace(priorityClasses map[string]types.PriorityClass, job interfaces.LegacySchedulerJob, node *Node) error {
+func (nodeDb *NodeDb) bindJobToNodeInPlace(node *Node, job interfaces.LegacySchedulerJob, priority int32) error {
 	jobId := job.GetId()
 	requests := job.GetResourceRequirements().Requests
 
@@ -913,12 +1001,13 @@ func bindJobToNodeInPlace(priorityClasses map[string]types.PriorityClass, job in
 	}
 
 	allocatable := node.AllocatableByPriority
-	priority := priorityClasses[job.GetPriorityClassName()].Priority
 	allocatable.MarkAllocatedV1ResourceList(priority, requests)
 	if isEvicted {
 		allocatable.MarkAllocatableV1ResourceList(evictedPriority, requests)
 	}
 
+	nodeDb.scheduledAtPriorityByJobId[jobId] = priority
+
 	return nil
 }
 
@@ -931,7 +1020,7 @@ func bindJobToNodeInPlace(priorityClasses map[string]types.PriorityClass, job in
 //   - Within AllocatableByPriorityAndResource, the resources allocated to these jobs are moved from
 //     the jobs' priorities to evictedPriority; they are not subtracted from AllocatedByJobId and
 //     AllocatedByQueue.
-func EvictJobsFromNode(
+func (nodeDb *NodeDb) EvictJobsFromNode(
 	priorityClasses map[string]types.PriorityClass,
 	jobFilter func(interfaces.LegacySchedulerJob) bool,
 	jobs []interfaces.LegacySchedulerJob,
@@ -944,7 +1033,7 @@ func EvictJobsFromNode(
 			continue
 		}
 		evicted = append(evicted, job)
-		if err := evictJobFromNodeInPlace(priorityClasses, job, node); err != nil {
+		if err := nodeDb.evictJobFromNodeInPlace(priorityClasses, job, node); err != nil {
 			return nil, nil, err
 		}
 	}
@@ -952,7 +1041,7 @@ func EvictJobsFromNode(
 }
 
 // evictJobFromNodeInPlace is the in-place operation backing EvictJobsFromNode.
-func evictJobFromNodeInPlace(priorityClasses map[string]types.PriorityClass, job interfaces.LegacySchedulerJob, node *Node) error {
+func (nodeDb *NodeDb) evictJobFromNodeInPlace(priorityClasses map[string]types.PriorityClass, job interfaces.LegacySchedulerJob, node *Node) error {
 	jobId := job.GetId()
 	if _, ok := node.AllocatedByJobId[jobId]; !ok {
 		return errors.Errorf("job %s has no resources allocated on node %s", jobId, node.Id)
@@ -972,7 +1061,10 @@ func evictJobFromNodeInPlace(priorityClasses map[string]types.PriorityClass, job
 	node.EvictedJobRunIds[jobId] = true
 
 	allocatable := node.AllocatableByPriority
-	priority := priorityClasses[job.GetPriorityClassName()].Priority
+	priority, ok := nodeDb.GetScheduledAtPriority(jobId)
+	if !ok {
+		return errors.Errorf("job %s not mapped to a priority", jobId)
+	}
 	requests := job.GetResourceRequirements().Requests
 	allocatable.MarkAllocatableV1ResourceList(priority, requests)
 	allocatable.MarkAllocatedV1ResourceList(evictedPriority, requests)
@@ -981,10 +1073,10 @@ func evictJobFromNodeInPlace(priorityClasses map[string]types.PriorityClass, job
 }
 
 // UnbindJobsFromNode returns a node with all elements of jobs unbound from it.
-func UnbindJobsFromNode(priorityClasses map[string]types.PriorityClass, jobs []interfaces.LegacySchedulerJob, node *Node) (*Node, error) {
+func (nodeDb *NodeDb) UnbindJobsFromNode(priorityClasses map[string]types.PriorityClass, jobs []interfaces.LegacySchedulerJob, node *Node) (*Node, error) {
 	node = node.UnsafeCopy()
 	for _, job := range jobs {
-		if err := unbindJobFromNodeInPlace(priorityClasses, job, node); err != nil {
+		if err := nodeDb.unbindJobFromNodeInPlace(priorityClasses, job, node); err != nil {
 			return nil, err
 		}
 	}
@@ -992,16 +1084,16 @@ func UnbindJobsFromNode(priorityClasses map[string]types.PriorityClass, jobs []i
 }
 
 // UnbindJobFromNode returns a copy of node with job unbound from it.
-func UnbindJobFromNode(priorityClasses map[string]types.PriorityClass, job interfaces.LegacySchedulerJob, node *Node) (*Node, error) {
+func (nodeDb *NodeDb) UnbindJobFromNode(priorityClasses map[string]types.PriorityClass, job interfaces.LegacySchedulerJob, node *Node) (*Node, error) {
 	node = node.UnsafeCopy()
-	if err := unbindJobFromNodeInPlace(priorityClasses, job, node); err != nil {
+	if err := nodeDb.unbindJobFromNodeInPlace(priorityClasses, job, node); err != nil {
 		return nil, err
 	}
 	return node, nil
 }
 
 // unbindPodFromNodeInPlace is like UnbindJobFromNode, but doesn't make a copy of node.
-func unbindJobFromNodeInPlace(priorityClasses map[string]types.PriorityClass, job interfaces.LegacySchedulerJob, node *Node) error {
+func (nodeDb *NodeDb) unbindJobFromNodeInPlace(priorityClasses map[string]types.PriorityClass, job interfaces.LegacySchedulerJob, node *Node) error {
 	jobId := job.GetId()
 	requests := job.GetResourceRequirements().Requests
 
@@ -1026,9 +1118,15 @@ func unbindJobFromNodeInPlace(priorityClasses map[string]types.PriorityClass, jo
 	}
 
 	allocatable := node.AllocatableByPriority
-	priority := priorityClasses[job.GetPriorityClassName()].Priority
+	var priority int32
 	if isEvicted {
 		priority = evictedPriority
+	} else {
+		var ok bool
+		priority, ok = nodeDb.GetScheduledAtPriority(jobId)
+		if !ok {
+			return errors.Errorf("job %s not mapped to a priority", jobId)
+		}
 	}
 	allocatable.MarkAllocatableV1ResourceList(priority, requests)
 
@@ -1037,13 +1135,13 @@ func unbindJobFromNodeInPlace(priorityClasses map[string]types.PriorityClass, jo
 
 // NodeTypesMatchingJob returns a slice with all node types a pod could be scheduled on.
 // It also returns the number of nodes excluded by reason for exclusion.
-func (nodeDb *NodeDb) NodeTypesMatchingJob(jctx *schedulercontext.JobSchedulingContext) ([]*schedulerobjects.NodeType, map[string]int, error) {
-	selectedNodeTypes := make([]*schedulerobjects.NodeType, 0)
+func (nodeDb *NodeDb) NodeTypesMatchingJob(jctx *schedulercontext.JobSchedulingContext) ([]uint64, map[string]int, error) {
+	var matchingNodeTypeIds []uint64
 	numExcludedNodesByReason := make(map[string]int)
 	for _, nodeType := range nodeDb.nodeTypes {
 		matches, reason := NodeTypeJobRequirementsMet(nodeType, jctx)
 		if matches {
-			selectedNodeTypes = append(selectedNodeTypes, nodeType)
+			matchingNodeTypeIds = append(matchingNodeTypeIds, nodeType.Id)
 		} else if reason != nil {
 			s := nodeDb.stringFromPodRequirementsNotMetReason(reason)
 			numExcludedNodesByReason[s] += nodeDb.numNodesByNodeType[nodeType.Id]
@@ -1051,7 +1149,7 @@ func (nodeDb *NodeDb) NodeTypesMatchingJob(jctx *schedulercontext.JobSchedulingC
 			numExcludedNodesByReason[PodRequirementsNotMetReasonUnknown] += nodeDb.numNodesByNodeType[nodeType.Id]
 		}
 	}
-	return selectedNodeTypes, numExcludedNodesByReason, nil
+	return matchingNodeTypeIds, numExcludedNodesByReason, nil
 }
 
 func (nodeDb *NodeDb) UpsertMany(nodes []*Node) error {
diff --git a/internal/scheduler/pool_assigner.go b/internal/scheduler/pool_assigner.go
index e2507083..18c42642 100644
--- a/internal/scheduler/pool_assigner.go
+++ b/internal/scheduler/pool_assigner.go
@@ -39,6 +39,7 @@ type DefaultPoolAssigner struct {
 	indexedResources       []configuration.IndexedResource
 	indexedTaints          []string
 	indexedNodeLabels      []string
+	wellKnownNodeTypes     []configuration.WellKnownNodeType
 	poolByExecutorId       map[string]string
 	executorsByPool        map[string][]*executor
 	executorRepository     database.ExecutorRepository
@@ -60,9 +61,10 @@ func NewPoolAssigner(executorTimeout time.Duration,
 		priorityClasses:        schedulingConfig.Preemption.PriorityClasses,
 		executorsByPool:        map[string][]*executor{},
 		poolByExecutorId:       map[string]string{},
-		priorities:             schedulingConfig.Preemption.AllowedPriorities(),
+		priorities:             types.AllowedPriorities(schedulingConfig.Preemption.PriorityClasses),
 		indexedResources:       schedulingConfig.IndexedResources,
 		indexedTaints:          schedulingConfig.IndexedTaints,
+		wellKnownNodeTypes:     schedulingConfig.WellKnownNodeTypes,
 		indexedNodeLabels:      schedulingConfig.IndexedNodeLabels,
 		executorRepository:     executorRepository,
 		schedulingKeyGenerator: schedulerobjects.NewSchedulingKeyGenerator(),
@@ -157,6 +159,7 @@ func (p *DefaultPoolAssigner) constructNodeDb(nodes []*schedulerobjects.Node) (*
 		p.indexedResources,
 		p.indexedTaints,
 		p.indexedNodeLabels,
+		p.wellKnownNodeTypes,
 	)
 	if err != nil {
 		return nil, err
diff --git a/internal/scheduler/preempting_queue_scheduler.go b/internal/scheduler/preempting_queue_scheduler.go
index 9457a215..c7a56ac1 100644
--- a/internal/scheduler/preempting_queue_scheduler.go
+++ b/internal/scheduler/preempting_queue_scheduler.go
@@ -1,6 +1,7 @@
 package scheduler
 
 import (
+	"fmt"
 	"math/rand"
 	"reflect"
 	"time"
@@ -12,6 +13,7 @@ import (
 	"k8s.io/apimachinery/pkg/api/resource"
 
 	"github.com/armadaproject/armada/internal/common/armadacontext"
+	"github.com/armadaproject/armada/internal/common/armadaerrors"
 	armadamaps "github.com/armadaproject/armada/internal/common/maps"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
 	"github.com/armadaproject/armada/internal/common/types"
@@ -62,7 +64,16 @@ func NewPreemptingQueueScheduler(
 	initialNodeIdByJobId map[string]string,
 	initialJobIdsByGangId map[string]map[string]bool,
 	initialGangIdByJobId map[string]string,
-) *PreemptingQueueScheduler {
+) (*PreemptingQueueScheduler, error) {
+	for priorityClassName, priorityClass := range sctx.PriorityClasses {
+		if len(priorityClass.AwayNodeTypes) > 0 && !priorityClass.Preemptible {
+			return nil, errors.WithStack(&armadaerrors.ErrInvalidArgument{
+				Name:    "priorityClasses",
+				Value:   sctx.PriorityClasses,
+				Message: fmt.Sprintf("only preemptible priority classes can have away node types; priority class %s violates this invariant", priorityClassName),
+			})
+		}
+	}
 	if initialNodeIdByJobId == nil {
 		initialNodeIdByJobId = make(map[string]string)
 	}
@@ -76,7 +87,7 @@ func NewPreemptingQueueScheduler(
 	for gangId, jobIds := range initialJobIdsByGangId {
 		initialJobIdsByGangId[gangId] = maps.Clone(jobIds)
 	}
-	return &PreemptingQueueScheduler{
+	scheduler := PreemptingQueueScheduler{
 		schedulingContext:                       sctx,
 		constraints:                             constraints,
 		nodeEvictionProbability:                 nodeEvictionProbability,
@@ -88,6 +99,7 @@ func NewPreemptingQueueScheduler(
 		jobIdsByGangId:                          initialJobIdsByGangId,
 		gangIdByJobId:                           maps.Clone(initialGangIdByJobId),
 	}
+	return &scheduler, nil
 }
 
 func (sch *PreemptingQueueScheduler) EnableAssertions() {
@@ -124,6 +136,7 @@ func (sch *PreemptingQueueScheduler) Schedule(ctx *armadacontext.Context) (*Sche
 		armadacontext.WithLogField(ctx, "stage", "evict for resource balancing"),
 		NewNodeEvictor(
 			sch.jobRepo,
+			sch.nodeDb,
 			sch.schedulingContext.PriorityClasses,
 			sch.nodeEvictionProbability,
 			func(ctx *armadacontext.Context, job interfaces.LegacySchedulerJob) bool {
@@ -143,10 +156,8 @@ func (sch *PreemptingQueueScheduler) Schedule(ctx *armadacontext.Context) (*Sche
 						return false
 					}
 				}
-				if priorityClass, ok := sch.schedulingContext.PriorityClasses[job.GetPriorityClassName()]; ok {
-					return priorityClass.Preemptible
-				}
-				return false
+				priorityClass := interfaces.PriorityClassFromLegacySchedulerJob(sch.schedulingContext.PriorityClasses, sch.schedulingContext.DefaultPriorityClass, job)
+				return priorityClass.Preemptible
 			},
 			nil,
 		),
@@ -182,6 +193,7 @@ func (sch *PreemptingQueueScheduler) Schedule(ctx *armadacontext.Context) (*Sche
 		armadacontext.WithLogField(ctx, "stage", "evict oversubscribed"),
 		NewOversubscribedEvictor(
 			sch.jobRepo,
+			sch.nodeDb,
 			sch.schedulingContext.PriorityClasses,
 			sch.schedulingContext.DefaultPriorityClass,
 			sch.nodeOversubscriptionEvictionProbability,
@@ -275,11 +287,7 @@ func (sch *PreemptingQueueScheduler) evict(ctx *armadacontext.Context, evictor *
 	defer txn.Abort()
 
 	// Evict using the provided evictor.
-	it, err := nodedb.NewNodesIterator(txn)
-	if err != nil {
-		return nil, nil, err
-	}
-	result, err := evictor.Evict(ctx, it)
+	result, err := evictor.Evict(ctx, txn)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -348,6 +356,7 @@ func (sch *PreemptingQueueScheduler) evictGangs(ctx *armadacontext.Context, txn
 	)
 	evictor := NewFilteredEvictor(
 		sch.jobRepo,
+		sch.nodeDb,
 		sch.schedulingContext.PriorityClasses,
 		gangNodeIds,
 		gangJobIds,
@@ -356,15 +365,7 @@ func (sch *PreemptingQueueScheduler) evictGangs(ctx *armadacontext.Context, txn
 		// No gangs to evict.
 		return &EvictorResult{}, nil
 	}
-	it, err := nodedb.NewNodesIterator(txn)
-	if err != nil {
-		return nil, err
-	}
-	evictorResult, err := evictor.Evict(ctx, it)
-	if err != nil {
-		return nil, err
-	}
-	return evictorResult, nil
+	return evictor.Evict(ctx, txn)
 }
 
 // Collect job ids for any gangs that were partially evicted and the ids of nodes those jobs are on.
@@ -591,7 +592,7 @@ func (sch *PreemptingQueueScheduler) unbindJobs(jobs []interfaces.LegacySchedule
 		if err != nil {
 			return err
 		}
-		node, err = nodedb.UnbindJobsFromNode(sch.schedulingContext.PriorityClasses, jobsOnNode, node)
+		node, err = sch.nodeDb.UnbindJobsFromNode(sch.schedulingContext.PriorityClasses, jobsOnNode, node)
 		if err != nil {
 			return err
 		}
@@ -701,6 +702,7 @@ func (sch *PreemptingQueueScheduler) assertions(
 
 type Evictor struct {
 	jobRepo         JobRepository
+	nodeDb          *nodedb.NodeDb
 	priorityClasses map[string]types.PriorityClass
 	nodeFilter      func(*armadacontext.Context, *nodedb.Node) bool
 	jobFilter       func(*armadacontext.Context, interfaces.LegacySchedulerJob) bool
@@ -717,6 +719,7 @@ type EvictorResult struct {
 
 func NewNodeEvictor(
 	jobRepo JobRepository,
+	nodeDb *nodedb.NodeDb,
 	priorityClasses map[string]types.PriorityClass,
 	perNodeEvictionProbability float64,
 	jobFilter func(*armadacontext.Context, interfaces.LegacySchedulerJob) bool,
@@ -730,6 +733,7 @@ func NewNodeEvictor(
 	}
 	return &Evictor{
 		jobRepo:         jobRepo,
+		nodeDb:          nodeDb,
 		priorityClasses: priorityClasses,
 		nodeFilter: func(_ *armadacontext.Context, node *nodedb.Node) bool {
 			return len(node.AllocatedByJobId) > 0 && random.Float64() < perNodeEvictionProbability
@@ -742,6 +746,7 @@ func NewNodeEvictor(
 // on nodes for which nodeIdsToEvict[nodeId] is true.
 func NewFilteredEvictor(
 	jobRepo JobRepository,
+	nodeDb *nodedb.NodeDb,
 	priorityClasses map[string]types.PriorityClass,
 	nodeIdsToEvict map[string]bool,
 	jobIdsToEvict map[string]bool,
@@ -751,6 +756,7 @@ func NewFilteredEvictor(
 	}
 	return &Evictor{
 		jobRepo:         jobRepo,
+		nodeDb:          nodeDb,
 		priorityClasses: priorityClasses,
 		nodeFilter: func(_ *armadacontext.Context, node *nodedb.Node) bool {
 			shouldEvict := nodeIdsToEvict[node.Id]
@@ -768,8 +774,9 @@ func NewFilteredEvictor(
 // with probability perNodeEvictionProbability.
 func NewOversubscribedEvictor(
 	jobRepo JobRepository,
+	nodeDb *nodedb.NodeDb,
 	priorityClasses map[string]types.PriorityClass,
-	defaultPriorityClass string,
+	defaultPriorityClassName string,
 	perNodeEvictionProbability float64,
 	random *rand.Rand,
 ) *Evictor {
@@ -785,6 +792,7 @@ func NewOversubscribedEvictor(
 	var overSubscribedPriorities map[int32]bool
 	return &Evictor{
 		jobRepo:         jobRepo,
+		nodeDb:          nodeDb,
 		priorityClasses: priorityClasses,
 		nodeFilter: func(_ *armadacontext.Context, node *nodedb.Node) bool {
 			overSubscribedPriorities = make(map[int32]bool)
@@ -803,19 +811,16 @@ func NewOversubscribedEvictor(
 			return len(overSubscribedPriorities) > 0 && random.Float64() < perNodeEvictionProbability
 		},
 		jobFilter: func(ctx *armadacontext.Context, job interfaces.LegacySchedulerJob) bool {
-			if job.GetAnnotations() == nil {
-				ctx.Warnf("can't evict job %s: annotations not initialised", job.GetId())
+			priorityClass := interfaces.PriorityClassFromLegacySchedulerJob(priorityClasses, defaultPriorityClassName, job)
+			if !priorityClass.Preemptible {
 				return false
 			}
-			priorityClassName := job.GetPriorityClassName()
-			priorityClass, ok := priorityClasses[priorityClassName]
+			priority, ok := nodeDb.GetScheduledAtPriority(job.GetId())
 			if !ok {
-				priorityClass = priorityClasses[defaultPriorityClass]
-			}
-			if priorityClass.Preemptible && overSubscribedPriorities[priorityClass.Priority] {
-				return true
+				ctx.Warnf("can't evict job %s: not mapped to a priority", job.GetId())
+				return false
 			}
-			return false
+			return overSubscribedPriorities[priority]
 		},
 	}
 }
@@ -824,7 +829,7 @@ func NewOversubscribedEvictor(
 // Any node for which nodeFilter returns false is skipped.
 // Any job for which jobFilter returns true is evicted (if the node was not skipped).
 // If a job was evicted from a node, postEvictFunc is called with the corresponding job and node.
-func (evi *Evictor) Evict(ctx *armadacontext.Context, it nodedb.NodeIterator) (*EvictorResult, error) {
+func (evi *Evictor) Evict(ctx *armadacontext.Context, nodeDbTxn *memdb.Txn) (*EvictorResult, error) {
 	var jobFilter func(job interfaces.LegacySchedulerJob) bool
 	if evi.jobFilter != nil {
 		jobFilter = func(job interfaces.LegacySchedulerJob) bool { return evi.jobFilter(ctx, job) }
@@ -832,6 +837,12 @@ func (evi *Evictor) Evict(ctx *armadacontext.Context, it nodedb.NodeIterator) (*
 	evictedJctxsByJobId := make(map[string]*schedulercontext.JobSchedulingContext)
 	affectedNodesById := make(map[string]*nodedb.Node)
 	nodeIdByJobId := make(map[string]string)
+
+	it, err := nodedb.NewNodesIterator(nodeDbTxn)
+	if err != nil {
+		return nil, err
+	}
+
 	for node := it.NextNode(); node != nil; node = it.NextNode() {
 		if evi.nodeFilter != nil && !evi.nodeFilter(ctx, node) {
 			continue
@@ -846,7 +857,7 @@ func (evi *Evictor) Evict(ctx *armadacontext.Context, it nodedb.NodeIterator) (*
 		if err != nil {
 			return nil, err
 		}
-		evictedJobs, node, err := nodedb.EvictJobsFromNode(evi.priorityClasses, jobFilter, jobs, node)
+		evictedJobs, node, err := evi.nodeDb.EvictJobsFromNode(evi.priorityClasses, jobFilter, jobs, node)
 		if err != nil {
 			return nil, err
 		}
diff --git a/internal/scheduler/scheduler.go b/internal/scheduler/scheduler.go
index b35be5be..def58a83 100644
--- a/internal/scheduler/scheduler.go
+++ b/internal/scheduler/scheduler.go
@@ -515,6 +515,7 @@ func AppendEventSequencesFromScheduledJobs(eventSequences []*armadaevents.EventS
 		if run == nil {
 			return nil, errors.Errorf("attempting to generate lease events for job %s with no associated runs", job.Id())
 		}
+		scheduledAtPriority, hasScheduledAtPriority := job.GetScheduledAtPriority()
 		eventSequences = append(eventSequences, &armadaevents.EventSequence{
 			Queue:      job.Queue(),
 			JobSetName: job.Jobset(), // TODO: Rename to JobSet.
@@ -528,8 +529,10 @@ func AppendEventSequencesFromScheduledJobs(eventSequences []*armadaevents.EventS
 							ExecutorId: run.Executor(),
 							// NodeId here refers to the unique identifier of the node in an executor cluster,
 							// which is referred to as the NodeName within the scheduler.
-							NodeId:               run.NodeName(),
-							UpdateSequenceNumber: job.QueuedVersion(),
+							NodeId:                 run.NodeName(),
+							UpdateSequenceNumber:   job.QueuedVersion(),
+							HasScheduledAtPriority: hasScheduledAtPriority,
+							ScheduledAtPriority:    scheduledAtPriority,
 						},
 					},
 				},
diff --git a/internal/scheduler/schedulerapp.go b/internal/scheduler/schedulerapp.go
index 57aa48ad..77fa1445 100644
--- a/internal/scheduler/schedulerapp.go
+++ b/internal/scheduler/schedulerapp.go
@@ -28,6 +28,7 @@ import (
 	"github.com/armadaproject/armada/internal/common/profiling"
 	"github.com/armadaproject/armada/internal/common/pulsarutils"
 	"github.com/armadaproject/armada/internal/common/serve"
+	"github.com/armadaproject/armada/internal/common/types"
 	schedulerconfig "github.com/armadaproject/armada/internal/scheduler/configuration"
 	"github.com/armadaproject/armada/internal/scheduler/database"
 	"github.com/armadaproject/armada/internal/scheduler/jobdb"
@@ -142,13 +143,12 @@ func Run(config schedulerconfig.Configuration) error {
 	if err != nil {
 		return errors.WithMessage(err, "error setting up gRPC server")
 	}
-	allowedPcs := config.Scheduling.Preemption.AllowedPriorities()
 	executorServer, err := NewExecutorApi(
 		apiProducer,
 		jobRepository,
 		executorRepository,
 		legacyExecutorRepository,
-		allowedPcs,
+		types.AllowedPriorities(config.Scheduling.Preemption.PriorityClasses),
 		config.Scheduling.Preemption.NodeIdLabel,
 		config.Scheduling.Preemption.PriorityClassNameOverride,
 		config.Pulsar.MaxAllowedMessageSize,
diff --git a/internal/scheduler/scheduling_algo.go b/internal/scheduler/scheduling_algo.go
index 48c06e9c..f44441c4 100644
--- a/internal/scheduler/scheduling_algo.go
+++ b/internal/scheduler/scheduling_algo.go
@@ -344,6 +344,7 @@ func (l *FairSchedulingAlgo) scheduleOnExecutors(
 		l.schedulingConfig.IndexedResources,
 		l.schedulingConfig.IndexedTaints,
 		l.schedulingConfig.IndexedNodeLabels,
+		l.schedulingConfig.WellKnownNodeTypes,
 	)
 	if err != nil {
 		return nil, nil, err
@@ -417,7 +418,7 @@ func (l *FairSchedulingAlgo) scheduleOnExecutors(
 		minimumJobSize,
 		l.schedulingConfig,
 	)
-	scheduler := NewPreemptingQueueScheduler(
+	scheduler, err := NewPreemptingQueueScheduler(
 		sctx,
 		constraints,
 		l.schedulingConfig.Preemption.NodeEvictionProbability,
@@ -429,6 +430,9 @@ func (l *FairSchedulingAlgo) scheduleOnExecutors(
 		fsctx.jobIdsByGangId,
 		fsctx.gangIdByJobId,
 	)
+	if err != nil {
+		return nil, nil, err
+	}
 	if l.schedulingConfig.AlwaysAttemptScheduling {
 		scheduler.SkipUnsuccessfulSchedulingKeyCheck()
 	}
@@ -453,15 +457,23 @@ func (l *FairSchedulingAlgo) scheduleOnExecutors(
 	}
 	for i, job := range result.ScheduledJobs {
 		jobDbJob := job.(*jobdb.Job)
-		nodeId := result.NodeIdByJobId[jobDbJob.GetId()]
+		jobId := jobDbJob.GetId()
+		nodeId := result.NodeIdByJobId[jobId]
 		if nodeId == "" {
-			return nil, nil, errors.Errorf("job %s not mapped to any node", jobDbJob.GetId())
+			return nil, nil, errors.Errorf("job %s not mapped to a node", jobId)
 		}
-		if node, err := nodeDb.GetNode(nodeId); err != nil {
+		node, err := nodeDb.GetNode(nodeId)
+		if err != nil {
 			return nil, nil, err
-		} else {
-			result.ScheduledJobs[i] = jobDbJob.WithQueuedVersion(jobDbJob.QueuedVersion()+1).WithQueued(false).WithNewRun(node.Executor, node.Id, node.Name)
 		}
+		priority, ok := nodeDb.GetScheduledAtPriority(jobId)
+		if !ok {
+			return nil, nil, errors.Errorf("job %s not mapped to a priority", jobId)
+		}
+		result.ScheduledJobs[i] = jobDbJob.
+			WithQueuedVersion(jobDbJob.QueuedVersion()+1).
+			WithQueued(false).
+			WithNewRun(node.Executor, node.Id, node.Name, priority)
 	}
 	for i, job := range result.FailedJobs {
 		jobDbJob := job.(*jobdb.Job)
diff --git a/internal/scheduler/simulator/simulator.go b/internal/scheduler/simulator/simulator.go
index 99393d39..30abd7b0 100644
--- a/internal/scheduler/simulator/simulator.go
+++ b/internal/scheduler/simulator/simulator.go
@@ -19,6 +19,7 @@ import (
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	"github.com/armadaproject/armada/internal/common/logging"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
+	"github.com/armadaproject/armada/internal/common/types"
 	"github.com/armadaproject/armada/internal/common/util"
 	"github.com/armadaproject/armada/internal/scheduler"
 	schedulerconstraints "github.com/armadaproject/armada/internal/scheduler/constraints"
@@ -218,6 +219,7 @@ func (s *Simulator) setupClusters() error {
 				s.schedulingConfig.IndexedResources,
 				s.schedulingConfig.IndexedTaints,
 				s.schedulingConfig.IndexedNodeLabels,
+				s.schedulingConfig.WellKnownNodeTypes,
 			)
 			if err != nil {
 				return err
@@ -228,18 +230,17 @@ func (s *Simulator) setupClusters() error {
 				for nodeTemplateIndex, nodeTemplate := range executor.NodeTemplates {
 					for i := 0; i < int(nodeTemplate.Number); i++ {
 						nodeId := fmt.Sprintf("%s-%d-%d-%d-%d", pool.Name, executorGroupIndex, executorIndex, nodeTemplateIndex, i)
-						allocatableByPriorityAndResource := make(map[int32]schedulerobjects.ResourceList)
-						for _, priorityClass := range s.schedulingConfig.Preemption.PriorityClasses {
-							allocatableByPriorityAndResource[priorityClass.Priority] = nodeTemplate.TotalResources.DeepCopy()
-						}
 						node := &schedulerobjects.Node{
-							Id:                               nodeId,
-							Name:                             nodeId,
-							Executor:                         executorName,
-							Taints:                           slices.Clone(nodeTemplate.Taints),
-							Labels:                           maps.Clone(nodeTemplate.Labels),
-							TotalResources:                   nodeTemplate.TotalResources.DeepCopy(),
-							AllocatableByPriorityAndResource: allocatableByPriorityAndResource,
+							Id:             nodeId,
+							Name:           nodeId,
+							Executor:       executorName,
+							Taints:         slices.Clone(nodeTemplate.Taints),
+							Labels:         maps.Clone(nodeTemplate.Labels),
+							TotalResources: nodeTemplate.TotalResources.DeepCopy(),
+							AllocatableByPriorityAndResource: schedulerobjects.NewAllocatableByPriorityAndResourceType(
+								types.AllowedPriorities(s.schedulingConfig.Preemption.PriorityClasses),
+								nodeTemplate.TotalResources,
+							),
 						}
 						txn := nodeDb.Txn(true)
 						if err := nodeDb.CreateAndInsertWithApiJobsWithTxn(txn, nil, node); err != nil {
@@ -458,7 +459,7 @@ func (s *Simulator) handleScheduleEvent(ctx *armadacontext.Context) error {
 				schedulerobjects.ResourceList{},
 				s.schedulingConfig,
 			)
-			sch := scheduler.NewPreemptingQueueScheduler(
+			sch, err := scheduler.NewPreemptingQueueScheduler(
 				sctx,
 				constraints,
 				s.schedulingConfig.Preemption.NodeEvictionProbability,
@@ -471,6 +472,9 @@ func (s *Simulator) handleScheduleEvent(ctx *armadacontext.Context) error {
 				nil,
 				nil,
 			)
+			if err != nil {
+				return err
+			}
 			if s.schedulingConfig.EnableNewPreemptionStrategy {
 				sch.EnableNewPreemptionStrategy()
 			}
@@ -518,12 +522,16 @@ func (s *Simulator) handleScheduleEvent(ctx *armadacontext.Context) error {
 			for i, job := range scheduledJobs {
 				nodeId := result.NodeIdByJobId[job.GetId()]
 				if nodeId == "" {
-					return errors.Errorf("job %s not mapped to any node", job.GetId())
+					return errors.Errorf("job %s not mapped to a node", job.GetId())
 				}
 				if node, err := nodeDb.GetNode(nodeId); err != nil {
 					return err
 				} else {
-					scheduledJobs[i] = job.WithQueued(false).WithNewRun(node.Executor, node.Id, node.Name)
+					priority, ok := nodeDb.GetScheduledAtPriority(job.Id())
+					if !ok {
+						return errors.Errorf("job %s not mapped to a priority", job.Id())
+					}
+					scheduledJobs[i] = job.WithQueued(false).WithNewRun(node.Executor, node.Id, node.Name, priority)
 				}
 			}
 			for i, job := range failedJobs {
@@ -791,7 +799,7 @@ func (s *Simulator) unbindRunningJob(job *jobdb.Job) error {
 	} else if node == nil {
 		return errors.Errorf("node %s not found", run.NodeId())
 	}
-	node, err = nodedb.UnbindJobFromNode(s.schedulingConfig.Preemption.PriorityClasses, job, node)
+	node, err = nodeDb.UnbindJobFromNode(s.schedulingConfig.Preemption.PriorityClasses, job, node)
 	if err != nil {
 		return err
 	}
diff --git a/internal/scheduler/submitcheck.go b/internal/scheduler/submitcheck.go
index f9945e56..2e8b2e6a 100644
--- a/internal/scheduler/submitcheck.go
+++ b/internal/scheduler/submitcheck.go
@@ -51,6 +51,7 @@ type SubmitChecker struct {
 	indexedResources          []configuration.IndexedResource
 	indexedTaints             []string
 	indexedNodeLabels         []string
+	wellKnownNodeTypes        []configuration.WellKnownNodeType
 	executorRepository        database.ExecutorRepository
 	clock                     clock.Clock
 	mu                        sync.Mutex
@@ -73,10 +74,11 @@ func NewSubmitChecker(
 		priorityClasses:           schedulingConfig.Preemption.PriorityClasses,
 		gangIdAnnotation:          configuration.GangIdAnnotation,
 		executorById:              map[string]minimalExecutor{},
-		priorities:                schedulingConfig.Preemption.AllowedPriorities(),
+		priorities:                types.AllowedPriorities(schedulingConfig.Preemption.PriorityClasses),
 		indexedResources:          schedulingConfig.IndexedResources,
 		indexedTaints:             schedulingConfig.IndexedTaints,
 		indexedNodeLabels:         schedulingConfig.IndexedNodeLabels,
+		wellKnownNodeTypes:        schedulingConfig.WellKnownNodeTypes,
 		executorRepository:        executorRepository,
 		clock:                     clock.RealClock{},
 		schedulingKeyGenerator:    schedulerobjects.NewSchedulingKeyGenerator(),
@@ -273,16 +275,13 @@ func (srv *SubmitChecker) filterStaleExecutors(executorsById map[string]minimalE
 }
 
 func (srv *SubmitChecker) constructNodeDb(nodes []*schedulerobjects.Node) (*nodedb.NodeDb, error) {
-	// Nodes to be considered by the scheduler.
-	// We just need to know if scheduling is possible;
-	// no need to try to find a good fit.
-	var maxExtraNodesToConsider uint = 0
 	nodeDb, err := nodedb.NewNodeDb(
 		srv.priorityClasses,
-		maxExtraNodesToConsider,
+		0,
 		srv.indexedResources,
 		srv.indexedTaints,
 		srv.indexedNodeLabels,
+		srv.wellKnownNodeTypes,
 	)
 	if err != nil {
 		return nil, err
diff --git a/internal/scheduleringester/instructions.go b/internal/scheduleringester/instructions.go
index 3a9d6d78..d4d3e1c3 100644
--- a/internal/scheduleringester/instructions.go
+++ b/internal/scheduleringester/instructions.go
@@ -183,15 +183,20 @@ func (c *InstructionConverter) handleJobRunLeased(jobRunLeased *armadaevents.Job
 	if err != nil {
 		return nil, err
 	}
+	var scheduledAtPriority *int32
+	if jobRunLeased.HasScheduledAtPriority {
+		scheduledAtPriority = &jobRunLeased.ScheduledAtPriority
+	}
 	return []DbOperation{
 		InsertRuns{runId: &JobRunDetails{
 			queue: meta.queue,
 			dbRun: &schedulerdb.Run{
-				RunID:    runId,
-				JobID:    jobId,
-				JobSet:   meta.jobset,
-				Executor: jobRunLeased.GetExecutorId(),
-				Node:     jobRunLeased.GetNodeId(),
+				RunID:               runId,
+				JobID:               jobId,
+				JobSet:              meta.jobset,
+				Executor:            jobRunLeased.GetExecutorId(),
+				Node:                jobRunLeased.GetNodeId(),
+				ScheduledAtPriority: scheduledAtPriority,
 			},
 		}},
 		UpdateJobQueuedState{jobId: &JobQueuedStateUpdate{
diff --git a/pkg/api/util.go b/pkg/api/util.go
index 7e3f7893..d9b83fa4 100644
--- a/pkg/api/util.go
+++ b/pkg/api/util.go
@@ -225,6 +225,10 @@ func (job *Job) GetPriorityClassName() string {
 	return podSpec.PriorityClassName
 }
 
+func (job *Job) GetScheduledAtPriority() (int32, bool) {
+	return -1, false
+}
+
 func (job *Job) GetNodeSelector() map[string]string {
 	podSpec := job.GetMainPodSpec()
 	return podSpec.NodeSelector
diff --git a/pkg/armadaevents/events.pb.go b/pkg/armadaevents/events.pb.go
index 3feeac79..9aa63f5f 100644
--- a/pkg/armadaevents/events.pb.go
+++ b/pkg/armadaevents/events.pb.go
@@ -1657,6 +1657,17 @@ type JobRunLeased struct {
 	NodeId     string `protobuf:"bytes,4,opt,name=node_id,json=nodeId,proto3" json:"nodeId,omitempty"`
 	// Used by the scheduler to maintain a consistent state
 	UpdateSequenceNumber int32 `protobuf:"varint,5,opt,name=update_sequence_number,json=updateSequenceNumber,proto3" json:"updateSequenceNumber,omitempty"`
+	// This bool is here because it is the most ergonomic way of distinguishing
+	// the case where `scheduled_at_priority` was explicitly set to zero from
+	// the case where it was not set at all.
+	HasScheduledAtPriority bool `protobuf:"varint,6,opt,name=has_scheduled_at_priority,json=hasScheduledAtPriority,proto3" json:"hasScheduledAtPriority,omitempty"`
+	// Priority class priority that this job was scheduled at.
+	//
+	// This is usually equal to the priority of the job's priority class, except
+	// in the case where the job was scheduled as an away job; this field is
+	// used to distinguish this case from the case where the job was scheduled
+	// as a home job.
+	ScheduledAtPriority int32 `protobuf:"varint,7,opt,name=scheduled_at_priority,json=scheduledAtPriority,proto3" json:"scheduledAtPriority,omitempty"`
 }
 
 func (m *JobRunLeased) Reset()         { *m = JobRunLeased{} }
@@ -1727,6 +1738,20 @@ func (m *JobRunLeased) GetUpdateSequenceNumber() int32 {
 	return 0
 }
 
+func (m *JobRunLeased) GetHasScheduledAtPriority() bool {
+	if m != nil {
+		return m.HasScheduledAtPriority
+	}
+	return false
+}
+
+func (m *JobRunLeased) GetScheduledAtPriority() int32 {
+	if m != nil {
+		return m.ScheduledAtPriority
+	}
+	return 0
+}
+
 // Indicates that a job has been assigned to nodes by Kubernetes.
 type JobRunAssigned struct {
 	RunId *Uuid `protobuf:"bytes,1,opt,name=run_id,json=runId,proto3" json:"runId,omitempty"`
@@ -3490,226 +3515,230 @@ func init() {
 func init() { proto.RegisterFile("pkg/armadaevents/events.proto", fileDescriptor_6aab92ca59e015f8) }
 
 var fileDescriptor_6aab92ca59e015f8 = []byte{
-	// 3498 bytes of a gzipped FileDescriptorProto
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x5b, 0x4b, 0x6c, 0x1b, 0xd7,
-	0xb9, 0xf6, 0x90, 0x12, 0x1f, 0x3f, 0x25, 0x91, 0x3e, 0x96, 0x15, 0x5a, 0xb1, 0x45, 0x67, 0x9c,
-	0x7b, 0xe3, 0x04, 0x09, 0x99, 0x38, 0x0f, 0xe4, 0x71, 0x91, 0x40, 0xb4, 0x15, 0x3f, 0x62, 0xd9,
-	0x0e, 0x65, 0xe7, 0xfa, 0x06, 0xb9, 0x60, 0x86, 0x9c, 0x23, 0x6a, 0xac, 0xe1, 0xcc, 0x64, 0x1e,
-	0xb2, 0x04, 0x64, 0x71, 0xef, 0xc5, 0xbd, 0xb9, 0xbb, 0xd4, 0x40, 0xbb, 0x28, 0xd0, 0x45, 0xba,
-	0x6d, 0x80, 0xae, 0xbb, 0xee, 0xaa, 0x59, 0x14, 0x45, 0xda, 0x55, 0x57, 0x6c, 0x91, 0xa0, 0x8b,
-	0x72, 0xd1, 0x75, 0xdb, 0x4d, 0x8b, 0xf3, 0x9a, 0x39, 0x67, 0x38, 0xb4, 0xe5, 0x57, 0x9d, 0xc2,
-	0x2b, 0x69, 0xbe, 0xff, 0x39, 0xe7, 0xf1, 0xcf, 0xff, 0xff, 0xe7, 0x10, 0x8e, 0x79, 0xdb, 0x83,
-	0x96, 0xe1, 0x0f, 0x0d, 0xd3, 0xc0, 0x3b, 0xd8, 0x09, 0x83, 0x16, 0xfb, 0xd3, 0xf4, 0x7c, 0x37,
-	0x74, 0xd1, 0x9c, 0x4c, 0x5a, 0xd6, 0xb7, 0x5f, 0x0f, 0x9a, 0x96, 0xdb, 0x32, 0x3c, 0xab, 0xd5,
-	0x77, 0x7d, 0xdc, 0xda, 0x79, 0xa9, 0x35, 0xc0, 0x0e, 0xf6, 0x8d, 0x10, 0x9b, 0x4c, 0x62, 0xf9,
-	0xa4, 0xc4, 0xe3, 0xe0, 0xf0, 0xa6, 0xeb, 0x6f, 0x5b, 0xce, 0x20, 0x8b, 0xb3, 0x31, 0x70, 0xdd,
-	0x81, 0x8d, 0x5b, 0xf4, 0xa9, 0x17, 0x6d, 0xb6, 0x42, 0x6b, 0x88, 0x83, 0xd0, 0x18, 0x7a, 0x9c,
-	0xe1, 0x95, 0x44, 0xd5, 0xd0, 0xe8, 0x6f, 0x59, 0x0e, 0xf6, 0xf7, 0x5a, 0xd4, 0x5f, 0xcf, 0x6a,
-	0xf9, 0x38, 0x70, 0x23, 0xbf, 0x8f, 0x27, 0xd4, 0xbe, 0x30, 0xb0, 0xc2, 0xad, 0xa8, 0xd7, 0xec,
-	0xbb, 0xc3, 0xd6, 0xc0, 0x1d, 0xb8, 0x89, 0x7e, 0xf2, 0x44, 0x1f, 0xe8, 0x7f, 0x9c, 0xfd, 0x4d,
-	0xcb, 0x09, 0xb1, 0xef, 0x18, 0x76, 0x2b, 0xe8, 0x6f, 0x61, 0x33, 0xb2, 0xb1, 0x9f, 0xfc, 0xe7,
-	0xf6, 0x6e, 0xe0, 0x7e, 0x18, 0x4c, 0x00, 0x4c, 0x56, 0xbf, 0xb5, 0x08, 0xf3, 0x6b, 0x64, 0x68,
-	0x36, 0xf0, 0x27, 0x11, 0x76, 0xfa, 0x18, 0x3d, 0x0b, 0xb3, 0x9f, 0x44, 0x38, 0xc2, 0x75, 0xed,
-	0xb8, 0x76, 0xb2, 0xdc, 0x3e, 0x34, 0x1e, 0x35, 0xaa, 0x14, 0x78, 0xde, 0x1d, 0x5a, 0x21, 0x1e,
-	0x7a, 0xe1, 0x5e, 0x87, 0x71, 0xa0, 0x37, 0x61, 0xee, 0x86, 0xdb, 0xeb, 0x06, 0x38, 0xec, 0x3a,
-	0xc6, 0x10, 0xd7, 0x73, 0x54, 0xa2, 0x3e, 0x1e, 0x35, 0x16, 0x6f, 0xb8, 0xbd, 0x0d, 0x1c, 0x5e,
-	0x32, 0x86, 0xb2, 0x18, 0x24, 0x28, 0x7a, 0x01, 0x8a, 0x51, 0x80, 0xfd, 0xae, 0x65, 0xd6, 0xf3,
-	0x54, 0x6c, 0x71, 0x3c, 0x6a, 0xd4, 0x08, 0x74, 0xde, 0x94, 0x44, 0x0a, 0x0c, 0x41, 0xcf, 0x43,
-	0x61, 0xe0, 0xbb, 0x91, 0x17, 0xd4, 0x67, 0x8e, 0xe7, 0x05, 0x37, 0x43, 0x64, 0x6e, 0x86, 0xa0,
-	0xcb, 0x50, 0x60, 0xf3, 0x5d, 0x9f, 0x3d, 0x9e, 0x3f, 0x59, 0x39, 0xf5, 0x54, 0x53, 0x5e, 0x04,
-	0x4d, 0xe5, 0x85, 0xd9, 0x13, 0x53, 0xc8, 0xe8, 0xb2, 0x42, 0xbe, 0x6c, 0xfe, 0x78, 0x10, 0x66,
-	0x29, 0x1f, 0xba, 0x0c, 0xc5, 0xbe, 0x8f, 0xc9, 0x64, 0xd5, 0xd1, 0x71, 0xed, 0x64, 0xe5, 0xd4,
-	0x72, 0x93, 0x2d, 0x82, 0xa6, 0x98, 0xa4, 0xe6, 0x55, 0xb1, 0x08, 0xda, 0x47, 0xc6, 0xa3, 0xc6,
-	0x41, 0xce, 0x9e, 0x68, 0xbd, 0xf5, 0xbb, 0x86, 0xd6, 0x11, 0x5a, 0xd0, 0x15, 0x28, 0x07, 0x51,
-	0x6f, 0x68, 0x85, 0x17, 0xdc, 0x1e, 0x1d, 0xf3, 0xca, 0xa9, 0x27, 0x54, 0x77, 0x37, 0x04, 0xb9,
-	0xfd, 0xc4, 0x78, 0xd4, 0x38, 0x14, 0x73, 0x27, 0x1a, 0xcf, 0x1d, 0xe8, 0x24, 0x4a, 0xd0, 0x16,
-	0x54, 0x7d, 0xec, 0xf9, 0x96, 0xeb, 0x5b, 0xa1, 0x15, 0x60, 0xa2, 0x37, 0x47, 0xf5, 0x1e, 0x53,
-	0xf5, 0x76, 0x54, 0xa6, 0xf6, 0xb1, 0xf1, 0xa8, 0x71, 0x24, 0x25, 0xa9, 0xd8, 0x48, 0xab, 0x45,
-	0x21, 0xa0, 0x14, 0xb4, 0x81, 0x43, 0x3a, 0x9f, 0x95, 0x53, 0xc7, 0x6f, 0x6b, 0x6c, 0x03, 0x87,
-	0xed, 0xe3, 0xe3, 0x51, 0xe3, 0xe8, 0xa4, 0xbc, 0x62, 0x32, 0x43, 0x3f, 0xb2, 0xa1, 0x26, 0xa3,
-	0x26, 0x79, 0xc1, 0x19, 0x6a, 0x73, 0x65, 0xba, 0x4d, 0xc2, 0xd5, 0x5e, 0x19, 0x8f, 0x1a, 0xcb,
-	0x69, 0x59, 0xc5, 0xde, 0x84, 0x66, 0x32, 0x3f, 0x7d, 0xc3, 0xe9, 0x63, 0x9b, 0x98, 0x99, 0xcd,
-	0x9a, 0x9f, 0xd3, 0x82, 0xcc, 0xe6, 0x27, 0xe6, 0x56, 0xe7, 0x27, 0x86, 0xd1, 0x47, 0x30, 0x17,
-	0x3f, 0x90, 0xf1, 0x2a, 0xf0, 0x75, 0x94, 0xad, 0x94, 0x8c, 0xd4, 0xf2, 0x78, 0xd4, 0x58, 0x92,
-	0x65, 0x14, 0xd5, 0x8a, 0xb6, 0x44, 0xbb, 0xcd, 0x46, 0xa6, 0x38, 0x5d, 0x3b, 0xe3, 0x90, 0xb5,
-	0xdb, 0x93, 0x23, 0xa2, 0x68, 0x23, 0xda, 0xc9, 0x26, 0x8e, 0xfa, 0x7d, 0x8c, 0x4d, 0x6c, 0xd6,
-	0x4b, 0x59, 0xda, 0x2f, 0x48, 0x1c, 0x4c, 0xbb, 0x2c, 0xa3, 0x6a, 0x97, 0x29, 0x64, 0xac, 0x6f,
-	0xb8, 0xbd, 0x35, 0xdf, 0x77, 0xfd, 0xa0, 0x5e, 0xce, 0x1a, 0xeb, 0x0b, 0x82, 0xcc, 0xc6, 0x3a,
-	0xe6, 0x56, 0xc7, 0x3a, 0x86, 0xb9, 0xbf, 0x9d, 0xc8, 0xb9, 0x88, 0x8d, 0x00, 0x9b, 0x75, 0x98,
-	0xe2, 0x6f, 0xcc, 0x11, 0xfb, 0x1b, 0x23, 0x13, 0xfe, 0xc6, 0x14, 0x64, 0xc2, 0x02, 0x7b, 0x5e,
-	0x0d, 0x02, 0x6b, 0xe0, 0x60, 0xb3, 0x5e, 0xa1, 0xfa, 0x8f, 0x66, 0xe9, 0x17, 0x3c, 0xed, 0xa3,
-	0xe3, 0x51, 0xa3, 0xae, 0xca, 0x29, 0x36, 0x52, 0x3a, 0xd1, 0xc7, 0x30, 0xcf, 0x90, 0x4e, 0xe4,
-	0x38, 0x96, 0x33, 0xa8, 0xcf, 0x51, 0x23, 0x4f, 0x66, 0x19, 0xe1, 0x2c, 0xed, 0x27, 0xc7, 0xa3,
-	0xc6, 0x13, 0x8a, 0x94, 0x62, 0x42, 0x55, 0x48, 0x22, 0x06, 0x03, 0x92, 0x89, 0x9d, 0xcf, 0x8a,
-	0x18, 0x17, 0x54, 0x26, 0x16, 0x31, 0x52, 0x92, 0x6a, 0xc4, 0x48, 0x11, 0x93, 0xf9, 0xe0, 0x93,
-	0xbc, 0x30, 0x7d, 0x3e, 0xf8, 0x3c, 0x4b, 0xf3, 0x91, 0x31, 0xd5, 0x8a, 0x36, 0xf4, 0x29, 0x90,
-	0x0f, 0xcf, 0x99, 0xc8, 0xb3, 0xad, 0xbe, 0x11, 0xe2, 0x33, 0x38, 0xc4, 0x7d, 0x12, 0xa9, 0xab,
-	0xd4, 0x8a, 0x3e, 0x61, 0x65, 0x82, 0xb3, 0xad, 0x8f, 0x47, 0x8d, 0x95, 0x2c, 0x1d, 0x8a, 0xd5,
-	0x4c, 0x2b, 0xe8, 0xbf, 0x34, 0x38, 0x1c, 0x84, 0x86, 0x63, 0x1a, 0xb6, 0xeb, 0xe0, 0xf3, 0xce,
-	0xc0, 0xc7, 0x41, 0x70, 0xde, 0xd9, 0x74, 0xeb, 0x35, 0x6a, 0xff, 0x44, 0x2a, 0xac, 0x67, 0xb1,
-	0xb6, 0x4f, 0x8c, 0x47, 0x8d, 0x46, 0xa6, 0x16, 0xc5, 0x83, 0x6c, 0x43, 0x68, 0x17, 0x0e, 0x89,
-	0xac, 0xe2, 0x5a, 0x68, 0xd9, 0x56, 0x60, 0x84, 0x96, 0xeb, 0xd4, 0x0f, 0x52, 0xfb, 0x4f, 0xa5,
-	0xa3, 0xe3, 0x04, 0x63, 0xfb, 0xa9, 0xf1, 0xa8, 0x71, 0x2c, 0x43, 0x83, 0x62, 0x3b, 0xcb, 0x44,
-	0xb2, 0x84, 0xae, 0xf8, 0x98, 0x30, 0x62, 0xb3, 0x7e, 0x68, 0xfa, 0x12, 0x8a, 0x99, 0xe4, 0x25,
-	0x14, 0x83, 0x59, 0x4b, 0x28, 0x26, 0x12, 0x4b, 0x9e, 0xe1, 0x87, 0x16, 0x31, 0xbb, 0x6e, 0xf8,
-	0xdb, 0xd8, 0xaf, 0x2f, 0x66, 0x59, 0xba, 0xa2, 0x32, 0x31, 0x4b, 0x29, 0x49, 0xd5, 0x52, 0x8a,
-	0x88, 0x6e, 0x69, 0xa0, 0xba, 0x66, 0xb9, 0x4e, 0x87, 0xa4, 0x0d, 0x01, 0x79, 0xbd, 0xc3, 0xd4,
-	0xe8, 0x33, 0xb7, 0x79, 0x3d, 0x99, 0xbd, 0xfd, 0xcc, 0x78, 0xd4, 0x38, 0x31, 0x55, 0x9b, 0xe2,
-	0xc8, 0x74, 0xa3, 0xe8, 0x3a, 0x54, 0x08, 0x11, 0xd3, 0x04, 0xcc, 0xac, 0x2f, 0x51, 0x1f, 0x8e,
-	0x4c, 0xfa, 0xc0, 0x19, 0x68, 0x06, 0x72, 0x58, 0x92, 0x50, 0xec, 0xc8, 0xaa, 0xda, 0x45, 0x98,
-	0xa5, 0xf2, 0xfa, 0xb8, 0x00, 0x87, 0x32, 0xd6, 0x06, 0x7a, 0x1b, 0x0a, 0x7e, 0xe4, 0x90, 0x84,
-	0x8d, 0x65, 0x29, 0x48, 0xb5, 0x7a, 0x2d, 0xb2, 0x4c, 0x96, 0x2d, 0xfa, 0x91, 0xa3, 0xe4, 0x70,
-	0xb3, 0x14, 0x20, 0xf2, 0x24, 0x5b, 0xb4, 0x4c, 0x9e, 0x8d, 0x4c, 0x95, 0xbf, 0xe1, 0xf6, 0x54,
-	0x79, 0x0a, 0x20, 0x0c, 0xf3, 0x62, 0xe1, 0x75, 0x2d, 0xb2, 0xab, 0x58, 0x9e, 0xf1, 0xb4, 0xaa,
-	0xe6, 0xbd, 0xa8, 0x87, 0x7d, 0x07, 0x87, 0x38, 0x10, 0xef, 0x40, 0xb7, 0x15, 0x8d, 0x22, 0xbe,
-	0x84, 0x48, 0xfa, 0xe7, 0x64, 0x1c, 0xfd, 0x40, 0x83, 0xfa, 0xd0, 0xd8, 0xed, 0x0a, 0x30, 0xe8,
-	0x6e, 0xba, 0x7e, 0xd7, 0xc3, 0xbe, 0xe5, 0x9a, 0x34, 0xf9, 0xac, 0x9c, 0xfa, 0xb7, 0x3b, 0x6e,
-	0xa4, 0xe6, 0xba, 0xb1, 0x2b, 0xe0, 0xe0, 0x5d, 0xd7, 0xbf, 0x42, 0xc5, 0xd7, 0x9c, 0xd0, 0xdf,
-	0x6b, 0x1f, 0xfb, 0x6a, 0xd4, 0x38, 0x40, 0xa6, 0x65, 0x98, 0xc5, 0xd3, 0xc9, 0x86, 0xd1, 0xf7,
-	0x34, 0x58, 0x0a, 0xdd, 0xd0, 0xb0, 0xbb, 0xfd, 0x68, 0x18, 0xd9, 0x46, 0x68, 0xed, 0xe0, 0x6e,
-	0x14, 0x18, 0x03, 0xcc, 0x73, 0xdc, 0xb7, 0xee, 0xec, 0xd4, 0x55, 0x22, 0x7f, 0x3a, 0x16, 0xbf,
-	0x46, 0xa4, 0x99, 0x4f, 0x47, 0xb9, 0x4f, 0x8b, 0x61, 0x06, 0x4b, 0x27, 0x13, 0x5d, 0xfe, 0xb1,
-	0x06, 0xcb, 0xd3, 0x5f, 0x13, 0x9d, 0x80, 0xfc, 0x36, 0xde, 0xe3, 0x55, 0xc4, 0xc1, 0xf1, 0xa8,
-	0x31, 0xbf, 0x8d, 0xf7, 0xa4, 0x51, 0x27, 0x54, 0xf4, 0x1f, 0x30, 0xbb, 0x63, 0xd8, 0x11, 0xe6,
-	0x4b, 0xa2, 0xd9, 0x64, 0xf5, 0x52, 0x53, 0xae, 0x97, 0x9a, 0xde, 0xf6, 0x80, 0x00, 0x4d, 0x31,
-	0x23, 0xcd, 0xf7, 0x23, 0xc3, 0x09, 0xad, 0x70, 0x8f, 0x2d, 0x17, 0xaa, 0x40, 0x5e, 0x2e, 0x14,
-	0x78, 0x33, 0xf7, 0xba, 0xb6, 0xfc, 0x85, 0x06, 0x47, 0xa6, 0xbe, 0xf4, 0x77, 0xc1, 0x43, 0xbd,
-	0x0b, 0x33, 0x64, 0xe1, 0x93, 0xfa, 0x66, 0xcb, 0x1a, 0x6c, 0xbd, 0xf6, 0x0a, 0x75, 0xa7, 0xc0,
-	0xca, 0x11, 0x86, 0xc8, 0xe5, 0x08, 0x43, 0x48, 0x8d, 0x66, 0xbb, 0x37, 0x5f, 0x7b, 0x85, 0x3a,
-	0x55, 0x60, 0x46, 0x28, 0x20, 0x1b, 0xa1, 0x80, 0xfe, 0xb7, 0x02, 0x94, 0xe3, 0x02, 0x42, 0xda,
-	0x83, 0xda, 0x3d, 0xed, 0xc1, 0x73, 0x50, 0x33, 0xb1, 0xc9, 0xbf, 0x7c, 0x96, 0xeb, 0x88, 0xdd,
-	0x5c, 0x66, 0xd1, 0x55, 0xa1, 0x29, 0xf2, 0xd5, 0x14, 0x09, 0x9d, 0x82, 0x12, 0x4f, 0xb4, 0xf7,
-	0xe8, 0x46, 0x9e, 0x6f, 0x2f, 0x8d, 0x47, 0x0d, 0x24, 0x30, 0x49, 0x34, 0xe6, 0x43, 0x1d, 0x00,
-	0x56, 0xbd, 0xae, 0xe3, 0xd0, 0xe0, 0x29, 0x7f, 0x5d, 0x7d, 0x83, 0xcb, 0x31, 0x9d, 0xd5, 0xa1,
-	0x09, 0xbf, 0x5c, 0x87, 0x26, 0x28, 0xfa, 0x08, 0x60, 0x68, 0x58, 0x0e, 0x93, 0xe3, 0xf9, 0xbd,
-	0x3e, 0x2d, 0xa4, 0xac, 0xc7, 0x9c, 0x4c, 0x7b, 0x22, 0x29, 0x6b, 0x4f, 0x50, 0x52, 0x2d, 0xf2,
-	0x7a, 0xbb, 0x5e, 0xa0, 0xbb, 0x74, 0x65, 0x9a, 0x6a, 0xae, 0xf6, 0x30, 0xa9, 0x18, 0xb9, 0x88,
-	0xa4, 0x53, 0x68, 0x21, 0xc3, 0x66, 0x5b, 0x9b, 0x38, 0xb4, 0x86, 0x98, 0x66, 0xf6, 0x7c, 0xd8,
-	0x04, 0x26, 0x0f, 0x9b, 0xc0, 0xd0, 0xeb, 0x00, 0x46, 0xb8, 0xee, 0x06, 0xe1, 0x65, 0xa7, 0x8f,
-	0x69, 0xc6, 0x5e, 0x62, 0xee, 0x27, 0xa8, 0xec, 0x7e, 0x82, 0xa2, 0xb7, 0xa0, 0xe2, 0xf1, 0x8f,
-	0x50, 0xcf, 0xc6, 0x34, 0x23, 0x2f, 0xb1, 0x4f, 0x8a, 0x04, 0x4b, 0xb2, 0x32, 0x37, 0x3a, 0x0b,
-	0xd5, 0xbe, 0xeb, 0xf4, 0x23, 0xdf, 0xc7, 0x4e, 0x7f, 0x6f, 0xc3, 0xd8, 0xc4, 0x34, 0xfb, 0x2e,
-	0xb1, 0xa5, 0x92, 0x22, 0xc9, 0x4b, 0x25, 0x45, 0x42, 0xaf, 0x42, 0x39, 0xee, 0x5e, 0xd0, 0x04,
-	0xbb, 0xcc, 0x0b, 0x61, 0x01, 0x4a, 0xc2, 0x09, 0x27, 0x71, 0xde, 0x0a, 0xe2, 0x2c, 0x8d, 0x26,
-	0xcd, 0xdc, 0x79, 0x09, 0x96, 0x9d, 0x97, 0x60, 0x74, 0x1e, 0x0e, 0xd2, 0xef, 0x62, 0x37, 0x0c,
-	0xed, 0x6e, 0x80, 0xfb, 0xae, 0x63, 0x06, 0x34, 0x27, 0xce, 0x33, 0xf7, 0x29, 0xf1, 0x6a, 0x68,
-	0x6f, 0x30, 0x92, 0xec, 0x7e, 0x8a, 0xa4, 0xff, 0x52, 0x83, 0xc5, 0xac, 0x25, 0x94, 0x5a, 0xce,
-	0xda, 0x03, 0x59, 0xce, 0x1f, 0x40, 0xc9, 0x73, 0xcd, 0x6e, 0xe0, 0xe1, 0x3e, 0x8f, 0x58, 0xa9,
-	0xc5, 0x7c, 0xc5, 0x35, 0x37, 0x3c, 0xdc, 0xff, 0x77, 0x2b, 0xdc, 0x5a, 0xdd, 0x71, 0x2d, 0xf3,
-	0xa2, 0x15, 0xf0, 0x55, 0xe7, 0x31, 0x8a, 0x92, 0x21, 0x14, 0x39, 0xd8, 0x2e, 0x41, 0x81, 0x59,
-	0xd1, 0x7f, 0x95, 0x87, 0x5a, 0x7a, 0xd9, 0xfe, 0x33, 0xbd, 0x0a, 0xba, 0x0e, 0x45, 0x8b, 0xa5,
-	0xcc, 0x3c, 0x83, 0xf8, 0x17, 0x29, 0xa6, 0x37, 0x93, 0x86, 0x5f, 0x73, 0xe7, 0xa5, 0x26, 0xcf,
-	0xad, 0xe9, 0x10, 0x50, 0xcd, 0x5c, 0x52, 0xd5, 0xcc, 0x41, 0xd4, 0x81, 0x62, 0x80, 0xfd, 0x1d,
-	0xab, 0x8f, 0x79, 0x70, 0x6a, 0xc8, 0x9a, 0xfb, 0xae, 0x8f, 0x89, 0xce, 0x0d, 0xc6, 0x92, 0xe8,
-	0xe4, 0x32, 0xaa, 0x4e, 0x0e, 0xa2, 0x0f, 0xa0, 0xdc, 0x77, 0x9d, 0x4d, 0x6b, 0xb0, 0x6e, 0x78,
-	0x3c, 0x3c, 0x1d, 0xcb, 0xd2, 0x7a, 0x5a, 0x30, 0xf1, 0x26, 0x84, 0x78, 0x4c, 0x35, 0x21, 0x62,
-	0xae, 0x64, 0x42, 0xff, 0x34, 0x03, 0x90, 0x4c, 0x0e, 0x7a, 0x03, 0x2a, 0x78, 0x17, 0xf7, 0xa3,
-	0xd0, 0xf5, 0xc5, 0x77, 0x82, 0xf7, 0xf4, 0x04, 0xac, 0x04, 0x76, 0x48, 0x50, 0xb2, 0x51, 0x1d,
-	0x63, 0x88, 0x03, 0xcf, 0xe8, 0x8b, 0x66, 0x20, 0x75, 0x26, 0x06, 0xe5, 0x8d, 0x1a, 0x83, 0xe8,
-	0x5f, 0x61, 0x86, 0xb6, 0x0f, 0x59, 0x1f, 0x10, 0x8d, 0x47, 0x8d, 0x05, 0x47, 0x6d, 0x1c, 0x52,
-	0x3a, 0x7a, 0x07, 0xe6, 0xb7, 0xe3, 0x85, 0x47, 0x7c, 0x9b, 0xa1, 0x02, 0x34, 0xb5, 0x4b, 0x08,
-	0x8a, 0x77, 0x73, 0x32, 0x8e, 0x36, 0xa1, 0x62, 0x38, 0x8e, 0x1b, 0xd2, 0x6f, 0x90, 0xe8, 0x0d,
-	0x3e, 0x3b, 0x6d, 0x99, 0x36, 0x57, 0x13, 0x5e, 0x96, 0x25, 0xd1, 0xe0, 0x21, 0x69, 0x90, 0x83,
-	0x87, 0x04, 0xa3, 0x0e, 0x14, 0x6c, 0xa3, 0x87, 0x6d, 0x11, 0xf4, 0x9f, 0x9e, 0x6a, 0xe2, 0x22,
-	0x65, 0x63, 0xda, 0xe9, 0x27, 0x9f, 0xc9, 0xc9, 0x9f, 0x7c, 0x86, 0x2c, 0x6f, 0x42, 0x2d, 0xed,
-	0xcf, 0xfe, 0x12, 0x98, 0x67, 0xe5, 0x04, 0xa6, 0x7c, 0xc7, 0x94, 0xc9, 0x80, 0x8a, 0xe4, 0xd4,
-	0xc3, 0x30, 0xa1, 0xff, 0x44, 0x83, 0xc5, 0xac, 0xbd, 0x8b, 0xd6, 0xa5, 0x1d, 0xaf, 0xf1, 0x1e,
-	0x47, 0xc6, 0x52, 0xe7, 0xb2, 0x53, 0xb6, 0x7a, 0xb2, 0xd1, 0xdb, 0xb0, 0xe0, 0xb8, 0x26, 0xee,
-	0x1a, 0xc4, 0x80, 0x6d, 0x05, 0x61, 0x3d, 0x47, 0x7b, 0xc7, 0xb4, 0x37, 0x42, 0x28, 0xab, 0x82,
-	0x20, 0x49, 0xcf, 0x2b, 0x04, 0xfd, 0xff, 0x34, 0xa8, 0xa6, 0x5a, 0x97, 0xf7, 0x9d, 0x44, 0xc9,
-	0xa9, 0x4f, 0x6e, 0x7f, 0xa9, 0x8f, 0xfe, 0xfd, 0x1c, 0x54, 0xa4, 0xba, 0xee, 0xbe, 0x7d, 0xb8,
-	0x01, 0x55, 0xfe, 0xa5, 0xb4, 0x9c, 0x01, 0x2b, 0xa7, 0x72, 0xbc, 0x49, 0x31, 0x71, 0x52, 0x70,
-	0xc1, 0xed, 0x6d, 0xc4, 0xbc, 0xb4, 0x9a, 0xa2, 0x1d, 0xac, 0x40, 0xc1, 0x24, 0x13, 0x0b, 0x2a,
-	0x05, 0x5d, 0x87, 0xa5, 0xc8, 0x33, 0x8d, 0x10, 0x77, 0x03, 0xde, 0x73, 0xef, 0x3a, 0xd1, 0xb0,
-	0x87, 0x7d, 0xba, 0xe3, 0x67, 0x59, 0xcf, 0x85, 0x71, 0x88, 0xa6, 0xfc, 0x25, 0x4a, 0x97, 0x74,
-	0x2e, 0x66, 0xd1, 0xf5, 0x73, 0x80, 0x26, 0xfb, 0xca, 0xca, 0xf8, 0x6a, 0xfb, 0x1c, 0xdf, 0xcf,
-	0x34, 0xa8, 0xa5, 0xdb, 0xc5, 0x8f, 0x64, 0xa2, 0xf7, 0xa0, 0x1c, 0xb7, 0x7e, 0xef, 0xdb, 0x81,
-	0xe7, 0xa1, 0xe0, 0x63, 0x23, 0x70, 0x1d, 0xbe, 0x33, 0x69, 0x88, 0x61, 0x88, 0x1c, 0x62, 0x18,
-	0xa2, 0x5f, 0x85, 0x39, 0x36, 0x82, 0xef, 0x5a, 0x76, 0x88, 0x7d, 0x74, 0x06, 0x0a, 0x41, 0x68,
-	0x84, 0x38, 0xa8, 0x6b, 0xc7, 0xf3, 0x27, 0x17, 0x4e, 0x2d, 0x4d, 0x76, 0x79, 0x09, 0x99, 0x69,
-	0x65, 0x9c, 0xb2, 0x56, 0x86, 0xe8, 0xff, 0xa3, 0xc1, 0x9c, 0xdc, 0xcc, 0x7e, 0x30, 0x6a, 0xef,
-	0xf2, 0xd5, 0x3e, 0x15, 0x3e, 0xd8, 0x0f, 0x66, 0x66, 0xef, 0xce, 0xfa, 0xcf, 0x34, 0x36, 0xb2,
-	0x71, 0x17, 0xf4, 0x7e, 0xcd, 0x0f, 0x92, 0x56, 0x08, 0xd9, 0x61, 0x01, 0x0d, 0x6c, 0xfb, 0x6d,
-	0x85, 0xd0, 0xf0, 0xa7, 0x88, 0xcb, 0xe1, 0x4f, 0x21, 0xe8, 0xbf, 0xc9, 0x51, 0xcf, 0x93, 0x8e,
-	0xf7, 0xa3, 0x6e, 0x02, 0xa5, 0xb2, 0x93, 0xfc, 0x5d, 0x64, 0x27, 0x2f, 0x40, 0x91, 0x7e, 0x0e,
-	0xe2, 0xc4, 0x81, 0x4e, 0x1a, 0x81, 0xd4, 0x13, 0x47, 0x86, 0xdc, 0x26, 0x6a, 0xcd, 0xde, 0x67,
-	0xd4, 0xfa, 0x8b, 0x06, 0x0b, 0xea, 0x91, 0xc0, 0x23, 0x1f, 0xd6, 0x89, 0x05, 0x95, 0x7f, 0x48,
-	0x0b, 0xea, 0xcf, 0x1a, 0xcc, 0x2b, 0x27, 0x15, 0x8f, 0xcf, 0xab, 0xff, 0x30, 0x07, 0x4b, 0xd9,
-	0x6a, 0x1e, 0x4a, 0xf9, 0x74, 0x0e, 0x48, 0x22, 0x74, 0x3e, 0xf9, 0xb2, 0x1f, 0x9e, 0xa8, 0x9e,
-	0xe8, 0x2b, 0x88, 0x2c, 0x6a, 0xe2, 0x88, 0x41, 0x88, 0xa3, 0xeb, 0x50, 0xb1, 0xa4, 0xc3, 0x8c,
-	0x7c, 0x56, 0xcf, 0x59, 0x3e, 0xc2, 0x60, 0x35, 0xf6, 0x94, 0x83, 0x0b, 0x59, 0x55, 0xbb, 0x00,
-	0x33, 0x24, 0xf5, 0xd0, 0x77, 0xa0, 0xc8, 0xdd, 0x41, 0x2f, 0x43, 0x99, 0xee, 0x52, 0x5a, 0x11,
-	0xb0, 0xb4, 0x93, 0x7e, 0x34, 0x09, 0x98, 0xba, 0x4e, 0x50, 0x12, 0x18, 0x7a, 0x0d, 0x80, 0x24,
-	0x8e, 0x7c, 0x7f, 0xe6, 0xe8, 0xfe, 0xa4, 0x95, 0x87, 0xe7, 0x9a, 0x13, 0x9b, 0xb2, 0x1c, 0x83,
-	0xfa, 0x4f, 0x73, 0x50, 0x91, 0x8f, 0x4f, 0xee, 0xc9, 0xf8, 0xa7, 0x20, 0xaa, 0xc2, 0xae, 0x61,
-	0x9a, 0xe4, 0x2f, 0x16, 0x01, 0xb9, 0x35, 0x75, 0x90, 0xc4, 0xff, 0xab, 0x42, 0x82, 0xd5, 0x00,
-	0xf4, 0x80, 0xda, 0x4a, 0x91, 0x24, 0xab, 0xb5, 0x34, 0x6d, 0x79, 0x1b, 0x0e, 0x67, 0xaa, 0x92,
-	0x33, 0xf7, 0xd9, 0x07, 0x95, 0xb9, 0xff, 0x7c, 0x16, 0x0e, 0x67, 0x1e, 0x5b, 0x3d, 0xf2, 0x5d,
-	0xac, 0xee, 0xa0, 0xfc, 0x03, 0xd9, 0x41, 0x9f, 0x69, 0x59, 0x33, 0xcb, 0x8e, 0x00, 0xde, 0xd8,
-	0xc7, 0x59, 0xde, 0x83, 0x9a, 0x63, 0x75, 0x59, 0xce, 0xde, 0xd3, 0x9e, 0x28, 0xec, 0x77, 0x4f,
-	0xa0, 0x17, 0x59, 0x11, 0x46, 0x6d, 0x15, 0xa9, 0x2d, 0x11, 0x21, 0x52, 0xa6, 0x8a, 0x1c, 0x22,
-	0x75, 0xb9, 0x90, 0x60, 0xa5, 0x7f, 0x29, 0xa9, 0xcb, 0x39, 0x4f, 0xba, 0xfa, 0x9f, 0x93, 0xf1,
-	0x7f, 0xec, 0x1a, 0xfe, 0xab, 0x06, 0xd5, 0xd4, 0x39, 0xf6, 0xe3, 0xf3, 0x0d, 0xfa, 0x5c, 0x83,
-	0x72, 0x7c, 0x85, 0xe2, 0xbe, 0xd3, 0xd0, 0x55, 0x28, 0x60, 0x76, 0x8c, 0xcf, 0xc2, 0xdd, 0xa1,
-	0xd4, 0x35, 0x2b, 0x42, 0xe3, 0x17, 0xab, 0x52, 0x27, 0xf7, 0x1d, 0x2e, 0xa8, 0xff, 0x5a, 0x13,
-	0x09, 0x66, 0xe2, 0xd3, 0x23, 0x9d, 0x8a, 0xe4, 0x9d, 0xf2, 0xf7, 0xfa, 0x4e, 0xbf, 0x28, 0xc3,
-	0x2c, 0xe5, 0x23, 0x05, 0x60, 0x88, 0xfd, 0xa1, 0xe5, 0x18, 0x36, 0x7d, 0x9d, 0x12, 0xdb, 0xb7,
-	0x02, 0x93, 0xf7, 0xad, 0xc0, 0xd0, 0x16, 0x54, 0x93, 0xa6, 0x15, 0x55, 0x93, 0x7d, 0x7b, 0xeb,
-	0x3d, 0x95, 0x89, 0xb5, 0xa5, 0x53, 0x92, 0xea, 0xf1, 0x76, 0x8a, 0x88, 0x4c, 0x58, 0xe8, 0xbb,
-	0x4e, 0x68, 0x58, 0x0e, 0xf6, 0x99, 0xa1, 0x7c, 0xd6, 0xed, 0x95, 0xd3, 0x0a, 0x0f, 0xab, 0xfd,
-	0x55, 0x39, 0xf5, 0xf6, 0x8a, 0x4a, 0x43, 0x1f, 0xc3, 0xbc, 0x48, 0xc2, 0x99, 0x91, 0x99, 0xac,
-	0xdb, 0x2b, 0x6b, 0x32, 0x0b, 0x5b, 0xd2, 0x8a, 0x94, 0x7a, 0x7b, 0x45, 0x21, 0x21, 0x1b, 0x6a,
-	0x9e, 0x6b, 0x5e, 0x73, 0x78, 0xdb, 0xc1, 0xe8, 0xd9, 0x98, 0x77, 0x4a, 0x57, 0x26, 0x52, 0x1e,
-	0x85, 0x8b, 0x85, 0xe2, 0xb4, 0xac, 0x7a, 0x1f, 0x2c, 0x4d, 0x45, 0x1f, 0xc1, 0x9c, 0x4d, 0x6a,
-	0xa1, 0xb5, 0x5d, 0xcf, 0xf2, 0xb1, 0x99, 0x7d, 0x7b, 0xeb, 0xa2, 0xc4, 0xc1, 0x02, 0xa1, 0x2c,
-	0xa3, 0xde, 0x60, 0x91, 0x29, 0x64, 0xf6, 0x87, 0xc6, 0x6e, 0x27, 0x72, 0x82, 0xb5, 0x5d, 0x7e,
-	0x13, 0xa7, 0x98, 0x35, 0xfb, 0xeb, 0x2a, 0x13, 0x9b, 0xfd, 0x94, 0xa4, 0x3a, 0xfb, 0x29, 0x22,
-	0xba, 0x48, 0xe3, 0x3c, 0x9b, 0x12, 0x76, 0x8b, 0x6b, 0x69, 0x62, 0xb4, 0xd8, 0x6c, 0xb0, 0xa6,
-	0x05, 0x7f, 0x52, 0x94, 0xc6, 0x1a, 0xf8, 0x1c, 0xd0, 0xd7, 0xee, 0xe0, 0x30, 0xf2, 0x1d, 0x6c,
-	0xf2, 0x0b, 0x5c, 0x93, 0x73, 0xa0, 0x70, 0xc5, 0x73, 0xa0, 0xa0, 0x13, 0x73, 0xa0, 0x50, 0xc9,
-	0x9a, 0xf2, 0x5c, 0xf3, 0x2a, 0xdb, 0x32, 0x61, 0x7c, 0xad, 0xeb, 0xc9, 0x09, 0x53, 0x09, 0x0b,
-	0x5b, 0x53, 0x8a, 0x94, 0xba, 0xa6, 0x14, 0x12, 0xbf, 0x49, 0x24, 0xdf, 0x3b, 0x61, 0x23, 0x55,
-	0x99, 0x72, 0x93, 0x68, 0x82, 0x33, 0xbe, 0x49, 0x34, 0x41, 0x99, 0xb8, 0x49, 0x34, 0xc1, 0x41,
-	0xac, 0x0f, 0x0c, 0x67, 0x70, 0xc1, 0xed, 0xa9, 0xab, 0x7a, 0x2e, 0xcb, 0xfa, 0xd9, 0x0c, 0x4e,
-	0x66, 0x3d, 0x4b, 0x87, 0x6a, 0x3d, 0x8b, 0xa3, 0x5d, 0x12, 0xcd, 0x0d, 0xfd, 0x0b, 0x0d, 0xaa,
-	0xa9, 0x38, 0x83, 0xde, 0x86, 0xf8, 0xbe, 0xc4, 0xd5, 0x3d, 0x4f, 0xa4, 0xc9, 0xca, 0xfd, 0x0a,
-	0x82, 0x67, 0xdd, 0xaf, 0x20, 0x38, 0xba, 0x08, 0x10, 0x7f, 0x93, 0x6e, 0x17, 0xa4, 0x69, 0x8e,
-	0x96, 0x70, 0xca, 0x39, 0x5a, 0x82, 0xea, 0x5f, 0xe7, 0xa1, 0x24, 0x16, 0xea, 0x43, 0x29, 0xa3,
-	0x5a, 0x50, 0x1c, 0xe2, 0x80, 0xde, 0xb3, 0xc8, 0x25, 0xd9, 0x10, 0x87, 0xe4, 0x6c, 0x88, 0x43,
-	0x6a, 0xb2, 0x96, 0xbf, 0xa7, 0x64, 0x6d, 0x66, 0xdf, 0xc9, 0x1a, 0xa6, 0x67, 0xac, 0x52, 0xb8,
-	0x15, 0xa7, 0x1a, 0xb7, 0x8f, 0xe1, 0xe2, 0x04, 0x56, 0x16, 0x4c, 0x9d, 0xc0, 0xca, 0x24, 0xb4,
-	0x0d, 0x07, 0xa5, 0x93, 0x17, 0xde, 0xf9, 0x22, 0x81, 0x6f, 0x61, 0xfa, 0x81, 0x76, 0x87, 0x72,
-	0xb1, 0xed, 0xbd, 0x9d, 0x42, 0xe5, 0x6c, 0x37, 0x4d, 0xd3, 0xff, 0x90, 0x83, 0x05, 0xd5, 0xdf,
-	0x87, 0x32, 0xb1, 0x2f, 0x43, 0x19, 0xef, 0x5a, 0x61, 0xb7, 0xef, 0x9a, 0x98, 0x97, 0x8c, 0x74,
-	0x9e, 0x08, 0x78, 0xda, 0x35, 0x95, 0x79, 0x12, 0x98, 0xbc, 0x1a, 0xf2, 0xfb, 0x5a, 0x0d, 0x49,
-	0xa3, 0x70, 0xe6, 0xce, 0x8d, 0xc2, 0xec, 0x71, 0x2e, 0x3f, 0xa4, 0x71, 0xbe, 0x95, 0x83, 0x5a,
-	0x3a, 0x1a, 0x7f, 0x37, 0xb6, 0x90, 0xba, 0x1b, 0xf2, 0xfb, 0xde, 0x0d, 0xef, 0xc0, 0x3c, 0xc9,
-	0x1d, 0x8d, 0x30, 0xe4, 0x37, 0x10, 0x67, 0x68, 0xce, 0xc5, 0x62, 0x53, 0xe4, 0xac, 0x0a, 0x5c,
-	0x89, 0x4d, 0x12, 0xae, 0xff, 0x77, 0x0e, 0xe6, 0x95, 0xaf, 0xc6, 0xe3, 0x17, 0x52, 0xf4, 0x2a,
-	0xcc, 0x2b, 0xc9, 0x98, 0xfe, 0xbf, 0x6c, 0x9d, 0xa8, 0x59, 0xd0, 0xe3, 0x37, 0x2e, 0x0b, 0x30,
-	0x27, 0x67, 0x75, 0x7a, 0x1b, 0xaa, 0xa9, 0x24, 0x4c, 0x7e, 0x01, 0x6d, 0x3f, 0x2f, 0xa0, 0x2f,
-	0xc1, 0x62, 0x56, 0xee, 0xa0, 0x9f, 0x85, 0xc5, 0xac, 0xaf, 0xfa, 0xdd, 0x1b, 0xf8, 0x52, 0xa3,
-	0x16, 0x26, 0xef, 0x2a, 0x9f, 0x03, 0x70, 0xf0, 0xcd, 0xee, 0x1d, 0xcb, 0x3f, 0x36, 0x9e, 0xf8,
-	0xe6, 0x85, 0x54, 0xb5, 0x54, 0x12, 0x18, 0xd1, 0xe4, 0xda, 0x66, 0xf7, 0x8e, 0x45, 0x17, 0xd5,
-	0xe4, 0xda, 0xe6, 0x84, 0x26, 0x81, 0xe9, 0xff, 0x9f, 0x17, 0x95, 0x79, 0x72, 0xd9, 0xf7, 0x43,
-	0xa8, 0x79, 0xe2, 0xe1, 0xce, 0xde, 0xd2, 0xda, 0x24, 0xe6, 0x4f, 0x5b, 0x5a, 0x50, 0x29, 0xaa,
-	0x6e, 0x5e, 0x74, 0xe6, 0xf6, 0xa9, 0xbb, 0x93, 0xaa, 0x3e, 0x17, 0x54, 0x0a, 0xfa, 0x4f, 0x38,
-	0x28, 0xee, 0x42, 0xed, 0x60, 0xe1, 0x78, 0x7e, 0xaa, 0x72, 0x76, 0x37, 0x39, 0x16, 0x48, 0x7b,
-	0x5e, 0x4d, 0x91, 0x52, 0xea, 0xb9, 0xef, 0x33, 0xfb, 0x55, 0x9f, 0x76, 0xbe, 0x9a, 0x22, 0xe9,
-	0x9f, 0x6b, 0x50, 0x4d, 0x5d, 0x9f, 0x46, 0x67, 0xa0, 0x44, 0x7f, 0x5d, 0x75, 0xfb, 0x19, 0xa0,
-	0x0b, 0x92, 0xf2, 0x29, 0x16, 0x8a, 0x1c, 0x42, 0xaf, 0x42, 0x39, 0xbe, 0x65, 0xcd, 0xcf, 0x44,
-	0xd9, 0xe6, 0x13, 0xa0, 0xb2, 0xf9, 0x04, 0xa8, 0xff, 0x48, 0x83, 0x23, 0x53, 0xaf, 0x56, 0x3f,
-	0xea, 0x9e, 0xc1, 0x73, 0x2f, 0x42, 0x49, 0x9c, 0x5a, 0x22, 0x80, 0xc2, 0xfb, 0xd7, 0xd6, 0xae,
-	0xad, 0x9d, 0xa9, 0x1d, 0x40, 0x15, 0x28, 0x5e, 0x59, 0xbb, 0x74, 0xe6, 0xfc, 0xa5, 0xb3, 0x35,
-	0x8d, 0x3c, 0x74, 0xae, 0x5d, 0xba, 0x44, 0x1e, 0x72, 0xcf, 0x5d, 0x94, 0xef, 0x50, 0xb1, 0xef,
-	0x31, 0x9a, 0x83, 0xd2, 0xaa, 0xe7, 0xd1, 0x00, 0xc0, 0x64, 0xd7, 0x76, 0x2c, 0xb2, 0x57, 0x6b,
-	0x1a, 0x2a, 0x42, 0xfe, 0xf2, 0xe5, 0xf5, 0x5a, 0x0e, 0x2d, 0x42, 0xed, 0x0c, 0x36, 0x4c, 0xdb,
-	0x72, 0xb0, 0x88, 0x3a, 0xb5, 0x7c, 0xfb, 0xc6, 0x57, 0xdf, 0xac, 0x68, 0x5f, 0x7f, 0xb3, 0xa2,
-	0xfd, 0xfe, 0x9b, 0x15, 0xed, 0xd6, 0xb7, 0x2b, 0x07, 0xbe, 0xfe, 0x76, 0xe5, 0xc0, 0x6f, 0xbf,
-	0x5d, 0x39, 0xf0, 0xe1, 0x8b, 0xd2, 0x2f, 0x09, 0xd9, 0x3b, 0x79, 0xbe, 0x4b, 0x02, 0x2e, 0x7f,
-	0x6a, 0xa5, 0x7f, 0x3b, 0xf9, 0x65, 0xee, 0xd8, 0x2a, 0x7d, 0xbc, 0xc2, 0xf8, 0x9a, 0xe7, 0xdd,
-	0x26, 0x03, 0xe8, 0xcf, 0xdf, 0x82, 0x5e, 0x81, 0xfe, 0xcc, 0xed, 0xe5, 0xbf, 0x07, 0x00, 0x00,
-	0xff, 0xff, 0x2c, 0x75, 0x86, 0x00, 0x76, 0x39, 0x00, 0x00,
+	// 3567 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x5b, 0x49, 0x6c, 0x1c, 0xd7,
+	0x99, 0x56, 0x75, 0x93, 0xbd, 0xfc, 0xcd, 0xa5, 0xf5, 0x44, 0xd2, 0x2d, 0x5a, 0x62, 0xd3, 0x25,
+	0xcf, 0x58, 0x36, 0xec, 0xa6, 0x2d, 0x2f, 0xf0, 0x32, 0xb0, 0xc1, 0x96, 0x68, 0x2d, 0x16, 0x25,
+	0xba, 0x29, 0x7a, 0x34, 0x86, 0x07, 0xed, 0xea, 0xae, 0xc7, 0x66, 0x89, 0xd5, 0x55, 0xe5, 0x5a,
+	0x28, 0x12, 0xf0, 0x61, 0x66, 0x30, 0xe3, 0x01, 0x72, 0x70, 0x04, 0x24, 0x87, 0x00, 0x39, 0x38,
+	0xd7, 0x18, 0xc8, 0x39, 0xe7, 0x9c, 0xe2, 0x43, 0x10, 0x38, 0xb7, 0x9c, 0x3a, 0x81, 0x8d, 0x1c,
+	0xd2, 0x87, 0x9c, 0x93, 0x5c, 0x12, 0xbc, 0xad, 0xea, 0xbd, 0xea, 0x6a, 0x89, 0xda, 0x22, 0x07,
+	0x3a, 0x91, 0xf5, 0xfd, 0x6b, 0xbd, 0xe5, 0xaf, 0xff, 0xff, 0xdf, 0x6b, 0x38, 0xe9, 0xed, 0xf6,
+	0x56, 0x0c, 0xbf, 0x6f, 0x98, 0x06, 0xde, 0xc3, 0x4e, 0x18, 0xac, 0xb0, 0x3f, 0x0d, 0xcf, 0x77,
+	0x43, 0x17, 0x4d, 0xc9, 0xa4, 0x45, 0x7d, 0xf7, 0xf5, 0xa0, 0x61, 0xb9, 0x2b, 0x86, 0x67, 0xad,
+	0x74, 0x5d, 0x1f, 0xaf, 0xec, 0xbd, 0xb4, 0xd2, 0xc3, 0x0e, 0xf6, 0x8d, 0x10, 0x9b, 0x4c, 0x62,
+	0xf1, 0xb4, 0xc4, 0xe3, 0xe0, 0xf0, 0xa6, 0xeb, 0xef, 0x5a, 0x4e, 0x2f, 0x8b, 0xb3, 0xde, 0x73,
+	0xdd, 0x9e, 0x8d, 0x57, 0xe8, 0x53, 0x27, 0xda, 0x5e, 0x09, 0xad, 0x3e, 0x0e, 0x42, 0xa3, 0xef,
+	0x71, 0x86, 0xa5, 0x34, 0xc3, 0x4d, 0xdf, 0xf0, 0x3c, 0xec, 0x73, 0xe7, 0x16, 0x5f, 0x49, 0x4c,
+	0xf5, 0x8d, 0xee, 0x8e, 0xe5, 0x60, 0xff, 0x60, 0x85, 0xbe, 0x8f, 0x67, 0xad, 0xf8, 0x38, 0x70,
+	0x23, 0xbf, 0x8b, 0x47, 0xcc, 0xbe, 0xd0, 0xb3, 0xc2, 0x9d, 0xa8, 0xd3, 0xe8, 0xba, 0xfd, 0x95,
+	0x9e, 0xdb, 0x73, 0x13, 0xf5, 0xe4, 0x89, 0x3e, 0xd0, 0xff, 0x38, 0xfb, 0x9b, 0x96, 0x13, 0x62,
+	0xdf, 0x31, 0xec, 0x95, 0xa0, 0xbb, 0x83, 0xcd, 0xc8, 0xc6, 0x7e, 0xf2, 0x9f, 0xdb, 0xb9, 0x81,
+	0xbb, 0x61, 0x30, 0x02, 0x30, 0x59, 0xfd, 0xd6, 0x1c, 0x4c, 0xaf, 0x91, 0xa1, 0xdb, 0xc4, 0x9f,
+	0x44, 0xd8, 0xe9, 0x62, 0xf4, 0x2c, 0x4c, 0x7e, 0x12, 0xe1, 0x08, 0xd7, 0xb4, 0x65, 0xed, 0x74,
+	0xb9, 0x79, 0x6c, 0x38, 0xa8, 0xcf, 0x52, 0xe0, 0x79, 0xb7, 0x6f, 0x85, 0xb8, 0xef, 0x85, 0x07,
+	0x2d, 0xc6, 0x81, 0xde, 0x84, 0xa9, 0x1b, 0x6e, 0xa7, 0x1d, 0xe0, 0xb0, 0xed, 0x18, 0x7d, 0x5c,
+	0xcb, 0x51, 0x89, 0xda, 0x70, 0x50, 0x9f, 0xbb, 0xe1, 0x76, 0x36, 0x71, 0x78, 0xc5, 0xe8, 0xcb,
+	0x62, 0x90, 0xa0, 0xe8, 0x05, 0x28, 0x46, 0x01, 0xf6, 0xdb, 0x96, 0x59, 0xcb, 0x53, 0xb1, 0xb9,
+	0xe1, 0xa0, 0x5e, 0x25, 0xd0, 0x45, 0x53, 0x12, 0x29, 0x30, 0x04, 0x3d, 0x0f, 0x85, 0x9e, 0xef,
+	0x46, 0x5e, 0x50, 0x9b, 0x58, 0xce, 0x0b, 0x6e, 0x86, 0xc8, 0xdc, 0x0c, 0x41, 0x57, 0xa1, 0xc0,
+	0xd6, 0x43, 0x6d, 0x72, 0x39, 0x7f, 0xba, 0x72, 0xe6, 0xa9, 0x86, 0xbc, 0x48, 0x1a, 0xca, 0x0b,
+	0xb3, 0x27, 0xa6, 0x90, 0xd1, 0x65, 0x85, 0x7c, 0x59, 0xfd, 0xf1, 0x28, 0x4c, 0x52, 0x3e, 0x74,
+	0x15, 0x8a, 0x5d, 0x1f, 0x93, 0xc9, 0xaa, 0xa1, 0x65, 0xed, 0x74, 0xe5, 0xcc, 0x62, 0x83, 0xad,
+	0x81, 0x86, 0x98, 0xa4, 0xc6, 0x35, 0xb1, 0x48, 0x9a, 0xc7, 0x87, 0x83, 0xfa, 0x51, 0xce, 0x9e,
+	0x68, 0xbd, 0xf5, 0xbb, 0xba, 0xd6, 0x12, 0x5a, 0xd0, 0x06, 0x94, 0x83, 0xa8, 0xd3, 0xb7, 0xc2,
+	0x4b, 0x6e, 0x87, 0x8e, 0x79, 0xe5, 0xcc, 0x13, 0xaa, 0xbb, 0x9b, 0x82, 0xdc, 0x7c, 0x62, 0x38,
+	0xa8, 0x1f, 0x8b, 0xb9, 0x13, 0x8d, 0x17, 0x8e, 0xb4, 0x12, 0x25, 0x68, 0x07, 0x66, 0x7d, 0xec,
+	0xf9, 0x96, 0xeb, 0x5b, 0xa1, 0x15, 0x60, 0xa2, 0x37, 0x47, 0xf5, 0x9e, 0x54, 0xf5, 0xb6, 0x54,
+	0xa6, 0xe6, 0xc9, 0xe1, 0xa0, 0x7e, 0x3c, 0x25, 0xa9, 0xd8, 0x48, 0xab, 0x45, 0x21, 0xa0, 0x14,
+	0xb4, 0x89, 0x43, 0x3a, 0x9f, 0x95, 0x33, 0xcb, 0xb7, 0x35, 0xb6, 0x89, 0xc3, 0xe6, 0xf2, 0x70,
+	0x50, 0x3f, 0x31, 0x2a, 0xaf, 0x98, 0xcc, 0xd0, 0x8f, 0x6c, 0xa8, 0xca, 0xa8, 0x49, 0x5e, 0x70,
+	0x82, 0xda, 0x5c, 0x1a, 0x6f, 0x93, 0x70, 0x35, 0x97, 0x86, 0x83, 0xfa, 0x62, 0x5a, 0x56, 0xb1,
+	0x37, 0xa2, 0x99, 0xcc, 0x4f, 0xd7, 0x70, 0xba, 0xd8, 0x26, 0x66, 0x26, 0xb3, 0xe6, 0xe7, 0xac,
+	0x20, 0xb3, 0xf9, 0x89, 0xb9, 0xd5, 0xf9, 0x89, 0x61, 0xf4, 0x11, 0x4c, 0xc5, 0x0f, 0x64, 0xbc,
+	0x0a, 0x7c, 0x1d, 0x65, 0x2b, 0x25, 0x23, 0xb5, 0x38, 0x1c, 0xd4, 0x17, 0x64, 0x19, 0x45, 0xb5,
+	0xa2, 0x2d, 0xd1, 0x6e, 0xb3, 0x91, 0x29, 0x8e, 0xd7, 0xce, 0x38, 0x64, 0xed, 0xf6, 0xe8, 0x88,
+	0x28, 0xda, 0x88, 0x76, 0xb2, 0x89, 0xa3, 0x6e, 0x17, 0x63, 0x13, 0x9b, 0xb5, 0x52, 0x96, 0xf6,
+	0x4b, 0x12, 0x07, 0xd3, 0x2e, 0xcb, 0xa8, 0xda, 0x65, 0x0a, 0x19, 0xeb, 0x1b, 0x6e, 0x67, 0xcd,
+	0xf7, 0x5d, 0x3f, 0xa8, 0x95, 0xb3, 0xc6, 0xfa, 0x92, 0x20, 0xb3, 0xb1, 0x8e, 0xb9, 0xd5, 0xb1,
+	0x8e, 0x61, 0xee, 0x6f, 0x2b, 0x72, 0x2e, 0x63, 0x23, 0xc0, 0x66, 0x0d, 0xc6, 0xf8, 0x1b, 0x73,
+	0xc4, 0xfe, 0xc6, 0xc8, 0x88, 0xbf, 0x31, 0x05, 0x99, 0x30, 0xc3, 0x9e, 0x57, 0x83, 0xc0, 0xea,
+	0x39, 0xd8, 0xac, 0x55, 0xa8, 0xfe, 0x13, 0x59, 0xfa, 0x05, 0x4f, 0xf3, 0xc4, 0x70, 0x50, 0xaf,
+	0xa9, 0x72, 0x8a, 0x8d, 0x94, 0x4e, 0xf4, 0x31, 0x4c, 0x33, 0xa4, 0x15, 0x39, 0x8e, 0xe5, 0xf4,
+	0x6a, 0x53, 0xd4, 0xc8, 0x93, 0x59, 0x46, 0x38, 0x4b, 0xf3, 0xc9, 0xe1, 0xa0, 0xfe, 0x84, 0x22,
+	0xa5, 0x98, 0x50, 0x15, 0x92, 0x88, 0xc1, 0x80, 0x64, 0x62, 0xa7, 0xb3, 0x22, 0xc6, 0x25, 0x95,
+	0x89, 0x45, 0x8c, 0x94, 0xa4, 0x1a, 0x31, 0x52, 0xc4, 0x64, 0x3e, 0xf8, 0x24, 0xcf, 0x8c, 0x9f,
+	0x0f, 0x3e, 0xcf, 0xd2, 0x7c, 0x64, 0x4c, 0xb5, 0xa2, 0x0d, 0x7d, 0x0a, 0xe4, 0xc3, 0x73, 0x2e,
+	0xf2, 0x6c, 0xab, 0x6b, 0x84, 0xf8, 0x1c, 0x0e, 0x71, 0x97, 0x44, 0xea, 0x59, 0x6a, 0x45, 0x1f,
+	0xb1, 0x32, 0xc2, 0xd9, 0xd4, 0x87, 0x83, 0xfa, 0x52, 0x96, 0x0e, 0xc5, 0x6a, 0xa6, 0x15, 0xf4,
+	0x5f, 0x1a, 0xcc, 0x07, 0xa1, 0xe1, 0x98, 0x86, 0xed, 0x3a, 0xf8, 0xa2, 0xd3, 0xf3, 0x71, 0x10,
+	0x5c, 0x74, 0xb6, 0xdd, 0x5a, 0x95, 0xda, 0x3f, 0x95, 0x0a, 0xeb, 0x59, 0xac, 0xcd, 0x53, 0xc3,
+	0x41, 0xbd, 0x9e, 0xa9, 0x45, 0xf1, 0x20, 0xdb, 0x10, 0xda, 0x87, 0x63, 0x22, 0xab, 0xd8, 0x0a,
+	0x2d, 0xdb, 0x0a, 0x8c, 0xd0, 0x72, 0x9d, 0xda, 0x51, 0x6a, 0xff, 0xa9, 0x74, 0x74, 0x1c, 0x61,
+	0x6c, 0x3e, 0x35, 0x1c, 0xd4, 0x4f, 0x66, 0x68, 0x50, 0x6c, 0x67, 0x99, 0x48, 0x96, 0xd0, 0x86,
+	0x8f, 0x09, 0x23, 0x36, 0x6b, 0xc7, 0xc6, 0x2f, 0xa1, 0x98, 0x49, 0x5e, 0x42, 0x31, 0x98, 0xb5,
+	0x84, 0x62, 0x22, 0xb1, 0xe4, 0x19, 0x7e, 0x68, 0x11, 0xb3, 0xeb, 0x86, 0xbf, 0x8b, 0xfd, 0xda,
+	0x5c, 0x96, 0xa5, 0x0d, 0x95, 0x89, 0x59, 0x4a, 0x49, 0xaa, 0x96, 0x52, 0x44, 0x74, 0x4b, 0x03,
+	0xd5, 0x35, 0xcb, 0x75, 0x5a, 0x24, 0x6d, 0x08, 0xc8, 0xeb, 0xcd, 0x53, 0xa3, 0xcf, 0xdc, 0xe6,
+	0xf5, 0x64, 0xf6, 0xe6, 0x33, 0xc3, 0x41, 0xfd, 0xd4, 0x58, 0x6d, 0x8a, 0x23, 0xe3, 0x8d, 0xa2,
+	0xeb, 0x50, 0x21, 0x44, 0x4c, 0x13, 0x30, 0xb3, 0xb6, 0x40, 0x7d, 0x38, 0x3e, 0xea, 0x03, 0x67,
+	0xa0, 0x19, 0xc8, 0xbc, 0x24, 0xa1, 0xd8, 0x91, 0x55, 0x35, 0x8b, 0x30, 0x49, 0xe5, 0xf5, 0x61,
+	0x01, 0x8e, 0x65, 0xac, 0x0d, 0xf4, 0x36, 0x14, 0xfc, 0xc8, 0x21, 0x09, 0x1b, 0xcb, 0x52, 0x90,
+	0x6a, 0x75, 0x2b, 0xb2, 0x4c, 0x96, 0x2d, 0xfa, 0x91, 0xa3, 0xe4, 0x70, 0x93, 0x14, 0x20, 0xf2,
+	0x24, 0x5b, 0xb4, 0x4c, 0x9e, 0x8d, 0x8c, 0x95, 0xbf, 0xe1, 0x76, 0x54, 0x79, 0x0a, 0x20, 0x0c,
+	0xd3, 0x62, 0xe1, 0xb5, 0x2d, 0xb2, 0xab, 0x58, 0x9e, 0xf1, 0xb4, 0xaa, 0xe6, 0xbd, 0xa8, 0x83,
+	0x7d, 0x07, 0x87, 0x38, 0x10, 0xef, 0x40, 0xb7, 0x15, 0x8d, 0x22, 0xbe, 0x84, 0x48, 0xfa, 0xa7,
+	0x64, 0x1c, 0xfd, 0x50, 0x83, 0x5a, 0xdf, 0xd8, 0x6f, 0x0b, 0x30, 0x68, 0x6f, 0xbb, 0x7e, 0xdb,
+	0xc3, 0xbe, 0xe5, 0x9a, 0x34, 0xf9, 0xac, 0x9c, 0xf9, 0xb7, 0x3b, 0x6e, 0xa4, 0xc6, 0xba, 0xb1,
+	0x2f, 0xe0, 0xe0, 0x5d, 0xd7, 0xdf, 0xa0, 0xe2, 0x6b, 0x4e, 0xe8, 0x1f, 0x34, 0x4f, 0x7e, 0x35,
+	0xa8, 0x1f, 0x21, 0xd3, 0xd2, 0xcf, 0xe2, 0x69, 0x65, 0xc3, 0xe8, 0xfb, 0x1a, 0x2c, 0x84, 0x6e,
+	0x68, 0xd8, 0xed, 0x6e, 0xd4, 0x8f, 0x6c, 0x23, 0xb4, 0xf6, 0x70, 0x3b, 0x0a, 0x8c, 0x1e, 0xe6,
+	0x39, 0xee, 0x5b, 0x77, 0x76, 0xea, 0x1a, 0x91, 0x3f, 0x1b, 0x8b, 0x6f, 0x11, 0x69, 0xe6, 0xd3,
+	0x09, 0xee, 0xd3, 0x5c, 0x98, 0xc1, 0xd2, 0xca, 0x44, 0x17, 0x7f, 0xa2, 0xc1, 0xe2, 0xf8, 0xd7,
+	0x44, 0xa7, 0x20, 0xbf, 0x8b, 0x0f, 0x78, 0x15, 0x71, 0x74, 0x38, 0xa8, 0x4f, 0xef, 0xe2, 0x03,
+	0x69, 0xd4, 0x09, 0x15, 0xfd, 0x07, 0x4c, 0xee, 0x19, 0x76, 0x84, 0xf9, 0x92, 0x68, 0x34, 0x58,
+	0xbd, 0xd4, 0x90, 0xeb, 0xa5, 0x86, 0xb7, 0xdb, 0x23, 0x40, 0x43, 0xcc, 0x48, 0xe3, 0xfd, 0xc8,
+	0x70, 0x42, 0x2b, 0x3c, 0x60, 0xcb, 0x85, 0x2a, 0x90, 0x97, 0x0b, 0x05, 0xde, 0xcc, 0xbd, 0xae,
+	0x2d, 0x7e, 0xa1, 0xc1, 0xf1, 0xb1, 0x2f, 0xfd, 0x5d, 0xf0, 0x50, 0x6f, 0xc3, 0x04, 0x59, 0xf8,
+	0xa4, 0xbe, 0xd9, 0xb1, 0x7a, 0x3b, 0xaf, 0xbd, 0x42, 0xdd, 0x29, 0xb0, 0x72, 0x84, 0x21, 0x72,
+	0x39, 0xc2, 0x10, 0x52, 0xa3, 0xd9, 0xee, 0xcd, 0xd7, 0x5e, 0xa1, 0x4e, 0x15, 0x98, 0x11, 0x0a,
+	0xc8, 0x46, 0x28, 0xa0, 0xff, 0xad, 0x00, 0xe5, 0xb8, 0x80, 0x90, 0xf6, 0xa0, 0x76, 0x4f, 0x7b,
+	0xf0, 0x02, 0x54, 0x4d, 0x6c, 0xf2, 0x2f, 0x9f, 0xe5, 0x3a, 0x62, 0x37, 0x97, 0x59, 0x74, 0x55,
+	0x68, 0x8a, 0xfc, 0x6c, 0x8a, 0x84, 0xce, 0x40, 0x89, 0x27, 0xda, 0x07, 0x74, 0x23, 0x4f, 0x37,
+	0x17, 0x86, 0x83, 0x3a, 0x12, 0x98, 0x24, 0x1a, 0xf3, 0xa1, 0x16, 0x00, 0xab, 0x5e, 0xd7, 0x71,
+	0x68, 0xf0, 0x94, 0xbf, 0xa6, 0xbe, 0xc1, 0xd5, 0x98, 0xce, 0xea, 0xd0, 0x84, 0x5f, 0xae, 0x43,
+	0x13, 0x14, 0x7d, 0x04, 0xd0, 0x37, 0x2c, 0x87, 0xc9, 0xf1, 0xfc, 0x5e, 0x1f, 0x17, 0x52, 0xd6,
+	0x63, 0x4e, 0xa6, 0x3d, 0x91, 0x94, 0xb5, 0x27, 0x28, 0xa9, 0x16, 0x79, 0xbd, 0x5d, 0x2b, 0xd0,
+	0x5d, 0xba, 0x34, 0x4e, 0x35, 0x57, 0x3b, 0x4f, 0x2a, 0x46, 0x2e, 0x22, 0xe9, 0x14, 0x5a, 0xc8,
+	0xb0, 0xd9, 0xd6, 0x36, 0x0e, 0xad, 0x3e, 0xa6, 0x99, 0x3d, 0x1f, 0x36, 0x81, 0xc9, 0xc3, 0x26,
+	0x30, 0xf4, 0x3a, 0x80, 0x11, 0xae, 0xbb, 0x41, 0x78, 0xd5, 0xe9, 0x62, 0x9a, 0xb1, 0x97, 0x98,
+	0xfb, 0x09, 0x2a, 0xbb, 0x9f, 0xa0, 0xe8, 0x2d, 0xa8, 0x78, 0xfc, 0x23, 0xd4, 0xb1, 0x31, 0xcd,
+	0xc8, 0x4b, 0xec, 0x93, 0x22, 0xc1, 0x92, 0xac, 0xcc, 0x8d, 0xce, 0xc3, 0x6c, 0xd7, 0x75, 0xba,
+	0x91, 0xef, 0x63, 0xa7, 0x7b, 0xb0, 0x69, 0x6c, 0x63, 0x9a, 0x7d, 0x97, 0xd8, 0x52, 0x49, 0x91,
+	0xe4, 0xa5, 0x92, 0x22, 0xa1, 0x57, 0xa1, 0x1c, 0x77, 0x2f, 0x68, 0x82, 0x5d, 0xe6, 0x85, 0xb0,
+	0x00, 0x25, 0xe1, 0x84, 0x93, 0x38, 0x6f, 0x05, 0x71, 0x96, 0x46, 0x93, 0x66, 0xee, 0xbc, 0x04,
+	0xcb, 0xce, 0x4b, 0x30, 0xba, 0x08, 0x47, 0xe9, 0x77, 0xb1, 0x1d, 0x86, 0x76, 0x3b, 0xc0, 0x5d,
+	0xd7, 0x31, 0x03, 0x9a, 0x13, 0xe7, 0x99, 0xfb, 0x94, 0x78, 0x2d, 0xb4, 0x37, 0x19, 0x49, 0x76,
+	0x3f, 0x45, 0xd2, 0x7f, 0xa5, 0xc1, 0x5c, 0xd6, 0x12, 0x4a, 0x2d, 0x67, 0xed, 0x81, 0x2c, 0xe7,
+	0x0f, 0xa0, 0xe4, 0xb9, 0x66, 0x3b, 0xf0, 0x70, 0x97, 0x47, 0xac, 0xd4, 0x62, 0xde, 0x70, 0xcd,
+	0x4d, 0x0f, 0x77, 0xff, 0xdd, 0x0a, 0x77, 0x56, 0xf7, 0x5c, 0xcb, 0xbc, 0x6c, 0x05, 0x7c, 0xd5,
+	0x79, 0x8c, 0xa2, 0x64, 0x08, 0x45, 0x0e, 0x36, 0x4b, 0x50, 0x60, 0x56, 0xf4, 0x5f, 0xe7, 0xa1,
+	0x9a, 0x5e, 0xb6, 0xff, 0x4c, 0xaf, 0x82, 0xae, 0x43, 0xd1, 0x62, 0x29, 0x33, 0xcf, 0x20, 0xfe,
+	0x45, 0x8a, 0xe9, 0x8d, 0xa4, 0x21, 0xd8, 0xd8, 0x7b, 0xa9, 0xc1, 0x73, 0x6b, 0x3a, 0x04, 0x54,
+	0x33, 0x97, 0x54, 0x35, 0x73, 0x10, 0xb5, 0xa0, 0x18, 0x60, 0x7f, 0xcf, 0xea, 0x62, 0x1e, 0x9c,
+	0xea, 0xb2, 0xe6, 0xae, 0xeb, 0x63, 0xa2, 0x73, 0x93, 0xb1, 0x24, 0x3a, 0xb9, 0x8c, 0xaa, 0x93,
+	0x83, 0xe8, 0x03, 0x28, 0x77, 0x5d, 0x67, 0xdb, 0xea, 0xad, 0x1b, 0x1e, 0x0f, 0x4f, 0x27, 0xb3,
+	0xb4, 0x9e, 0x15, 0x4c, 0xbc, 0x09, 0x21, 0x1e, 0x53, 0x4d, 0x88, 0x98, 0x2b, 0x99, 0xd0, 0x3f,
+	0x4d, 0x00, 0x24, 0x93, 0x83, 0xde, 0x80, 0x0a, 0xde, 0xc7, 0xdd, 0x28, 0x74, 0x7d, 0xf1, 0x9d,
+	0xe0, 0x3d, 0x3d, 0x01, 0x2b, 0x81, 0x1d, 0x12, 0x94, 0x6c, 0x54, 0xc7, 0xe8, 0xe3, 0xc0, 0x33,
+	0xba, 0xa2, 0x19, 0x48, 0x9d, 0x89, 0x41, 0x79, 0xa3, 0xc6, 0x20, 0xfa, 0x57, 0x98, 0xa0, 0xed,
+	0x43, 0xd6, 0x07, 0x44, 0xc3, 0x41, 0x7d, 0xc6, 0x51, 0x1b, 0x87, 0x94, 0x8e, 0xde, 0x81, 0xe9,
+	0xdd, 0x78, 0xe1, 0x11, 0xdf, 0x26, 0xa8, 0x00, 0x4d, 0xed, 0x12, 0x82, 0xe2, 0xdd, 0x94, 0x8c,
+	0xa3, 0x6d, 0xa8, 0x18, 0x8e, 0xe3, 0x86, 0xf4, 0x1b, 0x24, 0x7a, 0x83, 0xcf, 0x8e, 0x5b, 0xa6,
+	0x8d, 0xd5, 0x84, 0x97, 0x65, 0x49, 0x34, 0x78, 0x48, 0x1a, 0xe4, 0xe0, 0x21, 0xc1, 0xa8, 0x05,
+	0x05, 0xdb, 0xe8, 0x60, 0x5b, 0x04, 0xfd, 0xa7, 0xc7, 0x9a, 0xb8, 0x4c, 0xd9, 0x98, 0x76, 0xfa,
+	0xc9, 0x67, 0x72, 0xf2, 0x27, 0x9f, 0x21, 0x8b, 0xdb, 0x50, 0x4d, 0xfb, 0x73, 0xb8, 0x04, 0xe6,
+	0x59, 0x39, 0x81, 0x29, 0xdf, 0x31, 0x65, 0x32, 0xa0, 0x22, 0x39, 0xf5, 0x30, 0x4c, 0xe8, 0x3f,
+	0xd5, 0x60, 0x2e, 0x6b, 0xef, 0xa2, 0x75, 0x69, 0xc7, 0x6b, 0xbc, 0xc7, 0x91, 0xb1, 0xd4, 0xb9,
+	0xec, 0x98, 0xad, 0x9e, 0x6c, 0xf4, 0x26, 0xcc, 0x38, 0xae, 0x89, 0xdb, 0x06, 0x31, 0x60, 0x5b,
+	0x41, 0x58, 0xcb, 0xd1, 0xde, 0x31, 0xed, 0x8d, 0x10, 0xca, 0xaa, 0x20, 0x48, 0xd2, 0xd3, 0x0a,
+	0x41, 0xff, 0x3f, 0x0d, 0x66, 0x53, 0xad, 0xcb, 0xfb, 0x4e, 0xa2, 0xe4, 0xd4, 0x27, 0x77, 0xb8,
+	0xd4, 0x47, 0xff, 0x41, 0x0e, 0x2a, 0x52, 0x5d, 0x77, 0xdf, 0x3e, 0xdc, 0x80, 0x59, 0xfe, 0xa5,
+	0xb4, 0x9c, 0x1e, 0x2b, 0xa7, 0x72, 0xbc, 0x49, 0x31, 0x72, 0x52, 0x70, 0xc9, 0xed, 0x6c, 0xc6,
+	0xbc, 0xb4, 0x9a, 0xa2, 0x1d, 0xac, 0x40, 0xc1, 0x24, 0x13, 0x33, 0x2a, 0x05, 0x5d, 0x87, 0x85,
+	0xc8, 0x33, 0x8d, 0x10, 0xb7, 0x03, 0xde, 0x73, 0x6f, 0x3b, 0x51, 0xbf, 0x83, 0x7d, 0xba, 0xe3,
+	0x27, 0x59, 0xcf, 0x85, 0x71, 0x88, 0xa6, 0xfc, 0x15, 0x4a, 0x97, 0x74, 0xce, 0x65, 0xd1, 0xf5,
+	0x0b, 0x80, 0x46, 0xfb, 0xca, 0xca, 0xf8, 0x6a, 0x87, 0x1c, 0xdf, 0xcf, 0x34, 0xa8, 0xa6, 0xdb,
+	0xc5, 0x8f, 0x64, 0xa2, 0x0f, 0xa0, 0x1c, 0xb7, 0x7e, 0xef, 0xdb, 0x81, 0xe7, 0xa1, 0xe0, 0x63,
+	0x23, 0x70, 0x1d, 0xbe, 0x33, 0x69, 0x88, 0x61, 0x88, 0x1c, 0x62, 0x18, 0xa2, 0x5f, 0x83, 0x29,
+	0x36, 0x82, 0xef, 0x5a, 0x76, 0x88, 0x7d, 0x74, 0x0e, 0x0a, 0x41, 0x68, 0x84, 0x38, 0xa8, 0x69,
+	0xcb, 0xf9, 0xd3, 0x33, 0x67, 0x16, 0x46, 0xbb, 0xbc, 0x84, 0xcc, 0xb4, 0x32, 0x4e, 0x59, 0x2b,
+	0x43, 0xf4, 0xff, 0xd1, 0x60, 0x4a, 0x6e, 0x66, 0x3f, 0x18, 0xb5, 0x77, 0xf9, 0x6a, 0x9f, 0x0a,
+	0x1f, 0xec, 0x07, 0x33, 0xb3, 0x77, 0x67, 0xfd, 0xe7, 0x1a, 0x1b, 0xd9, 0xb8, 0x0b, 0x7a, 0xbf,
+	0xe6, 0x7b, 0x49, 0x2b, 0x84, 0xec, 0xb0, 0x80, 0x06, 0xb6, 0xc3, 0xb6, 0x42, 0x68, 0xf8, 0x53,
+	0xc4, 0xe5, 0xf0, 0xa7, 0x10, 0xf4, 0xef, 0x4d, 0x50, 0xcf, 0x93, 0x8e, 0xf7, 0xa3, 0x6e, 0x02,
+	0xa5, 0xb2, 0x93, 0xfc, 0x5d, 0x64, 0x27, 0x2f, 0x40, 0x91, 0x7e, 0x0e, 0xe2, 0xc4, 0x81, 0x4e,
+	0x1a, 0x81, 0xd4, 0x13, 0x47, 0x86, 0xdc, 0x26, 0x6a, 0x4d, 0xde, 0x5f, 0xd4, 0x42, 0x6d, 0x38,
+	0xbe, 0x63, 0x04, 0x6d, 0x11, 0x67, 0xcd, 0xb6, 0x11, 0xb6, 0xe3, 0x38, 0x51, 0xa0, 0x65, 0xca,
+	0xd3, 0xc3, 0x41, 0x7d, 0x79, 0xc7, 0x08, 0x36, 0x05, 0xcf, 0x6a, 0xb8, 0x31, 0x1a, 0x35, 0x16,
+	0xb2, 0x39, 0xd0, 0x16, 0xcc, 0x67, 0x2b, 0x2f, 0x52, 0xcf, 0x69, 0x93, 0x37, 0xb8, 0xad, 0xe6,
+	0x63, 0x19, 0x64, 0xfd, 0x2f, 0x1a, 0xcc, 0xa8, 0x47, 0x19, 0x8f, 0x7c, 0x39, 0x8c, 0x6c, 0x84,
+	0xfc, 0x43, 0xda, 0x08, 0x7f, 0xd6, 0x60, 0x5a, 0x39, 0x61, 0x79, 0x7c, 0x5e, 0xfd, 0x47, 0x39,
+	0x58, 0xc8, 0x56, 0xf3, 0x50, 0xca, 0xbe, 0x0b, 0x40, 0x12, 0xb8, 0x8b, 0x49, 0x46, 0x32, 0x3f,
+	0x52, 0xf5, 0xd1, 0x57, 0x10, 0xd9, 0xdf, 0xc8, 0xd1, 0x88, 0x10, 0x47, 0xd7, 0xa1, 0x62, 0x49,
+	0x87, 0x30, 0xf9, 0xac, 0x5e, 0xb9, 0x7c, 0xf4, 0xc2, 0x7a, 0x03, 0x63, 0x0e, 0x5c, 0x64, 0x55,
+	0xcd, 0x02, 0x4c, 0x90, 0x94, 0x49, 0xdf, 0x83, 0x22, 0x77, 0x07, 0xbd, 0x0c, 0x65, 0x1a, 0x5d,
+	0x68, 0x25, 0xc3, 0xd2, 0x65, 0xfa, 0xb1, 0x27, 0x60, 0xea, 0x1a, 0x44, 0x49, 0x60, 0xe8, 0x35,
+	0x00, 0x92, 0xf0, 0xf2, 0xb8, 0x92, 0xa3, 0xbb, 0x93, 0x56, 0x4c, 0x9e, 0x6b, 0x8e, 0x04, 0x93,
+	0x72, 0x0c, 0xea, 0x3f, 0xcb, 0x41, 0x45, 0x3e, 0xf6, 0xb9, 0x27, 0xe3, 0x9f, 0x82, 0xa8, 0x66,
+	0xdb, 0x86, 0x69, 0x92, 0xbf, 0x58, 0x7c, 0x48, 0x56, 0xc6, 0x0e, 0x92, 0xf8, 0x7f, 0x55, 0x48,
+	0xb0, 0xda, 0x85, 0x1e, 0xac, 0x5b, 0x29, 0x92, 0x64, 0xb5, 0x9a, 0xa6, 0x2d, 0xee, 0xc2, 0x7c,
+	0xa6, 0x2a, 0xb9, 0xe2, 0x98, 0x7c, 0x50, 0x15, 0xc7, 0x2f, 0x26, 0x61, 0x3e, 0xf3, 0xb8, 0xed,
+	0x91, 0xef, 0x62, 0x75, 0x07, 0xe5, 0x1f, 0xc8, 0x0e, 0xfa, 0x4c, 0xcb, 0x9a, 0x59, 0x76, 0x74,
+	0xf1, 0xc6, 0x21, 0xce, 0x20, 0x1f, 0xd4, 0x1c, 0xab, 0xcb, 0x72, 0xf2, 0x9e, 0xf6, 0x44, 0xe1,
+	0xb0, 0x7b, 0x02, 0xbd, 0xc8, 0x8a, 0x47, 0x6a, 0xab, 0x48, 0x6d, 0x89, 0x08, 0x91, 0x32, 0x55,
+	0xe4, 0x10, 0x7a, 0x07, 0xa6, 0x85, 0x04, 0x6b, 0x59, 0x94, 0x92, 0x7e, 0x02, 0xe7, 0x49, 0x77,
+	0x2d, 0xa6, 0x64, 0xfc, 0x1f, 0xbb, 0x86, 0xff, 0xaa, 0xc1, 0x6c, 0xea, 0xfc, 0xfd, 0xf1, 0xf9,
+	0x06, 0x7d, 0xae, 0x41, 0x39, 0xbe, 0xfa, 0x71, 0xdf, 0xe9, 0xf3, 0x2a, 0x14, 0x30, 0xbb, 0x7e,
+	0xc0, 0xc2, 0xdd, 0xb1, 0xd4, 0xf5, 0x30, 0x42, 0xe3, 0x17, 0xc2, 0x52, 0x37, 0x0e, 0x5a, 0x5c,
+	0x50, 0xff, 0x8d, 0x26, 0x12, 0xe3, 0xc4, 0xa7, 0x47, 0x3a, 0x15, 0xc9, 0x3b, 0xe5, 0xef, 0xf5,
+	0x9d, 0x7e, 0x59, 0x86, 0x49, 0xca, 0x47, 0x0a, 0xd7, 0x10, 0xfb, 0x7d, 0xcb, 0x31, 0x6c, 0xfa,
+	0x3a, 0x25, 0xb6, 0x6f, 0x05, 0x26, 0xef, 0x5b, 0x81, 0xa1, 0x1d, 0x98, 0x4d, 0x9a, 0x6d, 0x54,
+	0x4d, 0xf6, 0xad, 0xb3, 0xf7, 0x54, 0x26, 0xd6, 0x4e, 0x4f, 0x49, 0xaa, 0xc7, 0xf2, 0x29, 0x22,
+	0x32, 0x61, 0xa6, 0xeb, 0x3a, 0xa1, 0x61, 0x39, 0xd8, 0x67, 0x86, 0xf2, 0x59, 0xb7, 0x6e, 0xce,
+	0x2a, 0x3c, 0xac, 0x67, 0xa1, 0xca, 0xa9, 0xb7, 0x6e, 0x54, 0x1a, 0xfa, 0x18, 0xa6, 0x45, 0xf1,
+	0xc0, 0x8c, 0x4c, 0x64, 0xdd, 0xba, 0x59, 0x93, 0x59, 0xd8, 0x92, 0x56, 0xa4, 0xd4, 0x5b, 0x37,
+	0x0a, 0x09, 0xd9, 0x50, 0xf5, 0x5c, 0x73, 0xcb, 0xe1, 0xb9, 0xb6, 0xd1, 0xb1, 0x31, 0xef, 0xf0,
+	0x2e, 0x8d, 0xa4, 0x3c, 0x0a, 0x17, 0x0b, 0xc5, 0x69, 0x59, 0xf5, 0x1e, 0x5b, 0x9a, 0x8a, 0x3e,
+	0x82, 0x29, 0x9b, 0xd4, 0x70, 0x6b, 0xfb, 0x9e, 0xe5, 0x63, 0x33, 0xfb, 0xd6, 0xd9, 0x65, 0x89,
+	0x83, 0x05, 0x42, 0x59, 0x46, 0xbd, 0x79, 0x23, 0x53, 0xc8, 0xec, 0xf7, 0x8d, 0xfd, 0x56, 0xe4,
+	0x04, 0x6b, 0xfb, 0xfc, 0x06, 0x51, 0x31, 0x6b, 0xf6, 0xd7, 0x55, 0x26, 0x36, 0xfb, 0x29, 0x49,
+	0x75, 0xf6, 0x53, 0x44, 0x74, 0x99, 0xc6, 0x79, 0x36, 0x25, 0xec, 0xf6, 0xd9, 0xc2, 0xc8, 0x68,
+	0xb1, 0xd9, 0x60, 0xcd, 0x16, 0xfe, 0xa4, 0x28, 0x8d, 0x35, 0xf0, 0x39, 0xa0, 0xaf, 0xdd, 0xc2,
+	0x61, 0xe4, 0x3b, 0xd8, 0xe4, 0x17, 0xcf, 0x46, 0xe7, 0x40, 0xe1, 0x8a, 0xe7, 0x40, 0x41, 0x47,
+	0xe6, 0x40, 0xa1, 0x92, 0x35, 0xe5, 0xb9, 0xe6, 0x35, 0xb6, 0x65, 0xc2, 0xf8, 0x3a, 0xda, 0x93,
+	0x23, 0xa6, 0x12, 0x16, 0xb6, 0xa6, 0x14, 0x29, 0x75, 0x4d, 0x29, 0x24, 0x7e, 0x03, 0x4a, 0xbe,
+	0x2f, 0xc3, 0x46, 0xaa, 0x32, 0xe6, 0x06, 0xd4, 0x08, 0x67, 0x7c, 0x03, 0x6a, 0x84, 0x32, 0x72,
+	0x03, 0x6a, 0x84, 0x83, 0x58, 0xef, 0x19, 0x4e, 0xef, 0x92, 0xdb, 0x51, 0x57, 0xf5, 0x54, 0x96,
+	0xf5, 0xf3, 0x19, 0x9c, 0xcc, 0x7a, 0x96, 0x0e, 0xd5, 0x7a, 0x16, 0x47, 0xb3, 0x24, 0x9a, 0x32,
+	0xfa, 0x17, 0x1a, 0xcc, 0xa6, 0xe2, 0x0c, 0x7a, 0x1b, 0xe2, 0x7b, 0x1e, 0xd7, 0x0e, 0x3c, 0x91,
+	0x26, 0x2b, 0xf7, 0x42, 0x08, 0x9e, 0x75, 0x2f, 0x84, 0xe0, 0xe8, 0x32, 0x40, 0xfc, 0x4d, 0xba,
+	0x5d, 0x90, 0xa6, 0x39, 0x5a, 0xc2, 0x29, 0xe7, 0x68, 0x09, 0xaa, 0x7f, 0x9d, 0x87, 0x92, 0x58,
+	0xa8, 0x0f, 0xa5, 0x8c, 0x5a, 0x81, 0x62, 0x1f, 0x07, 0xf4, 0x7e, 0x48, 0x2e, 0xc9, 0x86, 0x38,
+	0x24, 0x67, 0x43, 0x1c, 0x52, 0x93, 0xb5, 0xfc, 0x3d, 0x25, 0x6b, 0x13, 0x87, 0x4e, 0xd6, 0x30,
+	0x3d, 0x1b, 0x96, 0xc2, 0xad, 0x38, 0x8d, 0xb9, 0x7d, 0x0c, 0x17, 0x27, 0xc7, 0xb2, 0x60, 0xea,
+	0xe4, 0x58, 0x26, 0xa1, 0x5d, 0x38, 0x2a, 0x9d, 0x18, 0xf1, 0x8e, 0x1d, 0x09, 0x7c, 0x33, 0xe3,
+	0x0f, 0xe2, 0x5b, 0x94, 0x8b, 0x6d, 0xef, 0xdd, 0x14, 0x2a, 0x67, 0xbb, 0x69, 0x9a, 0xfe, 0x87,
+	0x1c, 0xcc, 0xa8, 0xfe, 0x3e, 0x94, 0x89, 0x7d, 0x19, 0xca, 0x78, 0xdf, 0x0a, 0xdb, 0x5d, 0xd7,
+	0xc4, 0xbc, 0x64, 0xa4, 0xf3, 0x44, 0xc0, 0xb3, 0xae, 0xa9, 0xcc, 0x93, 0xc0, 0xe4, 0xd5, 0x90,
+	0x3f, 0xd4, 0x6a, 0x48, 0x1a, 0x9c, 0x13, 0x77, 0x6e, 0x70, 0x66, 0x8f, 0x73, 0xf9, 0x21, 0x8d,
+	0xf3, 0xad, 0x1c, 0x54, 0xd3, 0xd1, 0xf8, 0xbb, 0xb1, 0x85, 0xd4, 0xdd, 0x90, 0x3f, 0xf4, 0x6e,
+	0x78, 0x07, 0xa6, 0x49, 0xee, 0x68, 0x84, 0x21, 0xbf, 0x39, 0x39, 0x41, 0x73, 0x2e, 0x16, 0x9b,
+	0x22, 0x67, 0x55, 0xe0, 0x4a, 0x6c, 0x92, 0x70, 0xfd, 0xbf, 0x73, 0x30, 0xad, 0x7c, 0x35, 0x1e,
+	0xbf, 0x90, 0xa2, 0xcf, 0xc2, 0xb4, 0x92, 0x8c, 0xe9, 0xff, 0xcb, 0xd6, 0x89, 0x9a, 0x05, 0x3d,
+	0x7e, 0xe3, 0x32, 0x03, 0x53, 0x72, 0x56, 0xa7, 0x37, 0x61, 0x36, 0x95, 0x84, 0xc9, 0x2f, 0xa0,
+	0x1d, 0xe6, 0x05, 0xf4, 0x05, 0x98, 0xcb, 0xca, 0x1d, 0xf4, 0xf3, 0x30, 0x97, 0xf5, 0x55, 0xbf,
+	0x7b, 0x03, 0x5f, 0x6a, 0xd4, 0xc2, 0xe8, 0x1d, 0xeb, 0x0b, 0x00, 0x0e, 0xbe, 0xd9, 0xbe, 0x63,
+	0xf9, 0xc7, 0xc6, 0x13, 0xdf, 0xbc, 0x94, 0xaa, 0x96, 0x4a, 0x02, 0x23, 0x9a, 0x5c, 0xdb, 0x6c,
+	0xdf, 0xb1, 0xe8, 0xa2, 0x9a, 0x5c, 0xdb, 0x1c, 0xd1, 0x24, 0x30, 0xfd, 0xff, 0xf3, 0xa2, 0x32,
+	0x4f, 0x2e, 0x29, 0x7f, 0x08, 0x55, 0x4f, 0x3c, 0xdc, 0xd9, 0x5b, 0x5a, 0x9b, 0xc4, 0xfc, 0x69,
+	0x4b, 0x33, 0x2a, 0x45, 0xd5, 0xcd, 0x8b, 0xce, 0xdc, 0x21, 0x75, 0xb7, 0x52, 0xd5, 0xe7, 0x8c,
+	0x4a, 0x41, 0xff, 0x09, 0x47, 0xc5, 0x1d, 0xae, 0x3d, 0x2c, 0x1c, 0xcf, 0x8f, 0x55, 0xce, 0xee,
+	0x54, 0xc7, 0x02, 0x69, 0xcf, 0x67, 0x53, 0xa4, 0x94, 0x7a, 0xee, 0xfb, 0xc4, 0x61, 0xd5, 0xa7,
+	0x9d, 0x9f, 0x4d, 0x91, 0xf4, 0xcf, 0x35, 0x98, 0x4d, 0x5d, 0xfb, 0x46, 0xe7, 0xa0, 0x44, 0x7f,
+	0x15, 0x76, 0xfb, 0x19, 0xa0, 0x0b, 0x92, 0xf2, 0x29, 0x16, 0x8a, 0x1c, 0x42, 0xaf, 0x42, 0x39,
+	0xbe, 0x1d, 0xce, 0xcf, 0x72, 0xd9, 0xe6, 0x13, 0xa0, 0xb2, 0xf9, 0x04, 0xa8, 0xff, 0x58, 0x83,
+	0xe3, 0x63, 0xaf, 0x84, 0x3f, 0xea, 0x9e, 0xc1, 0x73, 0x2f, 0x42, 0x49, 0x9c, 0xb6, 0x22, 0x80,
+	0xc2, 0xfb, 0x5b, 0x6b, 0x5b, 0x6b, 0xe7, 0xaa, 0x47, 0x50, 0x05, 0x8a, 0x1b, 0x6b, 0x57, 0xce,
+	0x5d, 0xbc, 0x72, 0xbe, 0xaa, 0x91, 0x87, 0xd6, 0xd6, 0x95, 0x2b, 0xe4, 0x21, 0xf7, 0xdc, 0x65,
+	0xf9, 0xee, 0x17, 0xfb, 0x1e, 0xa3, 0x29, 0x28, 0xad, 0x7a, 0x1e, 0x0d, 0x00, 0x4c, 0x76, 0x6d,
+	0xcf, 0x22, 0x7b, 0xb5, 0xaa, 0xa1, 0x22, 0xe4, 0xaf, 0x5e, 0x5d, 0xaf, 0xe6, 0xd0, 0x1c, 0x54,
+	0xcf, 0x61, 0xc3, 0xb4, 0x2d, 0x07, 0x8b, 0xa8, 0x53, 0xcd, 0x37, 0x6f, 0x7c, 0xf5, 0xcd, 0x92,
+	0xf6, 0xf5, 0x37, 0x4b, 0xda, 0xef, 0xbf, 0x59, 0xd2, 0x6e, 0x7d, 0xbb, 0x74, 0xe4, 0xeb, 0x6f,
+	0x97, 0x8e, 0xfc, 0xf6, 0xdb, 0xa5, 0x23, 0x1f, 0xbe, 0x28, 0xfd, 0x02, 0x92, 0xbd, 0x93, 0xe7,
+	0xbb, 0x24, 0xe0, 0xf2, 0xa7, 0x95, 0xf4, 0x6f, 0x42, 0xbf, 0xcc, 0x9d, 0x5c, 0xa5, 0x8f, 0x1b,
+	0x8c, 0xaf, 0x71, 0xd1, 0x6d, 0x30, 0x80, 0xfe, 0x6c, 0x2f, 0xe8, 0x14, 0xe8, 0xcf, 0xf3, 0x5e,
+	0xfe, 0x7b, 0x00, 0x00, 0x00, 0xff, 0xff, 0x65, 0xc1, 0xed, 0xaa, 0x4e, 0x3a, 0x00, 0x00,
 }
 
 func (m *EventSequence) Marshal() (dAtA []byte, err error) {
@@ -5288,6 +5317,21 @@ func (m *JobRunLeased) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	if m.ScheduledAtPriority != 0 {
+		i = encodeVarintEvents(dAtA, i, uint64(m.ScheduledAtPriority))
+		i--
+		dAtA[i] = 0x38
+	}
+	if m.HasScheduledAtPriority {
+		i--
+		if m.HasScheduledAtPriority {
+			dAtA[i] = 1
+		} else {
+			dAtA[i] = 0
+		}
+		i--
+		dAtA[i] = 0x30
+	}
 	if m.UpdateSequenceNumber != 0 {
 		i = encodeVarintEvents(dAtA, i, uint64(m.UpdateSequenceNumber))
 		i--
@@ -7595,6 +7639,12 @@ func (m *JobRunLeased) Size() (n int) {
 	if m.UpdateSequenceNumber != 0 {
 		n += 1 + sovEvents(uint64(m.UpdateSequenceNumber))
 	}
+	if m.HasScheduledAtPriority {
+		n += 2
+	}
+	if m.ScheduledAtPriority != 0 {
+		n += 1 + sovEvents(uint64(m.ScheduledAtPriority))
+	}
 	return n
 }
 
@@ -12298,6 +12348,45 @@ func (m *JobRunLeased) Unmarshal(dAtA []byte) error {
 					break
 				}
 			}
+		case 6:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field HasScheduledAtPriority", wireType)
+			}
+			var v int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEvents
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				v |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			m.HasScheduledAtPriority = bool(v != 0)
+		case 7:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field ScheduledAtPriority", wireType)
+			}
+			m.ScheduledAtPriority = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowEvents
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.ScheduledAtPriority |= int32(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
 		default:
 			iNdEx = preIndex
 			skippy, err := skipEvents(dAtA[iNdEx:])
diff --git a/pkg/armadaevents/events.proto b/pkg/armadaevents/events.proto
index dff09a67..e2d2a0b9 100644
--- a/pkg/armadaevents/events.proto
+++ b/pkg/armadaevents/events.proto
@@ -6,6 +6,7 @@ option csharp_namespace = "ArmadaProject.Io.ArmadaEvents";
 import "k8s.io/api/core/v1/generated.proto";
 import "k8s.io/api/networking/v1/generated.proto";
 import "google/protobuf/timestamp.proto";
+import "google/protobuf/wrappers.proto";
 import "k8s.io/apimachinery/pkg/api/resource/generated.proto";
 import "github.com/gogo/protobuf/gogoproto/gogo.proto";
 import "internal/scheduler/schedulerobjects/schedulerobjects.proto";
@@ -290,6 +291,17 @@ message JobRunLeased {
     string node_id = 4;
     // Used by the scheduler to maintain a consistent state
     int32 update_sequence_number = 5;
+    // This bool is here because it is the most ergonomic way of distinguishing
+    // the case where `scheduled_at_priority` was explicitly set to zero from
+    // the case where it was not set at all.
+    bool has_scheduled_at_priority = 6;
+    // Priority class priority that this job was scheduled at.
+    //
+    // This is usually equal to the priority of the job's priority class, except
+    // in the case where the job was scheduled as an away job; this field is
+    // used to distinguish this case from the case where the job was scheduled
+    // as a home job.
+    int32 scheduled_at_priority = 7;
 }
 
 // Indicates that a job has been assigned to nodes by Kubernetes.


Test patch:
diff --git a/internal/armada/server/submit_test.go b/internal/armada/server/submit_test.go
index b0be610c..9521cb28 100644
--- a/internal/armada/server/submit_test.go
+++ b/internal/armada/server/submit_test.go
@@ -1527,7 +1527,7 @@ func withSubmitServerAndRepos(action func(s *SubmitServer, jobRepo repository.Jo
 		MaxPodSpecSizeBytes: 65535,
 		Preemption: configuration.PreemptionConfig{
 			DefaultPriorityClass: "high",
-			PriorityClasses:      map[string]schedulertypes.PriorityClass{"high": {0, false, nil, nil}},
+			PriorityClasses:      map[string]schedulertypes.PriorityClass{"high": {Priority: 0, Preemptible: false}},
 		},
 		MinTerminationGracePeriod: time.Duration(30 * time.Second),
 		MaxTerminationGracePeriod: time.Duration(300 * time.Second),
diff --git a/internal/common/ingest/testfixtures/event.go b/internal/common/ingest/testfixtures/event.go
index d4bda96d..02aa355c 100644
--- a/internal/common/ingest/testfixtures/event.go
+++ b/internal/common/ingest/testfixtures/event.go
@@ -3,7 +3,7 @@ package testfixtures
 import (
 	"time"
 
-	"github.com/golang/protobuf/proto"
+	"github.com/gogo/protobuf/proto"
 	"github.com/google/uuid"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
@@ -77,6 +77,8 @@ const (
 	PartitionMarkerPartitionId = 456
 )
 
+var ScheduledAtPriority = int32(15)
+
 var Submit = &armadaevents.EventSequence_Event{
 	Created: &testfixtures.BaseTime,
 	Event: &armadaevents.EventSequence_Event_SubmitJob{
@@ -199,11 +201,13 @@ var Leased = &armadaevents.EventSequence_Event{
 	Created: &testfixtures.BaseTime,
 	Event: &armadaevents.EventSequence_Event_JobRunLeased{
 		JobRunLeased: &armadaevents.JobRunLeased{
-			RunId:                RunIdProto,
-			JobId:                JobIdProto,
-			ExecutorId:           ExecutorId,
-			NodeId:               NodeName,
-			UpdateSequenceNumber: 1,
+			RunId:                  RunIdProto,
+			JobId:                  JobIdProto,
+			ExecutorId:             ExecutorId,
+			NodeId:                 NodeName,
+			HasScheduledAtPriority: true,
+			ScheduledAtPriority:    15,
+			UpdateSequenceNumber:   1,
 		},
 	},
 }
diff --git a/internal/scheduler/adapters/adapters_test.go b/internal/scheduler/adapters/adapters_test.go
index 93b57637..90e6df03 100644
--- a/internal/scheduler/adapters/adapters_test.go
+++ b/internal/scheduler/adapters/adapters_test.go
@@ -18,10 +18,10 @@ import (
 
 var (
 	priorityByPriorityClassName = map[string]types.PriorityClass{
-		"priority-0": {0, true, nil, nil},
-		"priority-1": {1, true, nil, nil},
-		"priority-2": {2, true, nil, nil},
-		"priority-3": {3, false, nil, nil},
+		"priority-0": {Priority: 0, Preemptible: true},
+		"priority-1": {Priority: 1, Preemptible: true},
+		"priority-2": {Priority: 2, Preemptible: true},
+		"priority-3": {Priority: 3, Preemptible: false},
 	}
 
 	priority int32 = 1
diff --git a/internal/scheduler/gang_scheduler_test.go b/internal/scheduler/gang_scheduler_test.go
index f506dbed..b480ab3a 100644
--- a/internal/scheduler/gang_scheduler_test.go
+++ b/internal/scheduler/gang_scheduler_test.go
@@ -3,15 +3,19 @@ package scheduler
 import (
 	"testing"
 
+	"github.com/oklog/ulid"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/exp/maps"
 	"golang.org/x/time/rate"
+	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 
 	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
+	"github.com/armadaproject/armada/internal/common/types"
+	"github.com/armadaproject/armada/internal/common/util"
 	schedulerconstraints "github.com/armadaproject/armada/internal/scheduler/constraints"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
 	"github.com/armadaproject/armada/internal/scheduler/fairness"
@@ -423,6 +427,73 @@ func TestGangScheduler(t *testing.T) {
 			ExpectedScheduledJobs:    []int{2},
 			ExpectedNodeUniformity:   map[int]string{0: "b"},
 		},
+		"AwayNodeTypes": {
+			SchedulingConfig: func() configuration.SchedulingConfig {
+				config := testfixtures.TestSchedulingConfig()
+				config.Preemption.PriorityClasses = map[string]types.PriorityClass{
+					"armada-preemptible-away": {
+						Priority:    30000,
+						Preemptible: true,
+
+						AwayNodeTypes: []types.AwayNodeType{
+							{Priority: 29000, WellKnownNodeTypeName: "node-type-a"},
+							{Priority: 29000, WellKnownNodeTypeName: "node-type-b"},
+						},
+					},
+					"armada-preemptible-away-both": {
+						Priority:    30000,
+						Preemptible: true,
+
+						AwayNodeTypes: []types.AwayNodeType{
+							{Priority: 29000, WellKnownNodeTypeName: "node-type-ab"},
+						},
+					},
+				}
+				config.Preemption.DefaultPriorityClass = "armada-preemptible-away"
+				config.WellKnownNodeTypes = []configuration.WellKnownNodeType{
+					{
+						Name: "node-type-a",
+						Taints: []v1.Taint{
+							{Key: "taint-a", Value: "true", Effect: v1.TaintEffectNoSchedule},
+						},
+					},
+					{
+						Name: "node-type-b",
+						Taints: []v1.Taint{
+							{Key: "taint-b", Value: "true", Effect: v1.TaintEffectNoSchedule},
+						},
+					},
+					{
+						Name: "node-type-ab",
+						Taints: []v1.Taint{
+							{Key: "taint-a", Value: "true", Effect: v1.TaintEffectNoSchedule},
+							{Key: "taint-b", Value: "true", Effect: v1.TaintEffectNoSchedule},
+						},
+					},
+				}
+				return config
+			}(),
+			Nodes: func() []*schedulerobjects.Node {
+				nodes := testfixtures.N8GpuNodes(1, []int32{29000, 30000})
+				for _, node := range nodes {
+					node.Taints = []v1.Taint{
+						{Key: "taint-a", Value: "true", Effect: v1.TaintEffectNoSchedule},
+						{Key: "taint-b", Value: "true", Effect: v1.TaintEffectNoSchedule},
+					}
+				}
+				return nodes
+			}(),
+			Gangs: func() (gangs [][]*jobdb.Job) {
+				var jobId ulid.ULID
+				jobId = util.ULID()
+				gangs = append(gangs, []*jobdb.Job{testfixtures.TestJob("A", jobId, "armada-preemptible-away", testfixtures.Test1Cpu4GiPodReqs("A", jobId, 30000))})
+				jobId = util.ULID()
+				gangs = append(gangs, []*jobdb.Job{testfixtures.TestJob("A", jobId, "armada-preemptible-away-both", testfixtures.Test1Cpu4GiPodReqs("A", jobId, 30000))})
+				return
+			}(),
+			ExpectedScheduledIndices: []int{1},
+			ExpectedScheduledJobs:    []int{0, 1},
+		},
 	}
 	for name, tc := range tests {
 		t.Run(name, func(t *testing.T) {
@@ -431,11 +502,12 @@ func TestGangScheduler(t *testing.T) {
 				nodesById[node.Id] = node
 			}
 			nodeDb, err := nodedb.NewNodeDb(
-				testfixtures.TestPriorityClasses,
-				testfixtures.TestMaxExtraNodesToConsider,
+				tc.SchedulingConfig.Preemption.PriorityClasses,
+				tc.SchedulingConfig.MaxExtraNodesToConsider,
 				tc.SchedulingConfig.IndexedResources,
-				testfixtures.TestIndexedTaints,
+				tc.SchedulingConfig.IndexedTaints,
 				tc.SchedulingConfig.IndexedNodeLabels,
+				tc.SchedulingConfig.WellKnownNodeTypes,
 			)
 			require.NoError(t, err)
 			txn := nodeDb.Txn(true)
@@ -496,7 +568,7 @@ func TestGangScheduler(t *testing.T) {
 			var actualScheduledIndices []int
 			scheduledGangs := 0
 			for i, gang := range tc.Gangs {
-				jctxs := schedulercontext.JobSchedulingContextsFromJobs(testfixtures.TestPriorityClasses, gang, GangIdAndCardinalityFromAnnotations)
+				jctxs := schedulercontext.JobSchedulingContextsFromJobs(tc.SchedulingConfig.Preemption.PriorityClasses, gang, GangIdAndCardinalityFromAnnotations)
 				gctx := schedulercontext.NewGangSchedulingContext(jctxs)
 				ok, reason, err := sch.Schedule(armadacontext.Background(), gctx)
 				require.NoError(t, err)
diff --git a/internal/scheduler/jobdb/comparison_test.go b/internal/scheduler/jobdb/comparison_test.go
index d9da271f..9b9cd81e 100644
--- a/internal/scheduler/jobdb/comparison_test.go
+++ b/internal/scheduler/jobdb/comparison_test.go
@@ -51,7 +51,7 @@ func TestJobPriorityComparer(t *testing.T) {
 		},
 		"Running jobs come before queued jobs": {
 			a:        &Job{id: "a", priority: 1},
-			b:        (&Job{id: "b", priority: 2}).WithNewRun("", "", ""),
+			b:        (&Job{id: "b", priority: 2}).WithNewRun("", "", "", 0),
 			expected: 1,
 		},
 		"Running jobs are ordered third by runtime": {
diff --git a/internal/scheduler/jobdb/job_run_test.go b/internal/scheduler/jobdb/job_run_test.go
index 282914ce..115baab0 100644
--- a/internal/scheduler/jobdb/job_run_test.go
+++ b/internal/scheduler/jobdb/job_run_test.go
@@ -34,6 +34,7 @@ var (
 		SchedulingKeyGenerator,
 		1024,
 	)
+	scheduledAtPriority = int32(5)
 )
 
 var baseJobRun = jobDb.CreateRun(
@@ -43,6 +44,7 @@ var baseJobRun = jobDb.CreateRun(
 	"test-executor",
 	"test-nodeId",
 	"test-nodeName",
+	&scheduledAtPriority,
 	false,
 	false,
 	false,
@@ -103,6 +105,7 @@ func TestDeepCopy(t *testing.T) {
 		"executor",
 		"nodeId",
 		"nodeName",
+		&scheduledAtPriority,
 		true,
 		true,
 		true,
@@ -117,6 +120,7 @@ func TestDeepCopy(t *testing.T) {
 		"executor",
 		"nodeId",
 		"nodeName",
+		&scheduledAtPriority,
 		true,
 		true,
 		true,
diff --git a/internal/scheduler/jobdb/job_test.go b/internal/scheduler/jobdb/job_test.go
index d62b2e1b..4cbd04cc 100644
--- a/internal/scheduler/jobdb/job_test.go
+++ b/internal/scheduler/jobdb/job_test.go
@@ -125,22 +125,28 @@ func TestJob_TestInTerminalState(t *testing.T) {
 
 func TestJob_TestHasRuns(t *testing.T) {
 	assert.Equal(t, false, baseJob.HasRuns())
-	assert.Equal(t, true, baseJob.WithNewRun("test-executor", "test-nodeId", "nodeId").HasRuns())
+	assert.Equal(t, true, baseJob.WithNewRun("test-executor", "test-nodeId", "nodeId", 5).HasRuns())
 }
 
 func TestJob_TestWithNewRun(t *testing.T) {
-	jobWithRun := baseJob.WithNewRun("test-executor", "test-nodeId", "nodeId")
+	scheduledAtPriority := int32(10)
+	jobWithRun := baseJob.WithNewRun("test-executor", "test-nodeId", "nodeId", scheduledAtPriority)
 	assert.Equal(t, true, jobWithRun.HasRuns())
 	run := jobWithRun.LatestRun()
 	assert.NotNil(t, run)
-	assert.Equal(t, &JobRun{
-		id:       run.id,
-		jobId:    "test-job",
-		created:  run.created,
-		executor: "test-executor",
-		nodeId:   "test-nodeId",
-		nodeName: "nodeId",
-	}, run)
+	assert.Equal(
+		t,
+		&JobRun{
+			id:                  run.id,
+			jobId:               "test-job",
+			created:             run.created,
+			executor:            "test-executor",
+			nodeId:              "test-nodeId",
+			nodeName:            "nodeId",
+			scheduledAtPriority: &scheduledAtPriority,
+		},
+		run,
+	)
 }
 
 func TestJob_TestWithUpdatedRun_NewRun(t *testing.T) {
diff --git a/internal/scheduler/jobdb/jobdb_test.go b/internal/scheduler/jobdb/jobdb_test.go
index 03fcedc9..a795ffc8 100644
--- a/internal/scheduler/jobdb/jobdb_test.go
+++ b/internal/scheduler/jobdb/jobdb_test.go
@@ -73,8 +73,8 @@ func TestJobDb_TestGetById(t *testing.T) {
 
 func TestJobDb_TestGetByRunId(t *testing.T) {
 	jobDb := NewTestJobDb()
-	job1 := newJob().WithNewRun("executor", "nodeId", "nodeName")
-	job2 := newJob().WithNewRun("executor", "nodeId", "nodeName")
+	job1 := newJob().WithNewRun("executor", "nodeId", "nodeName", 5)
+	job2 := newJob().WithNewRun("executor", "nodeId", "nodeName", 10)
 	txn := jobDb.WriteTxn()
 
 	err := txn.Upsert([]*Job{job1, job2})
@@ -90,8 +90,8 @@ func TestJobDb_TestGetByRunId(t *testing.T) {
 
 func TestJobDb_TestHasQueuedJobs(t *testing.T) {
 	jobDb := NewTestJobDb()
-	job1 := newJob().WithNewRun("executor", "nodeId", "nodeName")
-	job2 := newJob().WithNewRun("executor", "nodeId", "nodeName")
+	job1 := newJob().WithNewRun("executor", "nodeId", "nodeName", 5)
+	job2 := newJob().WithNewRun("executor", "nodeId", "nodeName", 10)
 	txn := jobDb.WriteTxn()
 
 	err := txn.Upsert([]*Job{job1, job2})
@@ -161,8 +161,8 @@ func TestJobDb_TestQueuedJobs(t *testing.T) {
 
 func TestJobDb_TestGetAll(t *testing.T) {
 	jobDb := NewTestJobDb()
-	job1 := newJob().WithNewRun("executor", "nodeId", "nodeName")
-	job2 := newJob().WithNewRun("executor", "nodeId", "nodeName")
+	job1 := newJob().WithNewRun("executor", "nodeId", "nodeName", 5)
+	job2 := newJob().WithNewRun("executor", "nodeId", "nodeName", 10)
 	txn := jobDb.WriteTxn()
 	assert.Equal(t, []*Job{}, txn.GetAll())
 
@@ -200,8 +200,8 @@ func TestJobDb_TestTransactions(t *testing.T) {
 
 func TestJobDb_TestBatchDelete(t *testing.T) {
 	jobDb := NewTestJobDb()
-	job1 := newJob().WithQueued(true).WithNewRun("executor", "nodeId", "nodeName")
-	job2 := newJob().WithQueued(true).WithNewRun("executor", "nodeId", "nodeName")
+	job1 := newJob().WithQueued(true).WithNewRun("executor", "nodeId", "nodeName", 5)
+	job2 := newJob().WithQueued(true).WithNewRun("executor", "nodeId", "nodeName", 10)
 	txn := jobDb.WriteTxn()
 
 	// Insert Job
diff --git a/internal/scheduler/nodedb/nodedb_test.go b/internal/scheduler/nodedb/nodedb_test.go
index 83e1b885..dabd9ab8 100644
--- a/internal/scheduler/nodedb/nodedb_test.go
+++ b/internal/scheduler/nodedb/nodedb_test.go
@@ -130,35 +130,35 @@ func TestNodeBindingEvictionUnbinding(t *testing.T) {
 	request := schedulerobjects.ResourceListFromV1ResourceList(job.GetResourceRequirements().Requests)
 	jobId := job.GetId()
 
-	boundNode, err := bindJobToNode(testfixtures.TestPriorityClasses, job, entry)
+	boundNode, err := nodeDb.bindJobToNode(entry, job, job.PodRequirements().Priority)
 	require.NoError(t, err)
 
-	unboundNode, err := UnbindJobFromNode(testfixtures.TestPriorityClasses, job, boundNode)
+	unboundNode, err := nodeDb.UnbindJobFromNode(testfixtures.TestPriorityClasses, job, boundNode)
 	require.NoError(t, err)
 
-	unboundMultipleNode, err := UnbindJobsFromNode(testfixtures.TestPriorityClasses, []interfaces.LegacySchedulerJob{job}, boundNode)
+	unboundMultipleNode, err := nodeDb.UnbindJobsFromNode(testfixtures.TestPriorityClasses, []interfaces.LegacySchedulerJob{job}, boundNode)
 	require.NoError(t, err)
 
-	evictedJobs, evictedNode, err := EvictJobsFromNode(testfixtures.TestPriorityClasses, jobFilter, []interfaces.LegacySchedulerJob{job}, boundNode)
+	evictedJobs, evictedNode, err := nodeDb.EvictJobsFromNode(testfixtures.TestPriorityClasses, jobFilter, []interfaces.LegacySchedulerJob{job}, boundNode)
 	require.NoError(t, err)
 	assert.Equal(t, []interfaces.LegacySchedulerJob{job}, evictedJobs)
 
-	evictedUnboundNode, err := UnbindJobFromNode(testfixtures.TestPriorityClasses, job, evictedNode)
+	evictedUnboundNode, err := nodeDb.UnbindJobFromNode(testfixtures.TestPriorityClasses, job, evictedNode)
 	require.NoError(t, err)
 
-	evictedBoundNode, err := bindJobToNode(testfixtures.TestPriorityClasses, job, evictedNode)
+	evictedBoundNode, err := nodeDb.bindJobToNode(evictedNode, job, job.PodRequirements().Priority)
 	require.NoError(t, err)
 
-	_, _, err = EvictJobsFromNode(testfixtures.TestPriorityClasses, jobFilter, []interfaces.LegacySchedulerJob{job}, entry)
+	_, _, err = nodeDb.EvictJobsFromNode(testfixtures.TestPriorityClasses, jobFilter, []interfaces.LegacySchedulerJob{job}, entry)
 	require.Error(t, err)
 
-	_, err = UnbindJobFromNode(testfixtures.TestPriorityClasses, job, entry)
+	_, err = nodeDb.UnbindJobFromNode(testfixtures.TestPriorityClasses, job, entry)
 	require.NoError(t, err)
 
-	_, err = bindJobToNode(testfixtures.TestPriorityClasses, job, boundNode)
+	_, err = nodeDb.bindJobToNode(boundNode, job, job.PodRequirements().Priority)
 	require.Error(t, err)
 
-	_, _, err = EvictJobsFromNode(testfixtures.TestPriorityClasses, jobFilter, []interfaces.LegacySchedulerJob{job}, evictedNode)
+	_, _, err = nodeDb.EvictJobsFromNode(testfixtures.TestPriorityClasses, jobFilter, []interfaces.LegacySchedulerJob{job}, evictedNode)
 	require.Error(t, err)
 
 	assertNodeAccountingEqual(t, entry, unboundNode)
@@ -295,7 +295,7 @@ func TestEviction(t *testing.T) {
 			for i, job := range jobs {
 				existingJobs[i] = job
 			}
-			actualEvictions, _, err := EvictJobsFromNode(testfixtures.TestPriorityClasses, tc.jobFilter, existingJobs, entry)
+			actualEvictions, _, err := nodeDb.EvictJobsFromNode(testfixtures.TestPriorityClasses, tc.jobFilter, existingJobs, entry)
 			require.NoError(t, err)
 			expectedEvictions := make([]interfaces.LegacySchedulerJob, 0, len(tc.expectedEvictions))
 			for _, i := range tc.expectedEvictions {
@@ -567,6 +567,7 @@ func benchmarkUpsert(nodes []*schedulerobjects.Node, b *testing.B) {
 		testfixtures.TestResources,
 		testfixtures.TestIndexedTaints,
 		testfixtures.TestIndexedNodeLabels,
+		testfixtures.TestWellKnownNodeTypes,
 	)
 	require.NoError(b, err)
 	txn := nodeDb.Txn(true)
@@ -605,6 +606,7 @@ func benchmarkScheduleMany(b *testing.B, nodes []*schedulerobjects.Node, jobs []
 		testfixtures.TestResources,
 		testfixtures.TestIndexedTaints,
 		testfixtures.TestIndexedNodeLabels,
+		testfixtures.TestWellKnownNodeTypes,
 	)
 	require.NoError(b, err)
 	txn := nodeDb.Txn(true)
@@ -728,6 +730,7 @@ func newNodeDbWithNodes(nodes []*schedulerobjects.Node) (*NodeDb, error) {
 		testfixtures.TestResources,
 		testfixtures.TestIndexedTaints,
 		testfixtures.TestIndexedNodeLabels,
+		testfixtures.TestWellKnownNodeTypes,
 	)
 	if err != nil {
 		return nil, err
diff --git a/internal/scheduler/preempting_queue_scheduler_test.go b/internal/scheduler/preempting_queue_scheduler_test.go
index 987eb63f..1e6ef190 100644
--- a/internal/scheduler/preempting_queue_scheduler_test.go
+++ b/internal/scheduler/preempting_queue_scheduler_test.go
@@ -19,6 +19,7 @@ import (
 	"github.com/armadaproject/armada/internal/common/logging"
 	armadamaps "github.com/armadaproject/armada/internal/common/maps"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
+	"github.com/armadaproject/armada/internal/common/types"
 	"github.com/armadaproject/armada/internal/common/util"
 	schedulerconstraints "github.com/armadaproject/armada/internal/scheduler/constraints"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
@@ -33,10 +34,7 @@ import (
 func TestEvictOversubscribed(t *testing.T) {
 	config := testfixtures.TestSchedulingConfig()
 
-	var priorities []int32
-	for _, pc := range config.Preemption.PriorityClasses {
-		priorities = append(priorities, pc.Priority)
-	}
+	priorities := types.AllowedPriorities(config.Preemption.PriorityClasses)
 
 	jobs := append(
 		testfixtures.N1Cpu4GiJobs("A", config.Preemption.DefaultPriorityClass, 20),
@@ -57,14 +55,13 @@ func TestEvictOversubscribed(t *testing.T) {
 
 	evictor := NewOversubscribedEvictor(
 		NewSchedulerJobRepositoryAdapter(jobDbTxn),
+		nodeDb,
 		config.Preemption.PriorityClasses,
 		config.Preemption.DefaultPriorityClass,
 		1,
 		nil,
 	)
-	it, err := nodedb.NewNodesIterator(nodeDbTxn)
-	require.NoError(t, err)
-	result, err := evictor.Evict(armadacontext.Background(), it)
+	result, err := evictor.Evict(armadacontext.Background(), nodeDbTxn)
 	require.NoError(t, err)
 
 	for nodeId, node := range result.AffectedNodesById {
@@ -1484,6 +1481,272 @@ func TestPreemptingQueueScheduler(t *testing.T) {
 			},
 			PriorityFactorByQueue: map[string]float64{"A": 1, "B": 1},
 		},
+		"home-away preemption, away jobs first": {
+			SchedulingConfig: func() configuration.SchedulingConfig {
+				config := testfixtures.TestSchedulingConfig()
+				config.Preemption.PriorityClasses = map[string]types.PriorityClass{
+					"armada-preemptible-away": {
+						Priority:    30000,
+						Preemptible: true,
+
+						AwayNodeTypes: []types.AwayNodeType{{Priority: 29000, WellKnownNodeTypeName: "gpu"}},
+					},
+					"armada-preemptible": {
+						Priority:    30000,
+						Preemptible: true,
+					},
+				}
+				config.Preemption.DefaultPriorityClass = "armada-preemptible"
+				config.WellKnownNodeTypes = []configuration.WellKnownNodeType{
+					{
+						Name:   "gpu",
+						Taints: []v1.Taint{{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule}},
+					},
+				}
+				return config
+			}(),
+			Nodes: func() []*schedulerobjects.Node {
+				nodes := testfixtures.N8GpuNodes(2, []int32{29000, 30000})
+				for _, node := range nodes {
+					node.Taints = []v1.Taint{{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule}}
+				}
+				return nodes
+			}(),
+			Rounds: []SchedulingRound{
+				{
+					JobsByQueue: map[string][]*jobdb.Job{
+						"A": func() (jobs []*jobdb.Job) {
+							for i := 0; i < 96; i++ {
+								jobId := util.ULID()
+								jobs = append(jobs, testfixtures.TestJob("A", jobId, "armada-preemptible-away", testfixtures.Test1Cpu4GiPodReqs("A", jobId, 30000)))
+							}
+							return
+						}(),
+					},
+					ExpectedScheduledIndices: map[string][]int{"A": testfixtures.IntRange(0, 95)},
+					ExpectedPreemptedIndices: nil,
+				},
+				{
+					JobsByQueue: map[string][]*jobdb.Job{
+						"B": func() (jobs []*jobdb.Job) {
+							for i := 0; i < 12; i++ {
+								jobId := util.ULID()
+								req := testfixtures.Test1GpuPodReqs("B", jobId, 30000)
+								req.Tolerations = append(req.Tolerations, v1.Toleration{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule})
+								jobs = append(jobs, testfixtures.TestJob("B", jobId, "armada-preemptible", req))
+							}
+							return
+						}(),
+					},
+					ExpectedScheduledIndices: map[string][]int{"B": testfixtures.IntRange(0, 11)},
+					ExpectedPreemptedIndices: map[string]map[int][]int{"A": {0: testfixtures.IntRange(32, 95)}},
+				},
+			},
+			PriorityFactorByQueue: map[string]float64{
+				"A": 1,
+				"B": 1,
+			},
+		},
+		"home-away preemption, home jobs first": {
+			SchedulingConfig: func() configuration.SchedulingConfig {
+				config := testfixtures.TestSchedulingConfig()
+				config.Preemption.PriorityClasses = map[string]types.PriorityClass{
+					"armada-preemptible-away": {
+						Priority:    30000,
+						Preemptible: true,
+
+						AwayNodeTypes: []types.AwayNodeType{{Priority: 29000, WellKnownNodeTypeName: "gpu"}},
+					},
+					"armada-preemptible": {
+						Priority:    30000,
+						Preemptible: true,
+					},
+				}
+				config.Preemption.DefaultPriorityClass = "armada-preemptible"
+				config.WellKnownNodeTypes = []configuration.WellKnownNodeType{
+					{
+						Name:   "gpu",
+						Taints: []v1.Taint{{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule}},
+					},
+				}
+				return config
+			}(),
+			Nodes: func() []*schedulerobjects.Node {
+				nodes := testfixtures.N8GpuNodes(2, []int32{29000, 30000})
+				for _, node := range nodes {
+					node.Taints = []v1.Taint{{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule}}
+				}
+				return nodes
+			}(),
+			Rounds: []SchedulingRound{
+				{
+					JobsByQueue: map[string][]*jobdb.Job{
+						"B": func() (jobs []*jobdb.Job) {
+							for i := 0; i < 12; i++ {
+								jobId := util.ULID()
+								req := testfixtures.Test1GpuPodReqs("B", jobId, 30000)
+								req.Tolerations = append(req.Tolerations, v1.Toleration{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule})
+								jobs = append(jobs, testfixtures.TestJob("B", jobId, "armada-preemptible", req))
+							}
+							return
+						}(),
+					},
+					ExpectedScheduledIndices: map[string][]int{"B": testfixtures.IntRange(0, 11)},
+					ExpectedPreemptedIndices: nil,
+				},
+				{
+					JobsByQueue: map[string][]*jobdb.Job{
+						"A": func() (jobs []*jobdb.Job) {
+							for i := 0; i < 96; i++ {
+								jobId := util.ULID()
+								jobs = append(jobs, testfixtures.TestJob("A", jobId, "armada-preemptible-away", testfixtures.Test1Cpu4GiPodReqs("A", jobId, 30000)))
+							}
+							return
+						}(),
+					},
+					ExpectedScheduledIndices: map[string][]int{"A": testfixtures.IntRange(0, 31)},
+					ExpectedPreemptedIndices: nil,
+				},
+			},
+			PriorityFactorByQueue: map[string]float64{
+				"A": 1,
+				"B": 1,
+			},
+		},
+		"home-away preemption, mixed nodes, away jobs first": {
+			SchedulingConfig: func() configuration.SchedulingConfig {
+				config := testfixtures.TestSchedulingConfig()
+				config.Preemption.PriorityClasses = map[string]types.PriorityClass{
+					"armada-preemptible-away": {
+						Priority:    30000,
+						Preemptible: true,
+
+						AwayNodeTypes: []types.AwayNodeType{{Priority: 29000, WellKnownNodeTypeName: "gpu"}},
+					},
+					"armada-preemptible": {
+						Priority:    30000,
+						Preemptible: true,
+					},
+				}
+				config.Preemption.DefaultPriorityClass = "armada-preemptible"
+				config.WellKnownNodeTypes = []configuration.WellKnownNodeType{
+					{
+						Name:   "gpu",
+						Taints: []v1.Taint{{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule}},
+					},
+				}
+				return config
+			}(),
+			Nodes: func() []*schedulerobjects.Node {
+				priorities := []int32{29000, 30000}
+				gpuNodes := testfixtures.N8GpuNodes(1, priorities)
+				for _, node := range gpuNodes {
+					node.Taints = []v1.Taint{{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule}}
+				}
+				return append(testfixtures.N32CpuNodes(1, priorities), gpuNodes...)
+			}(),
+			Rounds: []SchedulingRound{
+				{
+					JobsByQueue: map[string][]*jobdb.Job{
+						"A": func() (jobs []*jobdb.Job) {
+							for i := 0; i < 96; i++ {
+								jobId := util.ULID()
+								jobs = append(jobs, testfixtures.TestJob("A", jobId, "armada-preemptible-away", testfixtures.Test1Cpu4GiPodReqs("A", jobId, 30000)))
+							}
+							return
+						}(),
+					},
+					ExpectedScheduledIndices: map[string][]int{"A": testfixtures.IntRange(0, 95)},
+					ExpectedPreemptedIndices: nil,
+				},
+				{
+					JobsByQueue: map[string][]*jobdb.Job{
+						"B": func() (jobs []*jobdb.Job) {
+							for i := 0; i < 12; i++ {
+								jobId := util.ULID()
+								req := testfixtures.Test1GpuPodReqs("B", jobId, 30000)
+								req.Tolerations = append(req.Tolerations, v1.Toleration{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule})
+								jobs = append(jobs, testfixtures.TestJob("B", jobId, "armada-preemptible", req))
+							}
+							return
+						}(),
+					},
+					ExpectedScheduledIndices: map[string][]int{"B": testfixtures.IntRange(0, 7)},
+					ExpectedPreemptedIndices: map[string]map[int][]int{"A": {0: testfixtures.IntRange(32, 95)}},
+				},
+			},
+			PriorityFactorByQueue: map[string]float64{
+				"A": 1,
+				"B": 1,
+			},
+		},
+		"home-away preemption, mixed nodes, home jobs first": {
+			SchedulingConfig: func() configuration.SchedulingConfig {
+				config := testfixtures.TestSchedulingConfig()
+				config.Preemption.PriorityClasses = map[string]types.PriorityClass{
+					"armada-preemptible-away": {
+						Priority:    30000,
+						Preemptible: true,
+
+						AwayNodeTypes: []types.AwayNodeType{{Priority: 29000, WellKnownNodeTypeName: "gpu"}},
+					},
+					"armada-preemptible": {
+						Priority:    30000,
+						Preemptible: true,
+					},
+				}
+				config.Preemption.DefaultPriorityClass = "armada-preemptible"
+				config.WellKnownNodeTypes = []configuration.WellKnownNodeType{
+					{
+						Name:   "gpu",
+						Taints: []v1.Taint{{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule}},
+					},
+				}
+				return config
+			}(),
+			Nodes: func() []*schedulerobjects.Node {
+				priorities := []int32{29000, 30000}
+				gpuNodes := testfixtures.N8GpuNodes(1, priorities)
+				for _, node := range gpuNodes {
+					node.Taints = []v1.Taint{{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule}}
+				}
+				return append(testfixtures.N32CpuNodes(1, priorities), gpuNodes...)
+			}(),
+			Rounds: []SchedulingRound{
+				{
+					JobsByQueue: map[string][]*jobdb.Job{
+						"B": func() (jobs []*jobdb.Job) {
+							for i := 0; i < 12; i++ {
+								jobId := util.ULID()
+								req := testfixtures.Test1GpuPodReqs("B", jobId, 30000)
+								req.Tolerations = append(req.Tolerations, v1.Toleration{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule})
+								jobs = append(jobs, testfixtures.TestJob("B", jobId, "armada-preemptible", req))
+							}
+							return
+						}(),
+					},
+					ExpectedScheduledIndices: map[string][]int{"B": testfixtures.IntRange(0, 7)},
+					ExpectedPreemptedIndices: nil,
+				},
+				{
+					JobsByQueue: map[string][]*jobdb.Job{
+						"A": func() (jobs []*jobdb.Job) {
+							for i := 0; i < 96; i++ {
+								jobId := util.ULID()
+								jobs = append(jobs, testfixtures.TestJob("A", jobId, "armada-preemptible-away", testfixtures.Test1Cpu4GiPodReqs("A", jobId, 30000)))
+							}
+							return
+						}(),
+					},
+					ExpectedScheduledIndices: map[string][]int{"A": testfixtures.IntRange(0, 31)},
+					ExpectedPreemptedIndices: nil,
+				},
+			},
+			PriorityFactorByQueue: map[string]float64{
+				"A": 1,
+				"B": 1,
+			},
+		},
 	}
 	for name, tc := range tests {
 		t.Run(name, func(t *testing.T) {
@@ -1496,10 +1759,7 @@ func TestPreemptingQueueScheduler(t *testing.T) {
 			}
 			nodeDbTxn.Commit()
 
-			var priorities []int32
-			for _, pc := range tc.SchedulingConfig.Preemption.PriorityClasses {
-				priorities = append(priorities, pc.Priority)
-			}
+			priorities := types.AllowedPriorities(tc.SchedulingConfig.Preemption.PriorityClasses)
 
 			jobDb := jobdb.NewJobDb(tc.SchedulingConfig.Preemption.PriorityClasses, tc.SchedulingConfig.Preemption.DefaultPriorityClass, 1024)
 			jobDbTxn := jobDb.WriteTxn()
@@ -1538,6 +1798,7 @@ func TestPreemptingQueueScheduler(t *testing.T) {
 				var queuedJobs []*jobdb.Job
 				for queue, jobs := range round.JobsByQueue {
 					for j, job := range jobs {
+						job = job.WithQueued(true)
 						require.Equal(t, queue, job.GetQueue())
 						queuedJobs = append(queuedJobs, job.WithQueued(true))
 						roundByJobId[job.GetId()] = i
@@ -1555,7 +1816,7 @@ func TestPreemptingQueueScheduler(t *testing.T) {
 							nodeId := nodeIdByJobId[job.GetId()]
 							node, err := nodeDb.GetNode(nodeId)
 							require.NoError(t, err)
-							node, err = nodedb.UnbindJobFromNode(tc.SchedulingConfig.Preemption.PriorityClasses, job, node)
+							node, err = nodeDb.UnbindJobFromNode(tc.SchedulingConfig.Preemption.PriorityClasses, job, node)
 							require.NoError(t, err)
 							err = nodeDb.Upsert(node)
 							require.NoError(t, err)
@@ -1607,7 +1868,6 @@ func TestPreemptingQueueScheduler(t *testing.T) {
 						limiterByQueue[queue],
 					)
 					require.NoError(t, err)
-
 				}
 				constraints := schedulerconstraints.SchedulingConstraintsFromSchedulingConfig(
 					"pool",
@@ -1615,7 +1875,7 @@ func TestPreemptingQueueScheduler(t *testing.T) {
 					schedulerobjects.ResourceList{Resources: tc.MinimumJobSize},
 					tc.SchedulingConfig,
 				)
-				sch := NewPreemptingQueueScheduler(
+				sch, err := NewPreemptingQueueScheduler(
 					sctx,
 					constraints,
 					tc.SchedulingConfig.Preemption.NodeEvictionProbability,
@@ -1627,6 +1887,7 @@ func TestPreemptingQueueScheduler(t *testing.T) {
 					jobIdsByGangId,
 					gangIdByJobId,
 				)
+				require.NoError(t, err)
 				sch.EnableAssertions()
 				if tc.SchedulingConfig.EnableNewPreemptionStrategy {
 					sch.EnableNewPreemptionStrategy()
@@ -1765,15 +2026,18 @@ func TestPreemptingQueueScheduler(t *testing.T) {
 				var scheduledJobs []*jobdb.Job
 				for _, job := range result.ScheduledJobs {
 					job := job.(*jobdb.Job)
-					node, err := nodeDb.GetNode(result.NodeIdByJobId[job.GetId()])
+					jobId := job.GetId()
+					node, err := nodeDb.GetNode(result.NodeIdByJobId[jobId])
 					require.NotNil(t, node)
 					require.NoError(t, err)
+					priority, ok := nodeDb.GetScheduledAtPriority(jobId)
+					require.True(t, ok)
 					scheduledJobs = append(
 						scheduledJobs,
 						job.
 							WithQueuedVersion(job.QueuedVersion()+1).
 							WithQueued(false).
-							WithNewRun(node.Executor, node.Id, node.Name),
+							WithNewRun(node.Executor, node.Id, node.Name, priority),
 					)
 				}
 				err = jobDbTxn.Upsert(scheduledJobs)
@@ -1945,7 +2209,7 @@ func BenchmarkPreemptingQueueScheduler(b *testing.B) {
 				schedulerobjects.ResourceList{Resources: tc.MinimumJobSize},
 				tc.SchedulingConfig,
 			)
-			sch := NewPreemptingQueueScheduler(
+			sch, err := NewPreemptingQueueScheduler(
 				sctx,
 				constraints,
 				tc.SchedulingConfig.Preemption.NodeEvictionProbability,
@@ -1957,6 +2221,7 @@ func BenchmarkPreemptingQueueScheduler(b *testing.B) {
 				nil,
 				nil,
 			)
+			require.NoError(b, err)
 			result, err := sch.Schedule(ctx)
 			require.NoError(b, err)
 			require.Equal(b, 0, len(result.PreemptedJobs))
@@ -2000,7 +2265,7 @@ func BenchmarkPreemptingQueueScheduler(b *testing.B) {
 					err := sctx.AddQueueSchedulingContext(queue, weight, allocatedByQueueAndPriorityClass[queue], limiterByQueue[queue])
 					require.NoError(b, err)
 				}
-				sch := NewPreemptingQueueScheduler(
+				sch, err := NewPreemptingQueueScheduler(
 					sctx,
 					constraints,
 					tc.SchedulingConfig.Preemption.NodeEvictionProbability,
@@ -2012,6 +2277,7 @@ func BenchmarkPreemptingQueueScheduler(b *testing.B) {
 					nil,
 					nil,
 				)
+				require.NoError(b, err)
 				result, err := sch.Schedule(ctx)
 				require.NoError(b, err)
 
diff --git a/internal/scheduler/queue_scheduler_test.go b/internal/scheduler/queue_scheduler_test.go
index 324d0865..ac49b4fc 100644
--- a/internal/scheduler/queue_scheduler_test.go
+++ b/internal/scheduler/queue_scheduler_test.go
@@ -530,11 +530,13 @@ func TestQueueScheduler(t *testing.T) {
 				legacySchedulerJobs[i] = job
 			}
 			jobRepo := NewInMemoryJobRepository()
-			jobRepo.EnqueueMany(schedulercontext.JobSchedulingContextsFromJobs(
-				testfixtures.TestPriorityClasses,
-				legacySchedulerJobs,
-				GangIdAndCardinalityFromAnnotations,
-			))
+			jobRepo.EnqueueMany(
+				schedulercontext.JobSchedulingContextsFromJobs(
+					tc.SchedulingConfig.Preemption.PriorityClasses,
+					legacySchedulerJobs,
+					GangIdAndCardinalityFromAnnotations,
+				),
+			)
 
 			fairnessCostProvider, err := fairness.NewDominantResourceFairness(
 				tc.TotalResources,
@@ -709,6 +711,7 @@ func NewNodeDb(config configuration.SchedulingConfig) (*nodedb.NodeDb, error) {
 		config.IndexedResources,
 		config.IndexedTaints,
 		config.IndexedNodeLabels,
+		config.WellKnownNodeTypes,
 	)
 	if err != nil {
 		return nil, err
diff --git a/internal/scheduler/scheduler_test.go b/internal/scheduler/scheduler_test.go
index 357b47c6..09523e0d 100644
--- a/internal/scheduler/scheduler_test.go
+++ b/internal/scheduler/scheduler_test.go
@@ -143,7 +143,8 @@ var leasedJob = testfixtures.JobDb.NewJob(
 	false,
 	false,
 	false,
-	1).WithQueued(false).WithNewRun("testExecutor", "test-node", "node")
+	1,
+).WithQueued(false).WithNewRun("testExecutor", "test-node", "node", 5)
 
 var defaultJobRunError = &armadaevents.Error{
 	Terminal: true,
@@ -165,7 +166,10 @@ var leasedFailFastJob = testfixtures.JobDb.NewJob(
 	false,
 	false,
 	false,
-	1).WithQueued(false).WithNewRun("testExecutor", "test-node", "node")
+	1,
+).WithQueued(false).WithNewRun("testExecutor", "test-node", "node", 5)
+
+var scheduledAtPriority = int32(5)
 
 var (
 	requeuedJobId = util.NewULID()
@@ -180,7 +184,8 @@ var (
 		false,
 		false,
 		false,
-		1).WithUpdatedRun(
+		1,
+	).WithUpdatedRun(
 		testfixtures.JobDb.CreateRun(
 			uuid.New(),
 			requeuedJobId,
@@ -188,6 +193,7 @@ var (
 			"testExecutor",
 			"test-node",
 			"node",
+			&scheduledAtPriority,
 			false,
 			false,
 			true,
@@ -927,6 +933,10 @@ func TestScheduler_TestSyncState(t *testing.T) {
 					Executor: "test-executor",
 					Node:     "test-node",
 					Created:  123,
+					ScheduledAtPriority: func() *int32 {
+						scheduledAtPriority := int32(5)
+						return &scheduledAtPriority
+					}(),
 				},
 			},
 			expectedUpdatedJobs: []*jobdb.Job{
@@ -938,6 +948,7 @@ func TestScheduler_TestSyncState(t *testing.T) {
 						"test-executor",
 						"test-executor-test-node",
 						"test-node",
+						&scheduledAtPriority,
 						false,
 						false,
 						false,
@@ -1179,7 +1190,11 @@ func (t *testSchedulingAlgo) Schedule(ctx *armadacontext.Context, txn *jobdb.Txn
 		if !job.Queued() {
 			return nil, errors.Errorf("was asked to lease %s but job was already leased", job.Id())
 		}
-		job = job.WithQueuedVersion(job.QueuedVersion()+1).WithQueued(false).WithNewRun("test-executor", "test-node", "node")
+		priority := int32(0)
+		if req := job.PodRequirements(); req != nil {
+			priority = req.Priority
+		}
+		job = job.WithQueuedVersion(job.QueuedVersion()+1).WithQueued(false).WithNewRun("test-executor", "test-node", "node", priority)
 		scheduledJobs = append(scheduledJobs, job)
 	}
 	for _, id := range t.jobsToFail {
diff --git a/internal/scheduler/scheduling_algo_test.go b/internal/scheduler/scheduling_algo_test.go
index 023572bf..9eb5939b 100644
--- a/internal/scheduler/scheduling_algo_test.go
+++ b/internal/scheduler/scheduling_algo_test.go
@@ -401,7 +401,7 @@ func TestSchedule(t *testing.T) {
 				for nodeIndex, existingJobs := range existingJobsByExecutorNodeIndex {
 					node := executor.Nodes[nodeIndex]
 					for jobIndex, job := range existingJobs.jobs {
-						job = job.WithQueued(false).WithNewRun(executor.Id, node.Id, node.Name)
+						job = job.WithQueued(false).WithNewRun(executor.Id, node.Id, node.Name, job.PodRequirements().Priority)
 						if existingJobs.acknowledged {
 							run := job.LatestRun()
 							node.StateByJobRunId[run.Id().String()] = schedulerobjects.JobRunState_RUNNING
@@ -517,7 +517,7 @@ func BenchmarkNodeDbConstruction(b *testing.B) {
 			nodes := testfixtures.N32CpuNodes(numNodes, testfixtures.TestPriorities)
 			for i, node := range nodes {
 				for j := 32 * i; j < 32*(i+1); j++ {
-					jobs[j] = jobs[j].WithNewRun("executor-01", node.Id, node.Name)
+					jobs[j] = jobs[j].WithNewRun("executor-01", node.Id, node.Name, jobs[j].PodRequirements().Priority)
 				}
 			}
 			armadaslices.Shuffle(jobs)
@@ -541,6 +541,7 @@ func BenchmarkNodeDbConstruction(b *testing.B) {
 					schedulingConfig.IndexedResources,
 					schedulingConfig.IndexedTaints,
 					schedulingConfig.IndexedNodeLabels,
+					schedulingConfig.WellKnownNodeTypes,
 				)
 				require.NoError(b, err)
 				err = algo.addExecutorToNodeDb(nodeDb, jobs, nodes)
diff --git a/internal/scheduler/simulator/simulator_test.go b/internal/scheduler/simulator/simulator_test.go
index 7373a15e..a933148d 100644
--- a/internal/scheduler/simulator/simulator_test.go
+++ b/internal/scheduler/simulator/simulator_test.go
@@ -7,11 +7,13 @@ import (
 
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 
 	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
+	"github.com/armadaproject/armada/internal/common/types"
 	"github.com/armadaproject/armada/internal/common/util"
 	schedulerobjects "github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
 	"github.com/armadaproject/armada/internal/scheduler/testfixtures"
@@ -297,6 +299,149 @@ func TestSimulator(t *testing.T) {
 			expectedEventSequences: nil,
 			simulatedTimeLimit:     2*time.Hour + 30*time.Minute,
 		},
+		"Home-away preemption": {
+			clusterSpec: &ClusterSpec{
+				Name: "cluster",
+				Pools: func() []*Pool {
+					whaleNodeTemplate := NodeTemplateGpu(2)
+					whaleNodeTemplate.Taints = []v1.Taint{
+						{Key: "gpu-whale", Value: "true", Effect: v1.TaintEffectNoSchedule},
+					}
+					whaleCluster := Cluster{NodeTemplates: []*NodeTemplate{whaleNodeTemplate}}
+					return []*Pool{
+						{
+							Name: "pool",
+							ClusterGroups: []*ClusterGroup{
+								{
+									Clusters: []*Cluster{
+										{NodeTemplates: []*NodeTemplate{NodeTemplateGpu(2)}},
+										&whaleCluster,
+									},
+								},
+							},
+						},
+					}
+				}(),
+			},
+			workloadSpec: &WorkloadSpec{
+				Queues: []*Queue{
+					{
+						Name:   "queue-0",
+						Weight: 1,
+						JobTemplates: []*JobTemplate{
+							{
+								Id:                "queue-0-template-0",
+								Number:            1,
+								JobSet:            "job-set-0",
+								PriorityClassName: "armada-preemptible",
+								Requirements: schedulerobjects.PodRequirements{
+									ResourceRequirements: v1.ResourceRequirements{
+										Requests: v1.ResourceList{
+											"cpu":            resource.MustParse("128"),
+											"memory":         resource.MustParse("4096Gi"),
+											"nvidia.com/gpu": resource.MustParse("8"),
+										},
+									},
+									Tolerations: []v1.Toleration{
+										{Key: "gpu-whale", Value: "true", Effect: v1.TaintEffectNoSchedule},
+									},
+								},
+								RuntimeDistribution: ShiftedExponential{Minimum: 5 * time.Minute},
+							},
+							{
+								Id:                "queue-0-template-1",
+								Number:            4,
+								JobSet:            "job-set-0",
+								PriorityClassName: "armada-preemptible",
+								Requirements: schedulerobjects.PodRequirements{
+									ResourceRequirements: v1.ResourceRequirements{
+										Requests: v1.ResourceList{
+											"cpu":            resource.MustParse("128"),
+											"memory":         resource.MustParse("4096Gi"),
+											"nvidia.com/gpu": resource.MustParse("8"),
+										},
+									},
+									Tolerations: []v1.Toleration{
+										{Key: "gpu-whale", Value: "true", Effect: v1.TaintEffectNoSchedule},
+									},
+								},
+								Dependencies:        []string{"queue-0-template-0"},
+								RuntimeDistribution: ShiftedExponential{Minimum: time.Hour},
+							},
+						},
+					},
+					{
+						Name:   "queue-1",
+						Weight: 1,
+						JobTemplates: []*JobTemplate{
+							{
+								Number:            32,
+								JobSet:            "job-set-1",
+								PriorityClassName: "armada-preemptible-away",
+								Requirements: schedulerobjects.PodRequirements{
+									ResourceRequirements: v1.ResourceRequirements{
+										Requests: v1.ResourceList{
+											"cpu":            resource.MustParse("16"),
+											"memory":         resource.MustParse("512Gi"),
+											"nvidia.com/gpu": resource.MustParse("1"),
+										},
+									},
+								},
+								RuntimeDistribution: ShiftedExponential{Minimum: time.Hour},
+							},
+						},
+					},
+				},
+			},
+			schedulingConfig: func() configuration.SchedulingConfig {
+				config := testfixtures.TestSchedulingConfig()
+				config.Preemption.PriorityClasses = map[string]types.PriorityClass{
+					"armada-preemptible-away": {
+						Priority:    30000,
+						Preemptible: true,
+
+						AwayNodeTypes: []types.AwayNodeType{{Priority: 29000, WellKnownNodeTypeName: "gpu-whale"}},
+					},
+					"armada-preemptible": {
+						Priority:    30000,
+						Preemptible: true,
+					},
+				}
+				config.Preemption.DefaultPriorityClass = "armada-preemptible"
+				config.WellKnownNodeTypes = []configuration.WellKnownNodeType{
+					{
+						Name:   "gpu-whale",
+						Taints: []v1.Taint{{Key: "gpu-whale", Value: "true", Effect: v1.TaintEffectNoSchedule}},
+					},
+				}
+				return config
+			}(),
+			expectedEventSequences: armadaslices.Concatenate(
+				[]*armadaevents.EventSequence{
+					{Queue: "queue-0", JobSetName: "job-set-0", Events: armadaslices.Repeat(1, SubmitJob())},
+					{Queue: "queue-1", JobSetName: "job-set-1", Events: armadaslices.Repeat(32, SubmitJob())},
+				},
+				armadaslices.Repeat(1, &armadaevents.EventSequence{Queue: "queue-0", JobSetName: "job-set-0", Events: []*armadaevents.EventSequence_Event{JobRunLeased()}}),
+				armadaslices.Repeat(24, &armadaevents.EventSequence{Queue: "queue-1", JobSetName: "job-set-1", Events: []*armadaevents.EventSequence_Event{JobRunLeased()}}),
+				armadaslices.Repeat(1, &armadaevents.EventSequence{Queue: "queue-0", JobSetName: "job-set-0", Events: []*armadaevents.EventSequence_Event{JobSucceeded()}}),
+				[]*armadaevents.EventSequence{
+					{Queue: "queue-0", JobSetName: "job-set-0", Events: armadaslices.Repeat(4, SubmitJob())},
+				},
+				armadaslices.Repeat(8, &armadaevents.EventSequence{Queue: "queue-1", JobSetName: "job-set-1", Events: []*armadaevents.EventSequence_Event{JobRunLeased()}}),
+				armadaslices.Repeat(24, &armadaevents.EventSequence{Queue: "queue-1", JobSetName: "job-set-1", Events: []*armadaevents.EventSequence_Event{JobRunPreempted()}}),
+				armadaslices.Repeat(3, &armadaevents.EventSequence{Queue: "queue-0", JobSetName: "job-set-0", Events: []*armadaevents.EventSequence_Event{JobRunLeased()}}),
+				armadaslices.Repeat(24, &armadaevents.EventSequence{Queue: "queue-1", JobSetName: "job-set-1", Events: []*armadaevents.EventSequence_Event{SubmitJob()}}),
+				armadaslices.Repeat(8, &armadaevents.EventSequence{Queue: "queue-1", JobSetName: "job-set-1", Events: []*armadaevents.EventSequence_Event{JobSucceeded()}}),
+				armadaslices.Repeat(8, &armadaevents.EventSequence{Queue: "queue-1", JobSetName: "job-set-1", Events: []*armadaevents.EventSequence_Event{JobRunLeased()}}),
+				armadaslices.Repeat(3, &armadaevents.EventSequence{Queue: "queue-0", JobSetName: "job-set-0", Events: []*armadaevents.EventSequence_Event{JobSucceeded()}}),
+				armadaslices.Repeat(1, &armadaevents.EventSequence{Queue: "queue-0", JobSetName: "job-set-0", Events: []*armadaevents.EventSequence_Event{JobRunLeased()}}),
+				armadaslices.Repeat(16, &armadaevents.EventSequence{Queue: "queue-1", JobSetName: "job-set-1", Events: []*armadaevents.EventSequence_Event{JobRunLeased()}}),
+				armadaslices.Repeat(8, &armadaevents.EventSequence{Queue: "queue-1", JobSetName: "job-set-1", Events: []*armadaevents.EventSequence_Event{JobSucceeded()}}),
+				armadaslices.Repeat(1, &armadaevents.EventSequence{Queue: "queue-0", JobSetName: "job-set-0", Events: []*armadaevents.EventSequence_Event{JobSucceeded()}}),
+				armadaslices.Repeat(16, &armadaevents.EventSequence{Queue: "queue-1", JobSetName: "job-set-1", Events: []*armadaevents.EventSequence_Event{JobSucceeded()}}),
+			),
+			simulatedTimeLimit: 24 * time.Hour,
+		},
 	}
 	for name, tc := range tests {
 		t.Run(name, func(t *testing.T) {
diff --git a/internal/scheduler/simulator/test_utils.go b/internal/scheduler/simulator/test_utils.go
index dd11099c..4573afaa 100644
--- a/internal/scheduler/simulator/test_utils.go
+++ b/internal/scheduler/simulator/test_utils.go
@@ -61,7 +61,7 @@ func GetBasicSchedulingConfig() configuration.SchedulingConfig {
 			"memory": 0.025,
 			"cpu":    0.025,
 		},
-		FairnessModel: "DominantResourceFairness",
+		FairnessModel: configuration.DominantResourceFairness,
 		DominantResourceFairnessResourcesToConsider: []string{"cpu", "memory", "nvidia.com/gpu", "ephemeral-storage"},
 		IndexedResources: []configuration.IndexedResource{
 			{
diff --git a/internal/scheduler/testfixtures/testfixtures.go b/internal/scheduler/testfixtures/testfixtures.go
index 407b4218..294e6ba2 100644
--- a/internal/scheduler/testfixtures/testfixtures.go
+++ b/internal/scheduler/testfixtures/testfixtures.go
@@ -61,9 +61,15 @@ var (
 		TestResources,
 		func(v configuration.IndexedResource) int64 { return v.Resolution.MilliValue() },
 	)
-	TestIndexedTaints     = []string{"largeJobsOnly", "gpu"}
-	TestIndexedNodeLabels = []string{"largeJobsOnly", "gpu"}
-	jobTimestamp          atomic.Int64
+	TestIndexedTaints      = []string{"largeJobsOnly", "gpu"}
+	TestIndexedNodeLabels  = []string{"largeJobsOnly", "gpu"}
+	TestWellKnownNodeTypes = []configuration.WellKnownNodeType{
+		{
+			Name:   "gpu",
+			Taints: []v1.Taint{{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule}},
+		},
+	}
+	jobTimestamp atomic.Int64
 	// SchedulingKeyGenerator to use in testing.
 	// Has to be consistent since creating one involves generating a random key.
 	// If this key isn't consistent, scheduling keys generated are not either.
@@ -112,8 +118,11 @@ func TestSchedulingConfig() configuration.SchedulingConfig {
 		MaximumSchedulingBurst:                      math.MaxInt,
 		MaximumPerQueueSchedulingRate:               math.Inf(1),
 		MaximumPerQueueSchedulingBurst:              math.MaxInt,
+		MaxExtraNodesToConsider:                     TestMaxExtraNodesToConsider,
 		IndexedResources:                            TestResources,
 		IndexedNodeLabels:                           TestIndexedNodeLabels,
+		IndexedTaints:                               TestIndexedTaints,
+		WellKnownNodeTypes:                          TestWellKnownNodeTypes,
 		DominantResourceFairnessResourcesToConsider: TestResourceNames,
 		ExecutorTimeout:                             15 * time.Minute,
 		MaxUnacknowledgedJobsPerExecutor:            math.MaxInt,
@@ -833,6 +842,7 @@ func Test1CoreCpuApiJob() *api.Job {
 					},
 				},
 			},
+			PriorityClassName: TestDefaultPriorityClass,
 		},
 	}
 }
diff --git a/internal/scheduleringester/instructions_test.go b/internal/scheduleringester/instructions_test.go
index 99ecde22..9fd9e759 100644
--- a/internal/scheduleringester/instructions_test.go
+++ b/internal/scheduleringester/instructions_test.go
@@ -53,11 +53,12 @@ func TestConvertSequence(t *testing.T) {
 			events: []*armadaevents.EventSequence_Event{f.Leased},
 			expected: []DbOperation{
 				InsertRuns{f.RunIdUuid: &JobRunDetails{queue: f.Queue, dbRun: &schedulerdb.Run{
-					RunID:    f.RunIdUuid,
-					JobID:    f.JobIdString,
-					JobSet:   f.JobSetName,
-					Executor: f.ExecutorId,
-					Node:     f.NodeName,
+					RunID:               f.RunIdUuid,
+					JobID:               f.JobIdString,
+					JobSet:              f.JobSetName,
+					Executor:            f.ExecutorId,
+					Node:                f.NodeName,
+					ScheduledAtPriority: &f.ScheduledAtPriority,
 				}}},
 				UpdateJobQueuedState{f.JobIdString: &JobQueuedStateUpdate{
 					Queued:             false,
diff --git a/internal/scheduleringester/schedulerdb_test.go b/internal/scheduleringester/schedulerdb_test.go
index 873885c3..6789aea5 100644
--- a/internal/scheduleringester/schedulerdb_test.go
+++ b/internal/scheduleringester/schedulerdb_test.go
@@ -28,6 +28,7 @@ func TestWriteOps(t *testing.T) {
 	for i := range runIds {
 		runIds[i] = uuid.New()
 	}
+	scheduledAtPriorities := []int32{5, 10}
 	tests := map[string]struct {
 		Ops []DbOperation
 	}{
@@ -53,8 +54,8 @@ func TestWriteOps(t *testing.T) {
 				runIds[1]: &JobRunDetails{queue: testQueueName, dbRun: &schedulerdb.Run{JobID: jobIds[1], RunID: runIds[1]}},
 			},
 			InsertRuns{
-				runIds[2]: &JobRunDetails{queue: testQueueName, dbRun: &schedulerdb.Run{JobID: jobIds[2], RunID: runIds[2]}},
-				runIds[3]: &JobRunDetails{queue: testQueueName, dbRun: &schedulerdb.Run{JobID: jobIds[3], RunID: runIds[3]}},
+				runIds[2]: &JobRunDetails{queue: testQueueName, dbRun: &schedulerdb.Run{JobID: jobIds[2], RunID: runIds[2], ScheduledAtPriority: &scheduledAtPriorities[0]}},
+				runIds[3]: &JobRunDetails{queue: testQueueName, dbRun: &schedulerdb.Run{JobID: jobIds[3], RunID: runIds[3], ScheduledAtPriority: &scheduledAtPriorities[1]}},
 			},
 			UpdateJobQueuedState{
 				jobIds[0]: &JobQueuedStateUpdate{Queued: false, QueuedStateVersion: 1},