Repository navigation
[Bug] A served model loses its pending count and output predictor when its only pod or its adapter is updated #3031
Description
🐛 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.
updatePodis a delete plus a re-add (pkg/cache/informers.go, lines 185–212 at453ab36).
deletePodAndModelMappingLockeddeletes the model when its last pod mapping goes (lines 599–601), and the re-add
creates a newModelwith a newOutputPredictor(lines 486–491).updateModelAdapterremoves every old instance before adding the new list (lines 314–321), so an unchanged
instance list does the same thing.DoneRequestCountthen decrements the new object (pkg/cache/cache_impl.go, lines 397–399), so the model-level
pendingRequestsgoes to-1. The balanced-accounting test (pkg/cache/cache_test.go, around line 698) expects
exactly one increment and one decrement.GetOutputPredictorreturns 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:
- Add one pod serving model
m. CallAddRequestCountformand record a few outputs into its predictor. - Deliver an
updatePodfor the same pod (same IP, model label unchanged). - 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:
- 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. - Adapter edge ownership. To keep a model across adapter updates, the cache needs to record which ModelAdapter
(bynamespace/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
github-actions commentedon Oct 13, 2026
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.
fix(cache): preserve served model state across pod and ModelAdapter u…