Skip to content

operator: scale down if proxy rollout is stuck - #65

Open
eric-higgins-ai wants to merge 1 commit into
NVIDIA:mainfrom
eric-higgins-ai:improved-rollout-behavior
Open

operator: scale down if proxy rollout is stuck#65
eric-higgins-ai wants to merge 1 commit into
NVIDIA:mainfrom
eric-higgins-ai:improved-rollout-behavior

Conversation

@eric-higgins-ai

@eric-higgins-ai eric-higgins-ai commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Rollouts to the proxy statefulset currently become stuck if scaling becomes necessary in the middle of the rollout. For example, if a node is removed from the cluster during the rollout then a pod will be pending and the rollout will get stuck there.

This was fixed in v3.0.0 for target pods by not blocking the rollout on unschedulable pods, but this isn't possible for proxies because they use the default RollingUpdate update strategy of StatefulSets. Instead, we detect the case where a pod is unschedulable during a rollout and scaling is needed and perform the scaling before continuing with the rollout.

Summary by CodeRabbit

  • Bug Fixes
    • Improved autoscaling desired cluster sizing by using the union of nodes where AIStore is running and nodes where it is schedulable.
    • Autoscaling updates now take effect only after completing rollout to the proxy and target statefulsets.
    • Enhanced detection of stalled proxy rollouts caused by unschedulable proxy pods during scale-down.
    • Proxy scaling will proceed when a rollout is blocked, helping prevent and reduce rollout delays.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The proxy reconciler detects unschedulable pods during scale-down rollouts and permits proxy scaling without the usual confirmation delay. The changelog documents the autoscaling and blocked-rollout behavior.

Changes

Proxy rollout autoscaling

Layer / File(s) Summary
Detect blocked proxy rollouts
operator/internal/controller/aistore/proxy_controller.go
Proxy reconciliation scans selected pods and marks rollouts blocked when unschedulable proxy pods are present during scale-down.
Scale blocked proxy rollouts
operator/internal/controller/aistore/proxy_controller.go, operator/CHANGELOG.md
Scaling proceeds during blocked rollouts without confirmation, and the changelog documents the updated autoscaling behavior.

Estimated code review effort: 3 (Moderate) | ~15–30 minutes

Sequence Diagram(s)

sequenceDiagram
  participant handleProxyState
  participant hasUnschedulableProxyPods
  participant KubernetesPodAPI
  participant handleProxyScale
  handleProxyState->>hasUnschedulableProxyPods: check proxy pods
  hasUnschedulableProxyPods->>KubernetesPodAPI: list pods by selector
  KubernetesPodAPI-->>hasUnschedulableProxyPods: return pod statuses
  hasUnschedulableProxyPods-->>handleProxyState: return rolloutBlocked
  handleProxyState->>handleProxyScale: scale blocked proxy rollout
Loading

Suggested labels: bug

Suggested reviewers: aaronnw

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: scaling down the proxy when a rollout is stuck.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@operator/CHANGELOG.md`:
- Line 31: Remove the duplicate “### Changed” heading from the changelog,
preserving the existing heading and its associated content.
- Line 29: Update the changelog entry describing the proxy rollout condition to
specify an unschedulable proxy pod rather than a generic pending pod, matching
the controller’s isPodUnschedulable gating behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 0616e253-9d97-4cf6-9078-e87b6d66483e

📥 Commits

Reviewing files that changed from the base of the PR and between c322e16 and 29e21d1.

📒 Files selected for processing (2)
  • operator/CHANGELOG.md
  • operator/internal/controller/aistore/proxy_controller.go

Comment thread operator/CHANGELOG.md Outdated
Comment thread operator/CHANGELOG.md Outdated
@eric-higgins-ai
eric-higgins-ai force-pushed the improved-rollout-behavior branch from 29e21d1 to 2d7a9a4 Compare July 23, 2026 17:19

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@operator/internal/controller/aistore/proxy_controller.go`:
- Around line 180-182: Update the comment above the scaling condition in the
proxy controller to describe rollout blocking as allowing scaling when the
desired proxy size is lower, rather than attributing it only to user-initiated
scale-downs. Leave the scaling logic unchanged.
- Around line 218-223: Update Reconciler.hasUnschedulableProxyPods to return
(bool, error), propagate the ListPods error instead of returning false, and
update every caller to handle and bubble the error so recovery is not skipped
after proxy pod-list failures.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 01a0848a-2fec-463a-ae57-c07466b90a2b

📥 Commits

Reviewing files that changed from the base of the PR and between 29e21d1 and 2d7a9a4.

📒 Files selected for processing (2)
  • operator/CHANGELOG.md
  • operator/internal/controller/aistore/proxy_controller.go

Comment thread operator/internal/controller/aistore/proxy_controller.go Outdated
Comment thread operator/internal/controller/aistore/proxy_controller.go
Signed-off-by: eric-higgins-ai <erichiggins@applied.co>
@eric-higgins-ai
eric-higgins-ai force-pushed the improved-rollout-behavior branch from 2d7a9a4 to 4cfed1d Compare July 23, 2026 17:48

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@operator/CHANGELOG.md`:
- Around line 27-29: Correct the misspelled “scheduable” term in the autoscaling
changelog bullet to “schedulable,” leaving the rest of the entry unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 02b50a8b-0572-41e7-85d2-846ca75d9763

📥 Commits

Reviewing files that changed from the base of the PR and between 2d7a9a4 and 4cfed1d.

📒 Files selected for processing (2)
  • operator/CHANGELOG.md
  • operator/internal/controller/aistore/proxy_controller.go

Comment thread operator/CHANGELOG.md
@aaronnw aaronnw added the bug Something isn't working label Jul 23, 2026
@aaronnw

aaronnw commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

This may work for emptyDir + autoscaling deployment but I think it's problematic for deployments where proxies are locked to a node and size is driven from spec, which we still need to support. Allowing scale to proceed won't resolve the issue in that case.

If a proxy is truly unschedulable then I believe we could instead check ais.GetProxySize() < ss.Spec.Replicas (GetProxySize checks against expected nodes which already checks the count of valid hosts). Then we could say, if we are using emptyDir + autoscale, to skip past the rollout block and proceed to scale before rollout is complete. That bypasses doing a full second proxy pod list to check schedule status.

One thing we may need to consider is how this could impact rollout partition/primary management.

@eric-higgins-ai

Copy link
Copy Markdown
Contributor Author

This may work for emptyDir + autoscaling deployment but I think it's problematic for deployments where proxies are locked to a node and size is driven from spec

I think there are a few different cases, and this addresses the problem to varying degrees of completeness:

  1. emptyDir + autoscaling: this fully addresses the problem, the rollout can be done fully autonomously
  2. emptyDir/hostPath/PVC that can move to a new node + size hardcoded in spec: this change allows you to manually decrease the size in the spec to unblock the rollout. Previously, the only way to fix the rollout would be to decrease the size of the proxy StatefulSet, which imo is sort of hacky
  3. Proxy locked to a particular node: this PR doesn't address this case at all. The only way I can think of to address this is to implement similar rollout behavior to that for targets, which would allow us to skip unschedulable pods. This was a bigger change than I wanted to make to address this (admittedly somewhat niche) edge case, but I'm down to do it if you prefer that approach

At a high level, while this PR only allows the rollout to continue fully autonomously in the emptyDir + autoscaling case, it doesn't cause a regression in any of the other cases.

If a proxy is truly unschedulable then I believe we could instead check ais.GetProxySize() < ss.Spec.Replicas

This is a good point, though we don't get the benefit to case 2. mentioned above. If you think it's worth losing that to get rid of the additional proxy pod list then I'm down to make the change.

One thing we may need to consider is how this could impact rollout partition/primary management.

Now that you mention it, the rollout primary management has been slightly broken since this change. It assumes proxy-0 is always the primary, which is now not the case. I don't think this breaks the primary behavior any more than it's already broken though. The flow would be like:

  1. proxy-n is unschedulable
  2. Scale down. Highest ordinal pod gets deleted and proxy-n is rescheduled on the now-vacant node
  3. The rollout continues to proxy-(n-1)
  4. Once the rollout finishes updating proxy-1 it sets the highest pod as primary and then rolls out to proxy-0

@aaronnw aaronnw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My main issue is "scale down if proxy rollout is stuck" doesn't describe what we want in all scenarios. We've previously required rollout to succeed before scaling because autoscaling wasn't supported.

I think, to rephrase it, we really want rollout to not block scale down however it's triggered, regardless of pod status. Avoiding this bunch of lists, filters, and if statements will help us avoid races and edge cases. If that scaling is driven by autoscale then fine, otherwise we respect the spec. If we can support this then we don't need to have special cases for each type of deployment.

But the tricky part is managing setting the primary proxy. Requiring rollout to succeed first allowed us to rely on the statefulset status to know we could assign ordinal 0 as primary and avoid elections during scaling.

There is a catch here now because if we reach that last stage of rollout we'll have set the highest ordinal as primary, which is exactly what will get removed if scale down kicks in. So scale down will try to set a new primary with reassignPrimaryForScaledown which will likely pick proxy-0, right around when the statefulset updates it. So we need to tighten that guard by setting primary to a different eligible proxy. This is another thing we can fix in a smaller scoped patch.

statefulsetScalingNeeded also blocks on isRolloutInProgress so will need an update there as well.

@aaronnw

aaronnw commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

I think the easy solution is:

  1. reassignPrimaryForScaledown requires the new primary to be both actually ready and on the newest revision (instead of picking lowest "running" pod) -- should probably prefer highest ordinal (oldest during rollout) that won't be scaled down.
  2. Allow scale down specifically during an existing rollout. No other guards. Part 1 should block this until we have at least one updated pod to make our new primary that survives both actions.

Edit: I'll push an update first to reactor and clean up our scale-down flow. It's fairly convoluted and untested as is.

@aaronnw

aaronnw commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

I think the easy solution is:

1. `reassignPrimaryForScaledown` requires the new primary to be both actually ready and on the newest revision (instead of picking lowest "running" pod) -- should probably prefer highest ordinal (oldest during rollout) that won't be scaled down.

2. Allow scale _down_ specifically during an existing rollout. No other guards. Part 1 should block this until we have at least one updated pod to make our new primary that survives both actions.

Part 1 is implemented. Part 2 will require more than just removing the guard. We'll need to watch out for any temporary readiness drops from rollout making it look like we need to scale, guard around low-replica-count edge cases, and be careful about requeue loops and making sure we hit handleProxyRollout.

We'll potentially also need to make both rollout and scale prefer the same primary -- today rollout sets to highest and scale still wants lowest valid proxy. I'm thinking rollout may want to prefer resetting to partition + 1 instead of highest, so we simply don't have to re-assign primary often on scale.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants