Skip to content

[Bug] A served model loses its pending count and output predictor when its only pod or its adapter is updated #3031

Description

@Jackmeson1

🐛 Describe the bug

When the only pod serving a model gets an ordinary informer update (for example a status heartbeat), or a
ModelAdapter is updated with the same instance list, the cache deletes the model and builds a new one. The model's
in-flight accounting and learned output predictor are lost while the model is still being served.

  • updatePod is a delete plus a re-add (pkg/cache/informers.go, lines 185–212 at 453ab36).
    deletePodAndModelMappingLocked deletes the model when its last pod mapping goes (lines 599–601), and the re-add
    creates a new Model with a new OutputPredictor (lines 486–491).
  • updateModelAdapter removes every old instance before adding the new list (lines 314–321), so an unchanged
    instance list does the same thing.
  • DoneRequestCount then decrements the new object (pkg/cache/cache_impl.go, lines 397–399), so the model-level
    pendingRequests goes to -1. The balanced-accounting test (pkg/cache/cache_test.go, around line 698) expects
    exactly one increment and one decrement.
  • GetOutputPredictor returns the fresh predictor, which has no history and returns the cold value.

The comment at addPodLocked (around line 390) already says a same-IP re-add must resume counters because the pod
never stopped serving. That resume covers the pod snapshot only, not the model.

Steps to Reproduce

Unit level, using the cache's own addPod/updatePod/AddRequestCount/DoneRequestCount paths:

  1. Add one pod serving model m. Call AddRequestCount for m and record a few outputs into its predictor.
  2. Deliver an updatePod for the same pod (same IP, model label unchanged).
  3. Check the model state, then call DoneRequestCount.

At 453ab36:

only-replica status update rebuilt the model: pending=0 want 1, samePredictor=false, predict=1 want not cold 1, afterDone=-1 want 0
unchanged adapter update rebuilt the model: pending=0 want 1, samePredictor=false, predict=1 want not cold 1, afterDone=-1 want 0

The same steps with a second replica of m keep the counter and the predictor, because the model never reaches
zero pods.

Expected behavior

A model that is served before and after an update keeps its *Model: pending counters, output predictor and its
history, and router. A real delete of the last pod, or a model-name change, still ends the model's lifetime.

What this reproduction shows is the counter going to -1 and the predictor resetting. Effects on queueing, latency
or routing were not measured.

Proposed fix, and two questions before I write it

Reconcile membership in place in informers.go, under Store.mu: compute the incoming eligibility and edges before
removing the departing ones, keep the published *Model while it is still served, and diff adapter instance lists
instead of replacing them. The fix is about 300 production lines plus tests, so I'd split it into two PRs: pod
updates first, then adapter updates.

Two behaviours change on purpose, and I'd like your view on both before writing the code:

  1. Router construction retry. Today a model whose queue router failed to construct gets another attempt only
    because updates recreate the model. Once the model is kept, the retry has to be explicit. The proposal is one
    construction attempt per membership event while the router is nil, keeping any non-nil router.
  2. Adapter edge ownership. To keep a model across adapter updates, the cache needs to record which ModelAdapter
    (by namespace/name) installed each adapter edge, with a deterministic order, so that removing one source does
    not drop edges another still provides. That record is discarded when its last edge goes.

Does this direction work for you? I have the reproduction ready and can open the first PR.

Environment

  • AIBrix version: 453ab36
  • Deployment environment: unit tests of pkg/cache (Go 1.27)
  • LLM(s) being used: not model specific

Area

Gateway / cache

Activity

  1. github-actions commented on Oct 13, 2026

    @github-actions

    Thanks for contributing to AIBrix! Please review the contribution guide and make sure this issue contains enough context for maintainers to reproduce or evaluate it.

  2. added a commit that references this issue on Oct 14, 2026
    ae08057
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions