Skip to content

fix(cache-aware): use backend global backlog for load-aware fallback#1863

Open
2JooYeon wants to merge 2 commits into
lightseekorg:mainfrom
2JooYeon:fix/cache-aware-global-load
Open

fix(cache-aware): use backend global backlog for load-aware fallback#1863
2JooYeon wants to merge 2 commits into
lightseekorg:mainfrom
2JooYeon:fix/cache-aware-global-load

Conversation

@2JooYeon

@2JooYeon 2JooYeon commented Jul 1, 2026

Copy link
Copy Markdown

Description

Problem

  • cache_aware still uses per-gateway worker.load() for count-based imbalance detection and min-load fallback.
  • In multi-gateway deployments, each router only sees its own local in-flight requests.
  • This can make count-spread detection and fallback worker selection inconsistent across router replicas.

Solution

  • Prefer backend-reported load snapshots from WorkerMonitor when available.
  • Use global running + waiting request count for cache-aware load decisions.
  • Fall back to local worker.load() when a worker has no fresh snapshot.

Changes

  • Added WorkerLoadResponse::total_running_waiting_reqs().
  • Added CacheAwarePolicy::effective_load() to resolve global-or-local load.
  • Updated cache_aware to use effective load for:
    • count-based imbalance detection
    • min-load fallback selection
    • event-driven overlap tie-breaks
  • Marked cache_aware as load-aware so cache-aware-only deployments poll backend load snapshots.

Test Plan

  • Added regression tests for cache-aware effective load selection:

    • cache_aware_uses_injected_global_load_for_selection
      • verifies backend-reported global load overrides local worker.load()
    • cache_aware_falls_back_to_local_load_when_worker_unmonitored
      • verifies missing backend snapshots fall back to local worker.load()
  • Manually validated with a multi-gateway mock setup:

    • built both main and this branch with:
      • cargo build --release -p smg -p mock-server
    • started 3 mock workers and 2 gateway instances
    • ran the same load test against both versions
    • verified that this branch uses backend-reported global load for cache_aware fallback decisions instead of each gateway's local worker.load()
Checklist
  • cargo +nightly fmt passes
  • cargo clippy --all-targets --all-features -- -D warnings passes
  • (Optional) Documentation updated
  • (Optional) Please join us on Slack #sig-smg to discuss, review, and merge PRs

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Added support for more consistent cache-aware load balancing using a gateway-wide in-flight request view.
  • Bug Fixes

    • Improved shortest-queue/overlap tie-breaking to use the same global load snapshot across routing decisions.
    • When global load data is missing for a worker, decisions correctly fall back to that worker’s local load.
  • Tests

    • Added/updated unit tests to verify injected global-load tie-breaking behavior and the local fallback case.

@github-actions github-actions Bot added protocols Protocols crate changes model-gateway Model gateway crate changes labels Jul 1, 2026
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a worker load aggregation helper, switches cache_aware routing to use snapshot-based load resolution with local fallback, and wires cache_aware policies to receive backend load snapshots through registry and app-context setup.

Changes

Global load snapshot adoption in cache_aware

Layer / File(s) Summary
WorkerLoadResponse aggregate helper
crates/protocols/src/worker.rs
Adds total_running_waiting_reqs summing running+waiting requests across all DP-rank load snapshots.
Snapshot-aware load selection
model_gateway/src/policies/cache_aware.rs
Adds effective_load, uses it in load logging and worker selection, and updates overlap tie-breaking to read the backend snapshot once and fall back to local load when needed.
Registry and app context wiring
model_gateway/src/policies/registry.rs, model_gateway/src/app_context.rs
Adds shared policy-context injection, includes cache_aware in load-aware policy polling, and moves worker-monitor load receiver wiring into app context setup.
Cache-aware routing tests
model_gateway/src/policies/cache_aware.rs
Adds snapshot injection helpers and extends selection tests to cover global-load tie-breaking and local fallback.
Registry load-snapshot tests
model_gateway/src/policies/registry.rs
Adds watch-based load snapshot test helpers and coverage for cache_aware policy context injection and selection behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
    participant AppContextBuilder
    participant PolicyRegistry
    participant WorkerMonitor
    participant CacheAwarePolicy
    participant load_rx

    AppContextBuilder->>WorkerMonitor: create worker monitor
    AppContextBuilder->>PolicyRegistry: set_load_receiver(Some(worker_monitor.subscribe()))
    PolicyRegistry->>load_rx: store backend load snapshot receiver
    PolicyRegistry->>CacheAwarePolicy: inject kv_event_monitor + load_rx
    CacheAwarePolicy->>load_rx: read latest snapshot
    load_rx-->>CacheAwarePolicy: WorkerLoadResponse map
    CacheAwarePolicy->>CacheAwarePolicy: total_running_waiting_reqs() / effective_load()
    CacheAwarePolicy->>CacheAwarePolicy: select_worker / score_overlap
Loading

Possibly related PRs

  • lightseekorg/smg#554: Builds directly on WorkerLoadResponse by adding total_running_waiting_reqs() and using it in routing.
  • lightseekorg/smg#1621: Both modify cache-aware routing’s use of WorkerMonitor load snapshots for selection behavior.
  • lightseekorg/smg#1758: Both modify cache_aware load-gathering and tie-break logic in select_worker.

Suggested labels: tests

Suggested reviewers: CatherineSue, key4ng, claude

Poem

Hoppity hop, the queue is bright,
Global loads now guide the flight.
Running and waiting, summed with care,
Local fallback still stays there.
This bunny nods: cache-aware! 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 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: cache-aware routing now prefers backend global backlog for load-aware fallback decisions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@2JooYeon 2JooYeon marked this pull request as draft July 1, 2026 08:37
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ef991b5dde

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +349 to +352
snapshot
.and_then(|loads| loads.get(url))
.map(|resp| resp.total_running_waiting_reqs().max(0) as usize)
.unwrap_or(local_load)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Include local in-flight load with backend snapshots

When a worker has any monitor entry, this path uses the polled backend count and ignores local_load entirely. Because the load monitor only refreshes periodically, a snapshot can still say a worker is idle while this gateway has since dispatched many in-flight requests to it; until the next poll, count-spread detection and shortest-queue selection continue treating that worker as idle and can stampede it. Consider taking at least max(snapshot_count, local_load) (or another since-poll correction) instead of falling back to local load only when the snapshot entry is missing.

Useful? React with 👍 / 👎.

Comment thread model_gateway/src/policies/registry.rs

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
model_gateway/src/policies/registry.rs (1)

448-458: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Doc comment now stale after including cache_aware in the load-aware set.

get_all_load_aware_policies was updated so the nested is_load_aware predicate includes cache_aware, so remove_worker_from_load_aware now also iterates cache_aware policies (harmlessly, since its remove_worker is a no-op per the new comment above). The doc here still says "all load-aware policies (power_of_two, least_load)" — worth updating to avoid confusing future readers about which policies flow through this path.

✏️ Suggested doc update
-    /// Drop a removed worker's cached load report from all load-aware policies
-    /// (`power_of_two`, `least_load`).
+    /// Drop a removed worker's cached load report from all load-aware policies
+    /// (`power_of_two`, `least_load`; `cache_aware` is also included but its
+    /// `remove_worker` is a no-op).
🤖 Prompt for 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.

In `@model_gateway/src/policies/registry.rs` around lines 448 - 458, Update the
stale doc comment on remove_worker_from_load_aware to match the current behavior
of get_all_load_aware_policies, which now includes cache_aware in addition to
power_of_two and least_load. Keep the comment consistent with the policy set
actually iterated by remove_worker, and mention that cache_aware is included
even though its remove_worker is a no-op.
🤖 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 `@crates/protocols/src/worker.rs`:
- Line 1158: The request count calculation in the worker routing logic is
summing two i32 counters before converting to i64, which can overflow first;
update the expression in the code path using the lambda over the load value so
each counter is cast to i64 before addition. Locate the mapping that uses
num_running_reqs and num_waiting_reqs in the worker selection logic and ensure
the sum is performed in i64 to preserve correct cache-aware routing.

---

Outside diff comments:
In `@model_gateway/src/policies/registry.rs`:
- Around line 448-458: Update the stale doc comment on
remove_worker_from_load_aware to match the current behavior of
get_all_load_aware_policies, which now includes cache_aware in addition to
power_of_two and least_load. Keep the comment consistent with the policy set
actually iterated by remove_worker, and mention that cache_aware is included
even though its remove_worker is a no-op.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2fdb5764-9bd4-4025-8fe2-04f6bd6fe695

📥 Commits

Reviewing files that changed from the base of the PR and between f54dabd and ef991b5.

📒 Files selected for processing (3)
  • crates/protocols/src/worker.rs
  • model_gateway/src/policies/cache_aware.rs
  • model_gateway/src/policies/registry.rs

Comment thread crates/protocols/src/worker.rs Outdated
@2JooYeon 2JooYeon marked this pull request as ready for review July 1, 2026 09:23
@mergify

mergify Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Hi @2JooYeon, the DCO sign-off check has failed. All commits must include a Signed-off-by line.

To fix existing commits:

# Sign off the last N commits (replace N with the number of unsigned commits)
git rebase HEAD~N --signoff
git push --force-with-lease

To sign off future commits automatically:

  • Use git commit -s every time, or
  • VSCode: enable Git: Always Sign Off in Settings
  • PyCharm: enable Sign-off commit in the Commit tool window

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

cache_aware.set_load_receiver(Some(rx.clone()));

P2 Badge Wire receivers for label-selected cache-aware policies

When cache_aware is selected from a worker policy label while the router default is not cache_aware, UpdatePoliciesStep still creates this policy via on_worker_added(..., policy_hint) (model_gateway/src/workflow/steps/shared/update_policies.rs:107-118), but self.load_rx remains None because AppContextBuilder only calls registry.set_load_receiver inside the default-policy CacheAware branch (model_gateway/src/app_context.rs:650-665). The policy is then included in get_all_load_aware_policies, so the monitor polls, but effective_load never receives the watch channel and silently falls back to per-gateway local counters, leaving the new global-backlog routing disabled for per-model cache-aware deployments.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@2JooYeon 2JooYeon force-pushed the fix/cache-aware-global-load branch from ef991b5 to 00baf82 Compare July 2, 2026 09:30
@mergify

