# DistributedScaledJob API reference

Job distribution requires member access and the KEDA raw-metrics path. Failover/duplication and retry behavior affect delivery semantics; design idempotent work and inspect member status before resubmitting jobs.

## API details

Group: `keda.kedify.io`. Kind: `DistributedScaledJob`. Scope: **Namespaced**.

Defaults below are schema defaults only. “Not specified” does not imply a controller default of zero or false. Required fields are required within their containing object. Runtime behavior can impose additional conditions described in the [capability guide](https://docs.kedify.io/features/multi-cluster-scaling/).

Start with [distribution strategies](https://docs.kedify.io/reference/kubernetes-api/distributedscaledjob/#documented-scaling-strategies), [pod override merge rules](https://docs.kedify.io/reference/kubernetes-api/distributedscaledjob/#documented-distributedscaledjob-pod-overrides) and [failover](https://docs.kedify.io/reference/kubernetes-api/distributedscaledjob/#documented-failoverpolicy). The tables retain Kedify fields and link inherited Kubernetes structures upstream.

## v1alpha1 fields

Served: `true`; storage: `true`.

### spec

| Field | Type | Required | Schema default | Meaning and validation |
| --- | --- | --- | --- | --- |
| `spec` | object | No | Not specified | DistributedScaledJobSpec defines the desired state of DistributedScaledJob x-kubernetes-validations: [{“message”:“memberClusters must be non-empty when clusterScheduling.strategy=priorityFailover”,“rule”:“self.clusterScheduling.strategy != ‘priorityFailover’ \|\| (has(self.memberClusters) && self.memberClusters.size() > 0)”},{“message”:“memberClusters[].scheduling.priority is required when clusterScheduling.strategy=priorityFailover”,“rule”:“self.clusterScheduling.strategy != ‘priorityFailover’ \|\| (has(self.memberClusters) && self.memberClusters.all(c, has(c.scheduling) && has(c.scheduling.priority)))”},{“message”:“scaledJobSpec.scalingStrategy.strategy must be pendingAware (or omitted) when clusterScheduling.strategy=priorityFailover”,“rule”:“self.clusterScheduling.strategy != ‘priorityFailover’ \|\| self.scaledJobSpec.scalingStrategy.strategy == ” \|\| self.scaledJobSpec.scalingStrategy.strategy == ‘pendingAware’”}] |
| `spec.clusterScheduling` | object | No | `{"strategy":"weightedRoundRobin"}` | ClusterScheduling controls member-cluster selection strategy for DSJ. x-kubernetes-validations: [{“message”:“clusterScheduling.workloadAffinity is required when clusterScheduling.strategy=priorityFailover”,“rule”:“self.strategy != ‘priorityFailover’ \|\| has(self.workloadAffinity)”},{“message”:“clusterScheduling.workloadAffinity must be omitted when clusterScheduling.strategy=weightedRoundRobin”,“rule”:“self.strategy == ‘priorityFailover’ \|\| !has(self.workloadAffinity)”}] |
| `spec.clusterScheduling.failoverPolicy` | object | No | `{"gracePeriod":"1m","hardTaintDuration":"5m","softTaintDuration":"3m"}` | FailoverPolicy controls how the controller should behave when clusters fail |
| `spec.clusterScheduling.failoverPolicy.duplicationPolicy` | string | No | `"keepAll"` | DuplicationPolicy controls how duplicate jobs are resolved after pending-aware failover. - keepAll: keep all source and failover cluster jobs, results in temporarily exceeded maxReplicaCount during failover but provides better availability (default) - preferFailover: prefer failover cluster job over source cluster job, delete source cluster job only when failover cluster job is running, best effort avoids maxReplicaCount exceed - preferSource: prefer source cluster job over failover cluster job, delete failover cluster job immediately, can cause running job preemption when source cluster recovers during failover, best effort avoids maxReplicaCount exceed - immediateSourceCleanup: clean up source cluster job right after failover enum: [“immediateSourceCleanup”,“keepAll”,“preferFailover”,“preferSource”] |
| `spec.clusterScheduling.failoverPolicy.gracePeriod` | string | No | `"1m"` | GracePeriod is the duration to wait before re-creating Jobs that fail to progress from Pending to Running state |
| `spec.clusterScheduling.failoverPolicy.hardTaintDuration` | string | No | `"5m"` | HardTaintDuration is the duration to taint a failing cluster before attempting to scale Jobs on it again |
| `spec.clusterScheduling.failoverPolicy.softTaintDuration` | string | No | `"3m"` | SoftTaintDuration is the time window to wait for a second failure before applying a hard taint. Set to 0s to disable soft taint and hard-taint on the first failure. |
| `spec.clusterScheduling.strategy` | string | No | `"weightedRoundRobin"` | Strategy defines how member clusters are selected when creating Jobs. enum: [“weightedRoundRobin”,“priorityFailover”] |
| `spec.clusterScheduling.workloadAffinity` | object | No | Not specified | WorkloadAffinity classifies workloads for failover taint matching. Required when strategy=priorityFailover. |
| `spec.clusterScheduling.workloadAffinity.class` | string | No | Not specified | Class groups workloads for failover taint matching (e.g., for org/team). |
| `spec.clusterScheduling.workloadAffinity.size` | integer | Yes | Not specified | Size is the monotonic affinity class value. minimum: 0 |
| `spec.memberClusters` | array | No | Not specified | MemberClusters is the list of member clusters where the ScaledJob should trigger scaling of the target workloads if empty, use all available member clusters maxItems: 64; x-kubernetes-validations: [{“message”:“memberClusters.name must be non-empty”,“rule”:“self.all(c, c.name != ”)”},{“message”:“memberClusters.name values must be unique”,“rule”:“self.map(c, c.name).all(n, self.map(c, c.name).filter(x, x == n).size() == 1)”}] |
| `spec.memberClusters[]` | object | No | Not specified | DistributedScaledJobMemberCluster represents a member cluster where the ScaledJob should be applied. |
| `spec.memberClusters[].name` | string | Yes | Not specified | Name is the name of the member cluster. |
| `spec.memberClusters[].podOverrides` | object | No | Not specified | PodOverrides are applied to Job pod template for this cluster. |
| `spec.memberClusters[].podOverrides.affinity` | object | No | Not specified | See [Kubernetes field definitions](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec). |
| `spec.memberClusters[].podOverrides.containerOverrides` | object | No | Not specified | ContainerOverrides are keyed by container name. |
| `spec.memberClusters[].podOverrides.containerOverrides.*` | object | No | Not specified | DistributedScaledJobContainerOverrides defines container-level overrides for a single member cluster. |
| `spec.memberClusters[].podOverrides.containerOverrides.*.env` | array | No | Not specified | Env overrides container environment variables. |
| `spec.memberClusters[].podOverrides.containerOverrides.*.env[]` | object | No | Not specified | EnvVar represents an environment variable present in a Container. |
| `spec.memberClusters[].podOverrides.containerOverrides.*.env[].name` | string | Yes | Not specified | Name of the environment variable. May consist of any printable ASCII characters except ’=’. |
| `spec.memberClusters[].podOverrides.containerOverrides.*.env[].value` | string | No | Not specified | Variable references $(VAR_NAME) are expanded using the previously defined environment variables in the container and any service environment variables. If a variable cannot be resolved, the reference in the input string will be unchanged. Double $$ are reduced to a single $, which allows for escaping the $(VAR_NAME) syntax: i.e. ”$$(VAR_NAME)” will produce the string literal ”$(VAR_NAME)”. Escaped references will never be expanded, regardless of whether the variable exists or not. Defaults to "". |
| `spec.memberClusters[].podOverrides.containerOverrides.*.env[].valueFrom` | object | No | Not specified | See [Kubernetes field definitions](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#Container). |
| `spec.memberClusters[].podOverrides.containerOverrides.*.image` | string | No | Not specified | Image overrides container image for this cluster. |
| `spec.memberClusters[].podOverrides.containerOverrides.*.resources` | object | No | Not specified | Resources partially overrides container resource requirements. |
| `spec.memberClusters[].podOverrides.containerOverrides.*.resources.claims` | array | No | Not specified | Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. This field depends on the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. |
| `spec.memberClusters[].podOverrides.containerOverrides.*.resources.claims[]` | object | No | Not specified | ResourceClaim references one entry in PodSpec.ResourceClaims. |
| `spec.memberClusters[].podOverrides.containerOverrides.*.resources.claims[].name` | string | Yes | Not specified | Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container. |
| `spec.memberClusters[].podOverrides.containerOverrides.*.resources.claims[].request` | string | No | Not specified | Request is the name chosen for a request in the referenced claim. If empty, everything from the claim is made available, otherwise only the result of this request. |
| `spec.memberClusters[].podOverrides.containerOverrides.*.resources.limits` | object | No | Not specified | Limits describes the maximum amount of compute resources allowed. More info: [https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) |
| `spec.memberClusters[].podOverrides.containerOverrides.*.resources.limits.*` | integer or string | No | Not specified | pattern: ”^(\+\|-)?(([0-9]+(\.[0-9]*)?)\|(\.[0-9]+))(([KMGTPE]i)\|[numkMGTPE]\|([eE](\+\|-)?(([0-9]+(\.[0-9]*)?)\|(\.[0-9]+))))?$” |
| `spec.memberClusters[].podOverrides.containerOverrides.*.resources.requests` | object | No | Not specified | Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: [https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) |
| `spec.memberClusters[].podOverrides.containerOverrides.*.resources.requests.*` | integer or string | No | Not specified | pattern: ”^(\+\|-)?(([0-9]+(\.[0-9]*)?)\|(\.[0-9]+))(([KMGTPE]i)\|[numkMGTPE]\|([eE](\+\|-)?(([0-9]+(\.[0-9]*)?)\|(\.[0-9]+))))?$” |
| `spec.memberClusters[].podOverrides.nodeSelector` | object | No | Not specified | NodeSelector overrides pod nodeSelector. |
| `spec.memberClusters[].podOverrides.nodeSelector.*` | string | No | Not specified |  |
| `spec.memberClusters[].podOverrides.tolerations` | array | No | Not specified | See [Kubernetes field definitions](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodSpec). |
| `spec.memberClusters[].scheduling` | object | No | Not specified | Scheduling contains cluster-selection attributes for this member cluster. |
| `spec.memberClusters[].scheduling.priority` | integer | No | Not specified | Priority is used by clusterScheduling.strategy=priorityFailover. Higher value means stronger preference. minimum: 0; maximum: 1000 |
| `spec.memberClusters[].targetNamespace` | string | No | Not specified | TargetNamespace is the namespace in the member cluster where the scaling should be applied. Defaults to the namespace of the owning distributed resource if not specified. |
| `spec.memberClusters[].weight` | integer | Yes | `1` | Weight is the weight of the member cluster for scaling distribution. |
| `spec.scaledJobSpec` | object | Yes | Not specified | ScaledJobSpec is the specification of the KEDA ScaledJob |
| `spec.scaledJobSpec.failedJobsHistoryLimit` | integer | No | Not specified |  |
| `spec.scaledJobSpec.jobTargetRef` | object | Yes | Not specified | See [Kubernetes field definitions](https://kubernetes.io/docs/reference/kubernetes-api/batch/job-v1/#JobSpec). |
| `spec.scaledJobSpec.maxReplicaCount` | integer | No | Not specified |  |
| `spec.scaledJobSpec.minReplicaCount` | integer | No | Not specified |  |
| `spec.scaledJobSpec.pollingInterval` | integer | No | Not specified |  |
| `spec.scaledJobSpec.rollout` | object | No | Not specified | Rollout defines the strategy for job rollouts |
| `spec.scaledJobSpec.rollout.propagationPolicy` | string | No | Not specified |  |
| `spec.scaledJobSpec.rollout.strategy` | string | No | Not specified |  |
| `spec.scaledJobSpec.scalingStrategy` | object | No | Not specified | ScalingStrategy defines the strategy of Scaling |
| `spec.scaledJobSpec.scalingStrategy.customScalingQueueLengthDeduction` | integer | No | Not specified |  |
| `spec.scaledJobSpec.scalingStrategy.customScalingRunningJobPercentage` | string | No | Not specified |  |
| `spec.scaledJobSpec.scalingStrategy.multipleScalersCalculation` | string | No | Not specified |  |
| `spec.scaledJobSpec.scalingStrategy.pendingPodConditions` | array | No | Not specified |  |
| `spec.scaledJobSpec.scalingStrategy.pendingPodConditions[]` | string | No | Not specified |  |
| `spec.scaledJobSpec.scalingStrategy.strategy` | string | No | Not specified | enum: [“basic”,“custom”,“accurate”,“eager”,“pendingAware”] |
| `spec.scaledJobSpec.successfulJobsHistoryLimit` | integer | No | Not specified |  |
| `spec.scaledJobSpec.triggers` | array | Yes | Not specified | maxItems: 64; x-kubernetes-validations: [{“message”:“triggers.name must be non-empty”,“rule”:“self.all(t, t.name != ”)”},{“message”:“triggers.name values must be unique”,“rule”:“self.map(t, t.name).all(n, self.map(t, t.name).filter(x, x == n).size() == 1)”}] |
| `spec.scaledJobSpec.triggers[]` | object | No | Not specified | ScaleTriggers reference the scaler that will be used |
| `spec.scaledJobSpec.triggers[].authenticationRef` | object | No | Not specified | AuthenticationRef points to the TriggerAuthentication or ClusterTriggerAuthentication object that is used to authenticate the scaler with the environment |
| `spec.scaledJobSpec.triggers[].authenticationRef.kind` | string | No | Not specified | Kind of the resource being referred to. Defaults to TriggerAuthentication. enum: [“TriggerAuthentication”,“ClusterTriggerAuthentication”] |
| `spec.scaledJobSpec.triggers[].authenticationRef.name` | string | Yes | Not specified |  |
| `spec.scaledJobSpec.triggers[].metadata` | object | Yes | Not specified |  |
| `spec.scaledJobSpec.triggers[].metadata.*` | string | No | Not specified |  |
| `spec.scaledJobSpec.triggers[].metricType` | string | No | Not specified | MetricTargetType specifies the type of metric being targeted, and should be either “Value”, “AverageValue”, or “Utilization” |
| `spec.scaledJobSpec.triggers[].name` | string | No | Not specified |  |
| `spec.scaledJobSpec.triggers[].type` | string | Yes | Not specified | minLength: 1 |
| `spec.scaledJobSpec.triggers[].useCachedMetrics` | boolean | No | Not specified |  |
| `spec.totalTargetReplicas` | integer | No | `0` | TotalTargetReplicas is the desired number of replicas |

### status

| Field | Type | Required | Schema default | Meaning and validation |
| --- | --- | --- | --- | --- |
| `status` | object | No | Not specified | DistributedScaledJobStatus defines the observed state of DistributedScaledJob |
| `status.conditions` | array | No | Not specified |  |
| `status.conditions[]` | object | No | Not specified | Condition contains details for one aspect of the current state of this API Resource. |
| `status.conditions[].lastTransitionTime` | string | Yes | Not specified | lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. |
| `status.conditions[].message` | string | Yes | Not specified | message is a human readable message indicating details about the transition. This may be an empty string. maxLength: 32768 |
| `status.conditions[].observedGeneration` | integer | No | Not specified | observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. minimum: 0 |
| `status.conditions[].reason` | string | Yes | Not specified | reason contains a programmatic identifier indicating the reason for the condition’s last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. minLength: 1; maxLength: 1024; pattern: ”^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$” |
| `status.conditions[].status` | string | Yes | Not specified | status of the condition, one of True, False, Unknown. enum: [“True”,“False”,“Unknown”] |
| `status.conditions[].type` | string | Yes | Not specified | type of condition in CamelCase or in foo.example.com/CamelCase. maxLength: 316; pattern: ”^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$” |
| `status.desiredJobs` | integer | Yes | Not specified | DesiredJobs is the desired number of jobs calculated from metrics |
| `status.memberClusterStatuses` | object | No | Not specified | MemberClusterStatuses represents the status of each member cluster |
| `status.memberClusterStatuses.*` | object | No | Not specified | DistributedScaledJobMemberClusterStatus represents the status of a member cluster |
| `status.memberClusterStatuses.*.description` | string | Yes | Not specified | Description provides additional information about the member cluster status |
| `status.memberClusterStatuses.*.excluded` | boolean | No | Not specified | Excluded indicates whether the member cluster is excluded from scaling for example, due to being unhealthy for longer than the grace period |
| `status.memberClusterStatuses.*.hardTaintScopes` | array | No | Not specified | HardTaintScopes lists hard-taint scopes for priorityFailover scheduling. Multiple entries may exist for a single cluster (for different classes/priorities). |
| `status.memberClusterStatuses.*.hardTaintScopes[]` | object | No | Not specified | DistributedScaledJobHardTaintScope identifies one priorityFailover hard-taint scope. |
| `status.memberClusterStatuses.*.hardTaintScopes[].class` | string | Yes | Not specified | Class is the workload affinity class for which the cluster is tainted. This can be empty to represent the default affinity class. |
| `status.memberClusterStatuses.*.hardTaintScopes[].priority` | integer | No | Not specified | Priority is the member-cluster scheduling priority for which the cluster is tainted. |
| `status.memberClusterStatuses.*.hardTainted` | boolean | No | Not specified | HardTainted indicates the cluster is hard tainted for at least one DSJ scheduling path. |
| `status.memberClusterStatuses.*.id` | string | Yes | Not specified | ID is the identification of the member cluster for multicluster-controller |
| `status.memberClusterStatuses.*.lastScalingProgressTime` | string | No | Not specified | LastScalingProgressTime is the last time the scaling made progress This is used to determine how long the member cluster has been making slow progress for rebalancing purposes |
| `status.memberClusterStatuses.*.lastStatusChangeTime` | string | No | Not specified | LastStatusChangeTime is the last time the status changed This is used to determine how long the member cluster has been unhealthy for rebalancing purposes |
| `status.memberClusterStatuses.*.pendingJobs` | integer | Yes | Not specified | PendingJobs is the number of jobs that are pending in the member cluster |
| `status.memberClusterStatuses.*.provider` | string | No | Not specified | Provider is the cluster provider type (for example file, kubeconfig) |
| `status.memberClusterStatuses.*.runningJobs` | integer | Yes | Not specified | RunningJobs is the current number of running jobs in the member cluster |
| `status.memberClusterStatuses.*.softTainted` | boolean | No | Not specified | SoftTainted indicates the cluster has a recent failure but is not hard tainted |
| `status.memberClusterStatuses.*.state` | string | Yes | Not specified | State is the state of the member cluster (e.g., Ready, NotReady) |
| `status.memberClusterStatuses.*.stuckJobs` | integer | Yes | Not specified | StuckJobs is the number of jobs that are stuck (e.g., pending for too long) in the member cluster |
| `status.pendingJobs` | integer | Yes | Not specified | PendingJobs is the current number of pending jobs across all member clusters |
| `status.print` | object | No | Not specified | Print is used for custom printing of DistributedScaledJob status |
| `status.print.membersCount` | string | No | Not specified | MembersCount is the status of member clusters |
| `status.print.runningJobs` | string | No | Not specified | RunningJobs represents the status of running jobs across member clusters |
| `status.runningJobs` | integer | Yes | Not specified | RunningJobs is the current number of running jobs across all member clusters |
| `status.scalersTargetAverageValue` | integer or string | No | Not specified | ScalersTargetAverageValue is the target average value across all scalers pattern: ”^(\+\|-)?(([0-9]+(\.[0-9]*)?)\|(\.[0-9]+))(([KMGTPE]i)\|[numkMGTPE]\|([eE](\+\|-)?(([0-9]+(\.[0-9]*)?)\|(\.[0-9]+))))?$” |
| `status.selector` | string | Yes | Not specified | Selector is the selector for the distributed jobs |
| `status.triggerMetricsStatus` | object | No | Not specified | TriggerMetricStatus represents the status of each metric used for scaling by trigger name |
| `status.triggerMetricsStatus.*` | object | No | Not specified | TriggerMetricStatus represents the status of a single metric used for scaling by trigger name |
| `status.triggerMetricsStatus.*.info` | string | No | Not specified | Info provides additional information about the metric status, such as error messages or reasons for inactivity |
| `status.triggerMetricsStatus.*.lastChangeTime` | string | No | Not specified | LastChangeTime is the last time the metric status changed, used for tracking how long a metric has been in a particular state |
| `status.triggerMetricsStatus.*.state` | string | Yes | Not specified | State represents the state of the metric (“Active”, “Inactive”, “Error”) used to determine if scaling should occur |

## Inspect the installed contract

```bash
kubectl get crd distributedscaledjobs.keda.kedify.io -o yaml
kubectl explain distributedscaledjob.spec --recursive
```

Compare served/storage versions and schema before applying configuration. Inspect the resource’s status and events after changing its owning source; schema acceptance alone does not prove successful reconciliation.

## Use, verify and diagnose

- [Distributed multi-cluster scaling](https://docs.kedify.io/features/multi-cluster-scaling/)

- [Distribute jobs across clusters](https://docs.kedify.io/how-to/multicluster-dsj/)

- [Register member clusters with GitOps](https://docs.kedify.io/how-to/multicluster-gitops/)

- [Diagnose multi-cluster and tenant controllers](https://docs.kedify.io/troubleshooting/platform-scaling/)

## Scaling strategies

Scaling strategies are used to compute how many new Jobs to create across clusters. Choose one of: `basic`, `pendingAware`, `custom`, `accurate`, `eager`.

When `clusterScheduling.strategy=priorityFailover`, only `pendingAware` (or omitted strategy, which defaults to `pendingAware`) is supported.

Inputs:

- desiredJobsCount: target number derived from metrics and DSJ min/max bounds

- runningJobsCount: number of non-terminal Jobs currently present (includes “pending”)

- pendingJobsCount: subset of running Jobs considered “pending” (not yet progressed)

- maxReplicaCount: DSJ upper bound on total concurrent non-terminal Jobs

#### Basic

Scale to the gap between desired and running.

- Formula: `desired - running`

- Behavior: simple “catch up” to desired.

#### Pending-Aware

Immediately re-create pending Jobs on other clusters while honoring capacity.

- Idea: replace stuck Jobs.

- Formula: 

  - `needed = max(0, desired - running + pending)`

  - `capacity = max(0, maxReplica - running)`

  - `scaleTo = min(needed, capacity)`

- Use when pending/stuck Jobs should be replaced/failovered elsewhere quickly.

#### Custom

User-defined scaling using a percentage of running Jobs and optional queue deduction.

- Inputs: `runningJobPercentage` (float), `queueLengthDeduction` (int)

- Formula: `scaleTo = min(desired - deduction - running * percentage, maxReplica)`

- Notes: 

  - If percentage parse fails, falls back to Basic.

#### Accurate

Balance towards desired while staying within capacity; subtract pending from desired unless over max.

- Formula: 

  - If `desired + running > maxReplica`: `scaleTo = maxReplica - running`

  - Else: `scaleTo = desired - pending`

- Use when pending work should defer new creations and capacity must be respected.

#### Eager

Fill available capacity (excluding pending) up to desired.

- Formula: `scaleTo = min(maxReplica - running - pending, desired)`

- Use when it’s safe to aggressively utilize capacity.

#### Choosing a strategy

- Pending-Aware (default): prioritize re-creating stuck Jobs elsewhere.

- Basic: simplest gap-based scaling.

- Accurate: conservative, subtracts pending.

- Eager: aggressive, fills capacity quickly.

- Custom: tailor behavior with percentage and deductions.

## DistributedScaledJob Pod Overrides

`DistributedScaledJob` supports per-cluster overrides of selected pod/container fields. Overrides are defined on each `memberClusters[]` entry and are applied only when creating new Jobs. Existing Jobs are not modified in place.

Supported overrides:

- `podOverrides.nodeSelector`: merged into the pod `nodeSelector` map (override values replace existing keys)

- `podOverrides.tolerations`: merged with deterministic deduplication by identity key `(key, operator, effect, value)`; `jobTargetRef` tolerations are applied first and `podOverrides.tolerations` are applied on top, so when the identity key matches an entry from `podOverrides.tolerations` overrides the one from `jobTargetRef` (last-writer-wins)

- `podOverrides.affinity`: replaces base pod affinity

- `podOverrides.containerOverrides.<containerName>.image`: replaces container image

- `podOverrides.containerOverrides.<containerName>.env`: merged by env var name with override taking precedence

- `podOverrides.containerOverrides.<containerName>.resources`: 

  - `requests` and `limits` are merged by resource name

  - `claims` are replaced when explicitly provided (including empty list to clear existing claims)

If `containerOverrides` references a container name that does not exist in `scaledJobSpec.jobTargetRef.template.spec.containers`, the override is ignored and a warning event is emitted.

Example:

```yaml
apiVersion: keda.kedify.io/v1alpha1
kind: DistributedScaledJob
metadata:
  name: processor-job
spec:
  memberClusters:
    - name: member-cluster-1
      weight: 2
      podOverrides:
        nodeSelector:
          nodepool: gpu
        tolerations:
          - key: "gpu"
            operator: "Exists"
            effect: "NoSchedule"
        affinity:
          nodeAffinity:
            requiredDuringSchedulingIgnoredDuringExecution:
              nodeSelectorTerms:
                - matchExpressions:
                    - key: region
                      operator: In
                      values: ["us-east-1"]
        containerOverrides:
          processor:
            image: my-registry.local/processor:v2
            env:
              - name: LOG_LEVEL
                value: debug
            resources:
              requests:
                cpu: 200m
                memory: 256Mi
              limits:
                cpu: "1"
                memory: 1Gi
    - name: member-cluster-2
      weight: 3
  clusterScheduling:
    strategy: weightedRoundRobin
    failoverPolicy:
      gracePeriod: 1m
      hardTaintDuration: 5m
      softTaintDuration: 3m
  scaledJobSpec:
    jobTargetRef:
      template:
        spec:
          containers:
            - name: processor
              image: myapp:latest
          restartPolicy: Never
    triggers:
      - type: rabbitmq
        name: rabbit
        metadata:
          queueName: tasks
          host: http://guest:password@localhost:15672/path/vhost
          value: "5"
```

Status of the `DistributedScaledJob` provides insights into the job state across member clusters:

```yaml
status:
  desiredJobs: 10
  runningJobs: 8
  pendingJobs: 2
  memberClusterStatuses:
    member-cluster-1:
      description: Cluster is healthy
      id: /etc/mc/kubeconfigs/member-cluster-1.kubeconfig+kedify-agent@member-cluster-1
      runningJobs: 3
      pendingJobs: 1
      stuckJobs: 0
      softTainted: false
      lastStatusChangeTime: "2025-11-05T16:46:39Z"
      state: Ready
      excluded: false
    member-cluster-2:
      description: Cluster is healthy
      id: /etc/mc/kubeconfigs/member-cluster-2.kubeconfig+kedify-agent@member-cluster-2
      runningJobs: 5
      pendingJobs: 1
      stuckJobs: 0
      softTainted: false
      lastStatusChangeTime: "2025-11-05T15:45:44Z"
      state: Ready
      excluded: false
  selector: kedify-agent-distributedscaledjob=processor-job
```

For a walkthrough example on how to set up and use multi-cluster scaling with Kedify, refer to [the examples repository](https://github.com/kedify/examples/tree/main/samples/multicluster).

## FailoverPolicy

`spec.clusterScheduling.failoverPolicy` controls failover timing, taint duration and duplicate-job handling.

```yaml
spec:
  clusterScheduling:
    failoverPolicy:
      gracePeriod: 1m
      hardTaintDuration: 5m
      softTaintDuration: 3m
      duplicationPolicy: keepAll
```

Fields:

- `gracePeriod` (default: `1m`): how long a pending job can stay pending before it is treated as stuck.

- `hardTaintDuration` (default: `5m`): how long a cluster remains hard-tainted after failure.

- `softTaintDuration` (default: `3m`): soft-taint window for escalation. 

  - If another failure happens within this window, taint escalates to hard taint.

  - Set to `0s` for immediate hard taint on first failure.

- `duplicationPolicy` (default: `keepAll`): how to resolve duplicate source/failover jobs in pending-aware failover.

#### Duplication Policy

[![A Pending source job causes a replacement to be scheduled; four duplication policies determine which job survives and when cleanup occurs.](https://docs.kedify.io/assets/images/docs/dsj-failover-duplicates.svg)](https://docs.kedify.io/assets/images/docs/dsj-failover-duplicates.svg)

Scroll to exploreDiagram description

In pending-aware DistributedScaledJob failover, a source job remains Pending beyond its grace period and a replacement is scheduled in an eligible cluster. keepAll retains both jobs. preferFailover removes the source only after a healthy replacement is confirmed. preferSource removes the replacement when the source resumes ownership. immediateSourceCleanup deletes the source immediately after failover scheduling, before replacement health is guaranteed.

- `keepAll`

   

  - Behavior: keep source and failover jobs running when both exist.

  - Provisioning tendency: can temporarily overprovision during failover/recovery windows (availability-first).

  - Underprovisioning risk: lowest among the policies.

- `preferFailover`

   

  - Behavior: prefer the failover job; source is deleted once a healthy failover replacement is confirmed.

  - Provisioning tendency: short-lived overprovisioning can happen while waiting for replacement health confirmation.

  - Underprovisioning risk: low to medium (mainly during failover transitions if replacement cannot be confirmed quickly).

- `preferSource`

   

  - Behavior: prefer the original source job; replacement is deleted when source should resume ownership.

  - Provisioning tendency: generally avoids prolonged overprovisioning.

  - Underprovisioning risk: medium to high during unstable source-cluster recovery (source can be preferred before fully stable).

- `immediateSourceCleanup`

   

  - Behavior: source job is deleted immediately after failover scheduling.

  - Provisioning tendency: minimizes overprovisioning.

  - Underprovisioning risk: highest if the replacement is delayed or fails to become healthy.

Rule of thumb:

- If you optimize for continuity/availability, use `keepAll` or `preferFailover`.

- If you optimize for strict capacity/cost control, use `immediateSourceCleanup` or `preferSource`, accepting higher underprovisioning risk.

## Cluster Scheduling Strategies

#### WeightedRoundRobin

`weightedRoundRobin` is the default strategy. If `spec.clusterScheduling.strategy` is omitted, `DistributedScaledJob` uses `weightedRoundRobin`. In this mode, jobs are distributed by `memberClusters[].weight`.

- hard-tainted clusters are excluded from scheduling for all workloads

- soft-tainted clusters remain eligible

- `memberClusters[].scheduling.priority` is ignored in this mode

#### PriorityFailover

`priorityFailover` provides primary/failover behavior. The scheduler prefers the highest `memberClusters[].scheduling.priority` cluster, and only falls back when that cluster is excluded (for example tainted or overloaded).

Short example focused on failover configuration: Member cluster `member-primary` is preferred by the scheduler for new jobs until a job fails to progress from `Pending` to `Running` for longer than `spec.clusterScheduling.failoverPolicy.gracePeriod`. After the grace period is reached, the cluster is tainted for this affinity tuple (`class: team-a`, `size: 4`): jobs with size `4` and higher are excluded from that tainted cluster, while smaller jobs can still be scheduled there.

```yaml
apiVersion: keda.kedify.io/v1alpha1
kind: DistributedScaledJob
metadata:
  name: processor-job-failover
spec:
  clusterScheduling:
    strategy: priorityFailover
    workloadAffinity:
      class: team-a
      size: 4
    failoverPolicy:
      gracePeriod: 1m
      hardTaintDuration: 5m
      softTaintDuration: 3m
  memberClusters:
    - name: member-primary
      scheduling:
        priority: 100
    - name: member-failover
      scheduling:
        priority: 0
  scaledJobSpec:
    scalingStrategy:
      strategy: pendingAware
```

Important differences vs `weightedRoundRobin`:

- `clusterScheduling.workloadAffinity` is required in `priorityFailover`

- `memberClusters[].scheduling.priority` is required for each member cluster in `priorityFailover`

- failover taints are evaluated by `(order, size, priority)`: 

  - a taint recorded at size `N` excludes workloads with size `>= N`

  - workloads with smaller size can still use the same cluster

- transient `create Job` API failures are treated as transient and do not taint the cluster in `priorityFailover`

- dsj `priorityFailover` supports `scaledJobSpec.scalingStrategy.strategy: pendingAware` (or omitted strategy, which defaults to `pendingAware`)

- `podOverrides.affinity` is only pod-placement configuration and does not drive cluster failover selection

## DistributedScaledJob Specification

The following example of a `DistributedScaledJob` splits the execution of job processing from the RabbitMQ task queue between two member clusters in a 2:3 ratio.

```yaml
apiVersion: keda.kedify.io/v1alpha1
kind: DistributedScaledJob
metadata:
  name: processor-job
spec:
  memberClusters: # optional list of member clusters to use, if omitted all registered member clusters will be used
    - name: member-cluster-1
      weight: 2 # weight determines the proportion of jobs to be allocated to this cluster
    - name: member-cluster-2
      weight: 3
  clusterScheduling:
    strategy: weightedRoundRobin
    failoverPolicy:
      gracePeriod: 1m # wait before re-creating jobs that do not progress from Pending to Running
      hardTaintDuration: 5m # taint a failing cluster before scheduling jobs on it again
      softTaintDuration: 3m # if another failure happens in this window, apply a hard taint
  scaledJobSpec: # standard ScaledJob spec
    failedJobsHistoryLimit: 2 # keep up to 2 failed jobs, delete all older
    successfulJobsHistoryLimit: 2 # keep up to 2 jobs that completed successfully, delete all older
    jobTargetRef:
      template:
        spec:
          containers:
            - name: processor
              image: myapp:latest
              command: ["process"]
          restartPolicy: Never
    pollingInterval: 30
    maxReplicaCount: 20
    scalingStrategy:
      strategy: pendingAware # pending/stuck jobs can be re-created on another cluster
    triggers:
      - type: rabbitmq
        name: rabbit
        metadata:
          queueName: tasks
          host: http://guest:password@localhost:15672/path/vhost
          value: "5"
```

In this example, the `DistributedScaledJob` named `processor-job` is configured to scale Jobs across two member clusters.

For `DistributedScaledJob`, each trigger in the `scaledJobSpec` must have a `name` field specified. This is a mandatory requirement.

`clusterScheduling.failoverPolicy` controls how taints are applied to failing clusters. If omitted, defaults are applied (`gracePeriod: 1m`, `hardTaintDuration: 5m`, `softTaintDuration: 3m`). See [FailoverPolicy](https://docs.kedify.io/features/multi-cluster-scaling/#failoverpolicy) for details.

The `jobTargetRef` field contains the standard Kubernetes Job template specification. Jobs are created in the member clusters based on the scaling metrics and cluster weights.

#### Soft And Hard Taints

Soft and hard taints for failing clusters: when a call from KEDA cluster to member cluster fails, that cluster is not removed from scheduling immediately. Instead, it is soft-tainted first. After a second failure, the soft taint is escalated to a hard taint. This helps prevent unnecessary scheduling pauses due to temporary network glitches.

Last updated: Sep 21, 2026

---
Canonical: https://docs.kedify.io/reference/kubernetes-api/distributedscaledjob/
Source: src/content/docs/reference/kubernetes-api/distributedscaledjob.md
Documentation index: https://docs.kedify.io/llms.txt