mergify Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Hi @2JooYeon, this PR has merge conflicts that must be resolved before it can be merged. Please rebase your branch:

git fetch origin main
git rebase origin/main
# resolve any conflicts, then:
git push --force-with-lease

@mergify mergify Bot added the needs-rebase PR has merge conflicts that need to be resolved label Jul 2, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 00baf82a30

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread model_gateway/src/policies/registry.rs
2JooYeon added 2 commits July 2, 2026 18:40
Signed-off-by: 2JooYeon <lewndus7749@naver.com>
Signed-off-by: 2JooYeon <lewndus7749@naver.com>
@2JooYeon 2JooYeon force-pushed the fix/cache-aware-global-load branch from 00baf82 to b3f4758 Compare July 2, 2026 09:48
@mergify mergify Bot removed the needs-rebase PR has merge conflicts that need to be resolved label Jul 2, 2026

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
model_gateway/src/policies/registry.rs (1)

307-317: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Inject context after factory creation so cache-aware aliases are covered.

Line 308 only matches the exact "cache_aware" string, but PolicyFactory::create_by_name accepts aliases/case variants like "cacheaware". Those supported names now create CacheAwarePolicy instances without the global load receiver/KV monitor, silently reverting to local-load routing.

Proposed fix
     /// Create a policy from a type string (delegates to PolicyFactory)
     fn create_policy_from_type(&self, policy_type: &str) -> Arc<dyn LoadBalancingPolicy> {
-        if policy_type == "cache_aware" {
-            let policy: Arc<dyn LoadBalancingPolicy> = Arc::new(CacheAwarePolicy::new());
-            self.inject_policy_context(&policy);
-            policy
-        } else {
-            PolicyFactory::create_by_name(policy_type).unwrap_or_else(|| {
-                warn!("Unknown policy type '{}', using default", policy_type);
-                Arc::clone(&self.default_policy)
-            })
-        }
+        let policy = PolicyFactory::create_by_name(policy_type).unwrap_or_else(|| {
+            warn!("Unknown policy type '{}', using default", policy_type);
+            Arc::clone(&self.default_policy)
+        });
+        self.inject_policy_context(&policy);
+        policy
     }
🤖 Prompt for 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.

In `@model_gateway/src/policies/registry.rs` around lines 307 - 317, Update
create_policy_from_type in registry.rs so context injection happens for every
CacheAwarePolicy returned by PolicyFactory::create_by_name, not just the exact
"cache_aware" string. The current special-case branch misses aliases and case
variants like "cacheaware", which can skip inject_policy_context and leave the
policy without the global load receiver/KV monitor. After resolving the policy
from PolicyFactory, detect CacheAwarePolicy instances and call
inject_policy_context before returning them, while keeping the default-policy
fallback behavior unchanged.
🤖 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.

Outside diff comments:
In `@model_gateway/src/policies/registry.rs`:
- Around line 307-317: Update create_policy_from_type in registry.rs so context
injection happens for every CacheAwarePolicy returned by
PolicyFactory::create_by_name, not just the exact "cache_aware" string. The
current special-case branch misses aliases and case variants like "cacheaware",
which can skip inject_policy_context and leave the policy without the global
load receiver/KV monitor. After resolving the policy from PolicyFactory, detect
CacheAwarePolicy instances and call inject_policy_context before returning them,
while keeping the default-policy fallback behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a3c83d1c-5e61-46b7-a17c-82f43ed4c24d

📥 Commits

Reviewing files that changed from the base of the PR and between 00baf82 and b3f4758.

📒 Files selected for processing (4)
  • crates/protocols/src/worker.rs
  • model_gateway/src/app_context.rs
  • model_gateway/src/policies/cache_aware.rs
  • model_gateway/src/policies/registry.rs

@2JooYeon

2JooYeon commented Jul 3, 2026

Copy link
Copy Markdown
Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request integrates the backend-reported global request count (running + waiting) into the cache_aware load balancing policy to ensure consistent imbalance detection and shortest-queue selection across multi-gateway deployments. It introduces an effective_load helper that prefers global loads from the WorkerMonitor snapshot and falls back to local in-flight counters when unavailable. Additionally, the cache_aware policy is registered as load-aware to trigger periodic polling of /v1/loads, and the policy registry is updated to properly inject context into prefill and decode policies. I have no feedback to provide as there are no review comments.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@mergify

mergify Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Hi @2JooYeon, this PR has merge conflicts that must be resolved before it can be merged. Please rebase your branch:

git fetch origin main
git rebase origin/main
# resolve any conflicts, then:
git push --force-with-lease

@mergify mergify Bot added the needs-rebase PR has merge conflicts that need to be resolved label Jul 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

model-gateway Model gateway crate changes needs-rebase PR has merge conflicts that need to be resolved protocols Protocols crate changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant