feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin - #997

Merged
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority
Aug 19, 2026
Merged

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin#997
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority

Conversation

@jarvis9443

Copy link
Copy Markdown
Contributor

What

Load-balancing taxonomy rework for model groups (the AISIX-Cloud#1206 requirement), aligned with the APISIX balancer model: weight and priority are per-target attributes available under every strategy, and consistent hashing is a strategy of its own rather than a flag on another one.

The driving scenario: two pools of direct models A & B — all traffic on A while any A member is healthy, spill to B only when the whole A pool is down, consistent-hash distribution inside both pools:

routing:
strategy: consistent_hashhash_on: # optional; default = [header x-aisix-routing-key, api_key]
- { type: header, name: x-session-id }targets:
- { model: vllm-a1 } # priority 0 = active pool
- { model: vllm-a2 }
- { model: vllm-a3 }
- { model: vllm-b1, priority: -1 } # backup pool (APISIX-style: higher value preferred)
- { model: vllm-b2, priority: -1 }

Changes

  • targets[].priority (i32, default 0, higher preferred, negatives for backups): targets partition into tiers; the strategy orders each tier independently — per-tier balancer state, mirroring APISIX's per-priority pickers; tiers concatenate, so a lower tier only receives traffic when every higher-tier target failed or was health/cooldown-filtered. max_fallbacks caps the concatenated walk, and the first request that discovers a fully-dead active tier still succeeds (in-request tier spill).
  • strategy: consistent_hash: ketama-style ring (160 points × weight units, capped; FNV-1a + splitmix64 finalizer — FNV alone clusters badly on name\0index inputs), keyed by the hash_on source chain (header / cookie / api_key / client_ip; default preserves the existing extraction chain byte-for-byte). A key's ring order doubles as its failover order: a failed target's keys spread to their ring successors, every other key keeps its mapping, and a recovered target takes its keys back.
  • round_robin is now smooth weighted round-robin (the nginx algorithm): exact proportions, interleaved, and equal/absent weights reproduce the old declaration-order cycle exactly.
  • least_busy now scores (1 + in-flight) / weight (the APISIX least_conn formula). failover / least_cost / least_latency accept weight but do not use it (documented in the schema).
  • Removed: the weighted strategy (folded into round_robin) and the sticky flag (superseded by consistent_hash). The retired enum value fails row-level in the lenient loader — a stale row cannot silently change meaning; declarative configs migrate weightedround_robin (weights kept) and weighted+stickyconsistent_hash. The control plane migrates stored rows and re-projects in the companion CP PR.

Breaking change (release notes)

strategy: weighted and routing.sticky are gone. Managed deployments are migrated by the CP automatically; self-hosted declarative configs must be updated as above. Sticky cohort assignments re-shuffle once when moving to consistent_hash (different hash construction).

Tests

  • Unit: WRR exactness/interleaving/state-reset, ring determinism, weight share, minimal-disruption property (dropping a target moves only its own keys), tier partition/order/cap, hash_on resolution.
  • E2E (consistent-hash-routing-e2e, 6 cases): key affinity + spread; cookie→api_key hash_on chain; backup tier idle while active healthy; fully-down active tier spills in-request with affinity inside the backup pool; cooldown recovery hands traffic back; single-member failure redistributes within its tier only.
  • E2E updates: distribution test now asserts exact 70/30 (smooth WRR is periodic); priority live-edit test (tier swap flips traffic); priority-tier test in routing-strategies. Full suite: 205 files / 620 tests green locally.

Fixes api7/AISIX-Cloud#1206

🤖 Generated with Claude Code

…ed into round_robin
Load-balancing taxonomy rework (AISIX-Cloud#1206), aligned with the
APISIX balancer model: weight and priority are per-target attributes
available under every strategy, and consistent hashing is a strategy of
its own rather than a flag on another one.
- targets[].priority (i32, default 0, higher preferred, negatives for
backups): targets partition into tiers; the strategy orders each tier
independently (per-tier balancer state); tiers concatenate, so a lower
tier only receives traffic when every higher-tier target failed or was
health-filtered. max_fallbacks caps the concatenated walk.
- strategy consistent_hash: ketama-style ring (160 points x weight
units, FNV-1a + splitmix64 finalizer), keyed by the hash_on source
chain (header / cookie / api_key / client_ip; default =
x-aisix-routing-key header, then the caller's API key id). A key's
ring order doubles as its failover order, so a failed target's keys
spread to their ring successors and every other key keeps its mapping.
- round_robin is now smooth WEIGHTED round-robin (the nginx algorithm);
equal weights keep the old declaration-order cycle exactly.
- least_busy now scores (1 + in-flight) / weight.
- REMOVED: the weighted strategy (fold into round_robin) and the sticky
flag (superseded by consistent_hash). The enum value now fails
row-level in the lenient loader; declarative configs must migrate
(weighted -> round_robin keeping weights; weighted+sticky ->
consistent_hash). The control plane migrates stored rows in the
companion PR.
Fixesapi7/AISIX-Cloud#1206
@nic-6443
nic-6443 requested a lite review from CopilotAugust 19, 2026 10:31

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in:5 minutes

Limit details: You’ve used all 2 included reviews currently available. Your 58 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

You can run this review on demand instead of waiting.

On-demand reviews are free until September 18, 2026. After that, they cost $0.25 per reviewed file.

  • Run review for free
How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca85c4da-d5d6-42af-b675-5cb68871d8f8

📥 Commits

Reviewing files that changed from the base of the PR and between 86dd01e and 8492a85.

📒 Files selected for processing (21)
  • README.md
  • crates/aisix-admin/src/openapi.rs
  • crates/aisix-core/src/lib.rs
  • crates/aisix-core/src/models/mod.rs
  • crates/aisix-core/src/models/routing.rs
  • crates/aisix-core/tests/model_schema_characterization.rs
  • crates/aisix-proxy/src/chat.rs
  • crates/aisix-proxy/src/client_ip.rs
  • crates/aisix-proxy/src/count_tokens.rs
  • crates/aisix-proxy/src/health.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/responses.rs
  • crates/aisix-proxy/src/routing.rs
  • crates/aisix-proxy/src/semantic.rs
  • schemas/resources/model.schema.json
  • schemas/resources/routing.schema.json
  • tests/e2e/src/cases/canary-routing-e2e.test.ts
  • tests/e2e/src/cases/consistent-hash-routing-e2e.test.ts
  • tests/e2e/src/cases/routing-priority-edit-e2e.test.ts
  • tests/e2e/src/cases/routing-strategies-e2e.test.ts
  • tests/e2e/src/cases/weighted-routing-distribution-e2e.test.ts

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

@jarvis9443
jarvis9443 merged commit 11497e1 into mainAug 19, 2026
13 checks passed
@jarvis9443
jarvis9443 deleted the feat/lb-taxonomy-chash-priority branch August 19, 2026 10:55
membphis added a commit that referenced this pull request Aug 24, 2026
`add_variant_titles` still assigns titles by position, and that is
deliberate — but it leaves one edit uncovered, so cover it where it
matters.
Positional title assignment is safe for the edits that actually happen.
`title_schema_variants` bails on a length mismatch, so adding or removing
a kind makes every title in that family disappear and
`openapi_titles_schema_variants_for_redoc_tabs` fail loudly; replacing a
variant in place edits the same line the title sits beside. The record
bears this out: #799 inserted a guardrail kind and updated the titles
correctly while silently breaking the descriptions (#1037), and #997
swapped `weighted` for `consistent_hash` with the titles staying right.
The descriptions had no such forcing function, which is the whole reason
the two tables diverged.
That leaves a length-preserving reorder as the only silent edit. The two
families that grow now pin their titles by discriminator in tests — the
guardrail kinds already did; the exporter kinds do now. Verified by
reordering two exporter titles: the new assertion names the offending
kind while `openapi_titles_schema_variants_for_redoc_tabs` stays green,
which is exactly the window being closed.
The remaining families are small, stable string enums, plus `Model`,
whose branches are mutual-exclusion shapes carrying no discriminator to
key on at all. Converting those would split `add_variant_titles` into two
mechanisms for a reorder that has never happened, and whose result is
self-contradictory on sight — the tab would read `Lakera Guard` over a
branch whose `kind` says `openai_moderation`, since kinds and
descriptions no longer travel by position.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jarvis9443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin - #997

Merged
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority
Aug 19, 2026
Merged

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin#997
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority

Conversation

@jarvis9443

Copy link
Copy Markdown
Contributor

What

Load-balancing taxonomy rework for model groups (the AISIX-Cloud#1206 requirement), aligned with the APISIX balancer model: weight and priority are per-target attributes available under every strategy, and consistent hashing is a strategy of its own rather than a flag on another one.

The driving scenario: two pools of direct models A & B — all traffic on A while any A member is healthy, spill to B only when the whole A pool is down, consistent-hash distribution inside both pools:

routing:
strategy: consistent_hashhash_on: # optional; default = [header x-aisix-routing-key, api_key]
- { type: header, name: x-session-id }targets:
- { model: vllm-a1 } # priority 0 = active pool
- { model: vllm-a2 }
- { model: vllm-a3 }
- { model: vllm-b1, priority: -1 } # backup pool (APISIX-style: higher value preferred)
- { model: vllm-b2, priority: -1 }

Changes

  • targets[].priority (i32, default 0, higher preferred, negatives for backups): targets partition into tiers; the strategy orders each tier independently — per-tier balancer state, mirroring APISIX's per-priority pickers; tiers concatenate, so a lower tier only receives traffic when every higher-tier target failed or was health/cooldown-filtered. max_fallbacks caps the concatenated walk, and the first request that discovers a fully-dead active tier still succeeds (in-request tier spill).
  • strategy: consistent_hash: ketama-style ring (160 points × weight units, capped; FNV-1a + splitmix64 finalizer — FNV alone clusters badly on name\0index inputs), keyed by the hash_on source chain (header / cookie / api_key / client_ip; default preserves the existing extraction chain byte-for-byte). A key's ring order doubles as its failover order: a failed target's keys spread to their ring successors, every other key keeps its mapping, and a recovered target takes its keys back.
  • round_robin is now smooth weighted round-robin (the nginx algorithm): exact proportions, interleaved, and equal/absent weights reproduce the old declaration-order cycle exactly.
  • least_busy now scores (1 + in-flight) / weight (the APISIX least_conn formula). failover / least_cost / least_latency accept weight but do not use it (documented in the schema).
  • Removed: the weighted strategy (folded into round_robin) and the sticky flag (superseded by consistent_hash). The retired enum value fails row-level in the lenient loader — a stale row cannot silently change meaning; declarative configs migrate weightedround_robin (weights kept) and weighted+stickyconsistent_hash. The control plane migrates stored rows and re-projects in the companion CP PR.

Breaking change (release notes)

strategy: weighted and routing.sticky are gone. Managed deployments are migrated by the CP automatically; self-hosted declarative configs must be updated as above. Sticky cohort assignments re-shuffle once when moving to consistent_hash (different hash construction).

Tests

  • Unit: WRR exactness/interleaving/state-reset, ring determinism, weight share, minimal-disruption property (dropping a target moves only its own keys), tier partition/order/cap, hash_on resolution.
  • E2E (consistent-hash-routing-e2e, 6 cases): key affinity + spread; cookie→api_key hash_on chain; backup tier idle while active healthy; fully-down active tier spills in-request with affinity inside the backup pool; cooldown recovery hands traffic back; single-member failure redistributes within its tier only.
  • E2E updates: distribution test now asserts exact 70/30 (smooth WRR is periodic); priority live-edit test (tier swap flips traffic); priority-tier test in routing-strategies. Full suite: 205 files / 620 tests green locally.

Fixes api7/AISIX-Cloud#1206

🤖 Generated with Claude Code

…ed into round_robin
Load-balancing taxonomy rework (AISIX-Cloud#1206), aligned with the
APISIX balancer model: weight and priority are per-target attributes
available under every strategy, and consistent hashing is a strategy of
its own rather than a flag on another one.
- targets[].priority (i32, default 0, higher preferred, negatives for
backups): targets partition into tiers; the strategy orders each tier
independently (per-tier balancer state); tiers concatenate, so a lower
tier only receives traffic when every higher-tier target failed or was
health-filtered. max_fallbacks caps the concatenated walk.
- strategy consistent_hash: ketama-style ring (160 points x weight
units, FNV-1a + splitmix64 finalizer), keyed by the hash_on source
chain (header / cookie / api_key / client_ip; default =
x-aisix-routing-key header, then the caller's API key id). A key's
ring order doubles as its failover order, so a failed target's keys
spread to their ring successors and every other key keeps its mapping.
- round_robin is now smooth WEIGHTED round-robin (the nginx algorithm);
equal weights keep the old declaration-order cycle exactly.
- least_busy now scores (1 + in-flight) / weight.
- REMOVED: the weighted strategy (fold into round_robin) and the sticky
flag (superseded by consistent_hash). The enum value now fails
row-level in the lenient loader; declarative configs must migrate
(weighted -> round_robin keeping weights; weighted+sticky ->
consistent_hash). The control plane migrates stored rows in the
companion PR.
Fixesapi7/AISIX-Cloud#1206
@nic-6443
nic-6443 requested a lite review from CopilotAugust 19, 2026 10:31

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in:5 minutes

Limit details: You’ve used all 2 included reviews currently available. Your 58 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

You can run this review on demand instead of waiting.

On-demand reviews are free until September 18, 2026. After that, they cost $0.25 per reviewed file.

  • Run review for free
How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca85c4da-d5d6-42af-b675-5cb68871d8f8

📥 Commits

Reviewing files that changed from the base of the PR and between 86dd01e and 8492a85.

📒 Files selected for processing (21)
  • README.md
  • crates/aisix-admin/src/openapi.rs
  • crates/aisix-core/src/lib.rs
  • crates/aisix-core/src/models/mod.rs
  • crates/aisix-core/src/models/routing.rs
  • crates/aisix-core/tests/model_schema_characterization.rs
  • crates/aisix-proxy/src/chat.rs
  • crates/aisix-proxy/src/client_ip.rs
  • crates/aisix-proxy/src/count_tokens.rs
  • crates/aisix-proxy/src/health.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/responses.rs
  • crates/aisix-proxy/src/routing.rs
  • crates/aisix-proxy/src/semantic.rs
  • schemas/resources/model.schema.json
  • schemas/resources/routing.schema.json
  • tests/e2e/src/cases/canary-routing-e2e.test.ts
  • tests/e2e/src/cases/consistent-hash-routing-e2e.test.ts
  • tests/e2e/src/cases/routing-priority-edit-e2e.test.ts
  • tests/e2e/src/cases/routing-strategies-e2e.test.ts
  • tests/e2e/src/cases/weighted-routing-distribution-e2e.test.ts

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

@jarvis9443
jarvis9443 merged commit 11497e1 into mainAug 19, 2026
13 checks passed
@jarvis9443
jarvis9443 deleted the feat/lb-taxonomy-chash-priority branch August 19, 2026 10:55
membphis added a commit that referenced this pull request Aug 24, 2026
`add_variant_titles` still assigns titles by position, and that is
deliberate — but it leaves one edit uncovered, so cover it where it
matters.
Positional title assignment is safe for the edits that actually happen.
`title_schema_variants` bails on a length mismatch, so adding or removing
a kind makes every title in that family disappear and
`openapi_titles_schema_variants_for_redoc_tabs` fail loudly; replacing a
variant in place edits the same line the title sits beside. The record
bears this out: #799 inserted a guardrail kind and updated the titles
correctly while silently breaking the descriptions (#1037), and #997
swapped `weighted` for `consistent_hash` with the titles staying right.
The descriptions had no such forcing function, which is the whole reason
the two tables diverged.
That leaves a length-preserving reorder as the only silent edit. The two
families that grow now pin their titles by discriminator in tests — the
guardrail kinds already did; the exporter kinds do now. Verified by
reordering two exporter titles: the new assertion names the offending
kind while `openapi_titles_schema_variants_for_redoc_tabs` stays green,
which is exactly the window being closed.
The remaining families are small, stable string enums, plus `Model`,
whose branches are mutual-exclusion shapes carrying no discriminator to
key on at all. Converting those would split `add_variant_titles` into two
mechanisms for a reorder that has never happened, and whose result is
self-contradictory on sight — the tab would read `Lakera Guard` over a
branch whose `kind` says `openai_moderation`, since kinds and
descriptions no longer travel by position.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jarvis9443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin - #997

Merged
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority
Aug 19, 2026
Merged

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin#997
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority

Conversation

@jarvis9443

Copy link
Copy Markdown
Contributor

What

Load-balancing taxonomy rework for model groups (the AISIX-Cloud#1206 requirement), aligned with the APISIX balancer model: weight and priority are per-target attributes available under every strategy, and consistent hashing is a strategy of its own rather than a flag on another one.

The driving scenario: two pools of direct models A & B — all traffic on A while any A member is healthy, spill to B only when the whole A pool is down, consistent-hash distribution inside both pools:

routing:
strategy: consistent_hashhash_on: # optional; default = [header x-aisix-routing-key, api_key]
- { type: header, name: x-session-id }targets:
- { model: vllm-a1 } # priority 0 = active pool
- { model: vllm-a2 }
- { model: vllm-a3 }
- { model: vllm-b1, priority: -1 } # backup pool (APISIX-style: higher value preferred)
- { model: vllm-b2, priority: -1 }

Changes

  • targets[].priority (i32, default 0, higher preferred, negatives for backups): targets partition into tiers; the strategy orders each tier independently — per-tier balancer state, mirroring APISIX's per-priority pickers; tiers concatenate, so a lower tier only receives traffic when every higher-tier target failed or was health/cooldown-filtered. max_fallbacks caps the concatenated walk, and the first request that discovers a fully-dead active tier still succeeds (in-request tier spill).
  • strategy: consistent_hash: ketama-style ring (160 points × weight units, capped; FNV-1a + splitmix64 finalizer — FNV alone clusters badly on name\0index inputs), keyed by the hash_on source chain (header / cookie / api_key / client_ip; default preserves the existing extraction chain byte-for-byte). A key's ring order doubles as its failover order: a failed target's keys spread to their ring successors, every other key keeps its mapping, and a recovered target takes its keys back.
  • round_robin is now smooth weighted round-robin (the nginx algorithm): exact proportions, interleaved, and equal/absent weights reproduce the old declaration-order cycle exactly.
  • least_busy now scores (1 + in-flight) / weight (the APISIX least_conn formula). failover / least_cost / least_latency accept weight but do not use it (documented in the schema).
  • Removed: the weighted strategy (folded into round_robin) and the sticky flag (superseded by consistent_hash). The retired enum value fails row-level in the lenient loader — a stale row cannot silently change meaning; declarative configs migrate weightedround_robin (weights kept) and weighted+stickyconsistent_hash. The control plane migrates stored rows and re-projects in the companion CP PR.

Breaking change (release notes)

strategy: weighted and routing.sticky are gone. Managed deployments are migrated by the CP automatically; self-hosted declarative configs must be updated as above. Sticky cohort assignments re-shuffle once when moving to consistent_hash (different hash construction).

Tests

  • Unit: WRR exactness/interleaving/state-reset, ring determinism, weight share, minimal-disruption property (dropping a target moves only its own keys), tier partition/order/cap, hash_on resolution.
  • E2E (consistent-hash-routing-e2e, 6 cases): key affinity + spread; cookie→api_key hash_on chain; backup tier idle while active healthy; fully-down active tier spills in-request with affinity inside the backup pool; cooldown recovery hands traffic back; single-member failure redistributes within its tier only.
  • E2E updates: distribution test now asserts exact 70/30 (smooth WRR is periodic); priority live-edit test (tier swap flips traffic); priority-tier test in routing-strategies. Full suite: 205 files / 620 tests green locally.

Fixes api7/AISIX-Cloud#1206

🤖 Generated with Claude Code

…ed into round_robin
Load-balancing taxonomy rework (AISIX-Cloud#1206), aligned with the
APISIX balancer model: weight and priority are per-target attributes
available under every strategy, and consistent hashing is a strategy of
its own rather than a flag on another one.
- targets[].priority (i32, default 0, higher preferred, negatives for
backups): targets partition into tiers; the strategy orders each tier
independently (per-tier balancer state); tiers concatenate, so a lower
tier only receives traffic when every higher-tier target failed or was
health-filtered. max_fallbacks caps the concatenated walk.
- strategy consistent_hash: ketama-style ring (160 points x weight
units, FNV-1a + splitmix64 finalizer), keyed by the hash_on source
chain (header / cookie / api_key / client_ip; default =
x-aisix-routing-key header, then the caller's API key id). A key's
ring order doubles as its failover order, so a failed target's keys
spread to their ring successors and every other key keeps its mapping.
- round_robin is now smooth WEIGHTED round-robin (the nginx algorithm);
equal weights keep the old declaration-order cycle exactly.
- least_busy now scores (1 + in-flight) / weight.
- REMOVED: the weighted strategy (fold into round_robin) and the sticky
flag (superseded by consistent_hash). The enum value now fails
row-level in the lenient loader; declarative configs must migrate
(weighted -> round_robin keeping weights; weighted+sticky ->
consistent_hash). The control plane migrates stored rows in the
companion PR.
Fixesapi7/AISIX-Cloud#1206
@nic-6443
nic-6443 requested a lite review from CopilotAugust 19, 2026 10:31

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in:5 minutes

Limit details: You’ve used all 2 included reviews currently available. Your 58 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

You can run this review on demand instead of waiting.

On-demand reviews are free until September 18, 2026. After that, they cost $0.25 per reviewed file.

  • Run review for free
How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca85c4da-d5d6-42af-b675-5cb68871d8f8

📥 Commits

Reviewing files that changed from the base of the PR and between 86dd01e and 8492a85.

📒 Files selected for processing (21)
  • README.md
  • crates/aisix-admin/src/openapi.rs
  • crates/aisix-core/src/lib.rs
  • crates/aisix-core/src/models/mod.rs
  • crates/aisix-core/src/models/routing.rs
  • crates/aisix-core/tests/model_schema_characterization.rs
  • crates/aisix-proxy/src/chat.rs
  • crates/aisix-proxy/src/client_ip.rs
  • crates/aisix-proxy/src/count_tokens.rs
  • crates/aisix-proxy/src/health.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/responses.rs
  • crates/aisix-proxy/src/routing.rs
  • crates/aisix-proxy/src/semantic.rs
  • schemas/resources/model.schema.json
  • schemas/resources/routing.schema.json
  • tests/e2e/src/cases/canary-routing-e2e.test.ts
  • tests/e2e/src/cases/consistent-hash-routing-e2e.test.ts
  • tests/e2e/src/cases/routing-priority-edit-e2e.test.ts
  • tests/e2e/src/cases/routing-strategies-e2e.test.ts
  • tests/e2e/src/cases/weighted-routing-distribution-e2e.test.ts

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

@jarvis9443
jarvis9443 merged commit 11497e1 into mainAug 19, 2026
13 checks passed
@jarvis9443
jarvis9443 deleted the feat/lb-taxonomy-chash-priority branch August 19, 2026 10:55
membphis added a commit that referenced this pull request Aug 24, 2026
`add_variant_titles` still assigns titles by position, and that is
deliberate — but it leaves one edit uncovered, so cover it where it
matters.
Positional title assignment is safe for the edits that actually happen.
`title_schema_variants` bails on a length mismatch, so adding or removing
a kind makes every title in that family disappear and
`openapi_titles_schema_variants_for_redoc_tabs` fail loudly; replacing a
variant in place edits the same line the title sits beside. The record
bears this out: #799 inserted a guardrail kind and updated the titles
correctly while silently breaking the descriptions (#1037), and #997
swapped `weighted` for `consistent_hash` with the titles staying right.
The descriptions had no such forcing function, which is the whole reason
the two tables diverged.
That leaves a length-preserving reorder as the only silent edit. The two
families that grow now pin their titles by discriminator in tests — the
guardrail kinds already did; the exporter kinds do now. Verified by
reordering two exporter titles: the new assertion names the offending
kind while `openapi_titles_schema_variants_for_redoc_tabs` stays green,
which is exactly the window being closed.
The remaining families are small, stable string enums, plus `Model`,
whose branches are mutual-exclusion shapes carrying no discriminator to
key on at all. Converting those would split `add_variant_titles` into two
mechanisms for a reorder that has never happened, and whose result is
self-contradictory on sight — the tab would read `Lakera Guard` over a
branch whose `kind` says `openai_moderation`, since kinds and
descriptions no longer travel by position.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jarvis9443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin - #997

Merged
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority
Aug 19, 2026
Merged

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin#997
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority

Conversation

@jarvis9443

Copy link
Copy Markdown
Contributor

What

Load-balancing taxonomy rework for model groups (the AISIX-Cloud#1206 requirement), aligned with the APISIX balancer model: weight and priority are per-target attributes available under every strategy, and consistent hashing is a strategy of its own rather than a flag on another one.

The driving scenario: two pools of direct models A & B — all traffic on A while any A member is healthy, spill to B only when the whole A pool is down, consistent-hash distribution inside both pools:

routing:
strategy: consistent_hashhash_on: # optional; default = [header x-aisix-routing-key, api_key]
- { type: header, name: x-session-id }targets:
- { model: vllm-a1 } # priority 0 = active pool
- { model: vllm-a2 }
- { model: vllm-a3 }
- { model: vllm-b1, priority: -1 } # backup pool (APISIX-style: higher value preferred)
- { model: vllm-b2, priority: -1 }

Changes

  • targets[].priority (i32, default 0, higher preferred, negatives for backups): targets partition into tiers; the strategy orders each tier independently — per-tier balancer state, mirroring APISIX's per-priority pickers; tiers concatenate, so a lower tier only receives traffic when every higher-tier target failed or was health/cooldown-filtered. max_fallbacks caps the concatenated walk, and the first request that discovers a fully-dead active tier still succeeds (in-request tier spill).
  • strategy: consistent_hash: ketama-style ring (160 points × weight units, capped; FNV-1a + splitmix64 finalizer — FNV alone clusters badly on name\0index inputs), keyed by the hash_on source chain (header / cookie / api_key / client_ip; default preserves the existing extraction chain byte-for-byte). A key's ring order doubles as its failover order: a failed target's keys spread to their ring successors, every other key keeps its mapping, and a recovered target takes its keys back.
  • round_robin is now smooth weighted round-robin (the nginx algorithm): exact proportions, interleaved, and equal/absent weights reproduce the old declaration-order cycle exactly.
  • least_busy now scores (1 + in-flight) / weight (the APISIX least_conn formula). failover / least_cost / least_latency accept weight but do not use it (documented in the schema).
  • Removed: the weighted strategy (folded into round_robin) and the sticky flag (superseded by consistent_hash). The retired enum value fails row-level in the lenient loader — a stale row cannot silently change meaning; declarative configs migrate weightedround_robin (weights kept) and weighted+stickyconsistent_hash. The control plane migrates stored rows and re-projects in the companion CP PR.

Breaking change (release notes)

strategy: weighted and routing.sticky are gone. Managed deployments are migrated by the CP automatically; self-hosted declarative configs must be updated as above. Sticky cohort assignments re-shuffle once when moving to consistent_hash (different hash construction).

Tests

  • Unit: WRR exactness/interleaving/state-reset, ring determinism, weight share, minimal-disruption property (dropping a target moves only its own keys), tier partition/order/cap, hash_on resolution.
  • E2E (consistent-hash-routing-e2e, 6 cases): key affinity + spread; cookie→api_key hash_on chain; backup tier idle while active healthy; fully-down active tier spills in-request with affinity inside the backup pool; cooldown recovery hands traffic back; single-member failure redistributes within its tier only.
  • E2E updates: distribution test now asserts exact 70/30 (smooth WRR is periodic); priority live-edit test (tier swap flips traffic); priority-tier test in routing-strategies. Full suite: 205 files / 620 tests green locally.

Fixes api7/AISIX-Cloud#1206

🤖 Generated with Claude Code

…ed into round_robin
Load-balancing taxonomy rework (AISIX-Cloud#1206), aligned with the
APISIX balancer model: weight and priority are per-target attributes
available under every strategy, and consistent hashing is a strategy of
its own rather than a flag on another one.
- targets[].priority (i32, default 0, higher preferred, negatives for
backups): targets partition into tiers; the strategy orders each tier
independently (per-tier balancer state); tiers concatenate, so a lower
tier only receives traffic when every higher-tier target failed or was
health-filtered. max_fallbacks caps the concatenated walk.
- strategy consistent_hash: ketama-style ring (160 points x weight
units, FNV-1a + splitmix64 finalizer), keyed by the hash_on source
chain (header / cookie / api_key / client_ip; default =
x-aisix-routing-key header, then the caller's API key id). A key's
ring order doubles as its failover order, so a failed target's keys
spread to their ring successors and every other key keeps its mapping.
- round_robin is now smooth WEIGHTED round-robin (the nginx algorithm);
equal weights keep the old declaration-order cycle exactly.
- least_busy now scores (1 + in-flight) / weight.
- REMOVED: the weighted strategy (fold into round_robin) and the sticky
flag (superseded by consistent_hash). The enum value now fails
row-level in the lenient loader; declarative configs must migrate
(weighted -> round_robin keeping weights; weighted+sticky ->
consistent_hash). The control plane migrates stored rows in the
companion PR.
Fixesapi7/AISIX-Cloud#1206
@nic-6443
nic-6443 requested a lite review from CopilotAugust 19, 2026 10:31

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in:5 minutes

Limit details: You’ve used all 2 included reviews currently available. Your 58 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

You can run this review on demand instead of waiting.

On-demand reviews are free until September 18, 2026. After that, they cost $0.25 per reviewed file.

  • Run review for free
How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca85c4da-d5d6-42af-b675-5cb68871d8f8

📥 Commits

Reviewing files that changed from the base of the PR and between 86dd01e and 8492a85.

📒 Files selected for processing (21)
  • README.md
  • crates/aisix-admin/src/openapi.rs
  • crates/aisix-core/src/lib.rs
  • crates/aisix-core/src/models/mod.rs
  • crates/aisix-core/src/models/routing.rs
  • crates/aisix-core/tests/model_schema_characterization.rs
  • crates/aisix-proxy/src/chat.rs
  • crates/aisix-proxy/src/client_ip.rs
  • crates/aisix-proxy/src/count_tokens.rs
  • crates/aisix-proxy/src/health.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/responses.rs
  • crates/aisix-proxy/src/routing.rs
  • crates/aisix-proxy/src/semantic.rs
  • schemas/resources/model.schema.json
  • schemas/resources/routing.schema.json
  • tests/e2e/src/cases/canary-routing-e2e.test.ts
  • tests/e2e/src/cases/consistent-hash-routing-e2e.test.ts
  • tests/e2e/src/cases/routing-priority-edit-e2e.test.ts
  • tests/e2e/src/cases/routing-strategies-e2e.test.ts
  • tests/e2e/src/cases/weighted-routing-distribution-e2e.test.ts

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

@jarvis9443
jarvis9443 merged commit 11497e1 into mainAug 19, 2026
13 checks passed
@jarvis9443
jarvis9443 deleted the feat/lb-taxonomy-chash-priority branch August 19, 2026 10:55
membphis added a commit that referenced this pull request Aug 24, 2026
`add_variant_titles` still assigns titles by position, and that is
deliberate — but it leaves one edit uncovered, so cover it where it
matters.
Positional title assignment is safe for the edits that actually happen.
`title_schema_variants` bails on a length mismatch, so adding or removing
a kind makes every title in that family disappear and
`openapi_titles_schema_variants_for_redoc_tabs` fail loudly; replacing a
variant in place edits the same line the title sits beside. The record
bears this out: #799 inserted a guardrail kind and updated the titles
correctly while silently breaking the descriptions (#1037), and #997
swapped `weighted` for `consistent_hash` with the titles staying right.
The descriptions had no such forcing function, which is the whole reason
the two tables diverged.
That leaves a length-preserving reorder as the only silent edit. The two
families that grow now pin their titles by discriminator in tests — the
guardrail kinds already did; the exporter kinds do now. Verified by
reordering two exporter titles: the new assertion names the offending
kind while `openapi_titles_schema_variants_for_redoc_tabs` stays green,
which is exactly the window being closed.
The remaining families are small, stable string enums, plus `Model`,
whose branches are mutual-exclusion shapes carrying no discriminator to
key on at all. Converting those would split `add_variant_titles` into two
mechanisms for a reorder that has never happened, and whose result is
self-contradictory on sight — the tab would read `Lakera Guard` over a
branch whose `kind` says `openai_moderation`, since kinds and
descriptions no longer travel by position.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jarvis9443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin - #997

Merged
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority
Aug 19, 2026
Merged

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin#997
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority

Conversation

@jarvis9443

Copy link
Copy Markdown
Contributor

What

Load-balancing taxonomy rework for model groups (the AISIX-Cloud#1206 requirement), aligned with the APISIX balancer model: weight and priority are per-target attributes available under every strategy, and consistent hashing is a strategy of its own rather than a flag on another one.

The driving scenario: two pools of direct models A & B — all traffic on A while any A member is healthy, spill to B only when the whole A pool is down, consistent-hash distribution inside both pools:

routing:
strategy: consistent_hashhash_on: # optional; default = [header x-aisix-routing-key, api_key]
- { type: header, name: x-session-id }targets:
- { model: vllm-a1 } # priority 0 = active pool
- { model: vllm-a2 }
- { model: vllm-a3 }
- { model: vllm-b1, priority: -1 } # backup pool (APISIX-style: higher value preferred)
- { model: vllm-b2, priority: -1 }

Changes

  • targets[].priority (i32, default 0, higher preferred, negatives for backups): targets partition into tiers; the strategy orders each tier independently — per-tier balancer state, mirroring APISIX's per-priority pickers; tiers concatenate, so a lower tier only receives traffic when every higher-tier target failed or was health/cooldown-filtered. max_fallbacks caps the concatenated walk, and the first request that discovers a fully-dead active tier still succeeds (in-request tier spill).
  • strategy: consistent_hash: ketama-style ring (160 points × weight units, capped; FNV-1a + splitmix64 finalizer — FNV alone clusters badly on name\0index inputs), keyed by the hash_on source chain (header / cookie / api_key / client_ip; default preserves the existing extraction chain byte-for-byte). A key's ring order doubles as its failover order: a failed target's keys spread to their ring successors, every other key keeps its mapping, and a recovered target takes its keys back.
  • round_robin is now smooth weighted round-robin (the nginx algorithm): exact proportions, interleaved, and equal/absent weights reproduce the old declaration-order cycle exactly.
  • least_busy now scores (1 + in-flight) / weight (the APISIX least_conn formula). failover / least_cost / least_latency accept weight but do not use it (documented in the schema).
  • Removed: the weighted strategy (folded into round_robin) and the sticky flag (superseded by consistent_hash). The retired enum value fails row-level in the lenient loader — a stale row cannot silently change meaning; declarative configs migrate weightedround_robin (weights kept) and weighted+stickyconsistent_hash. The control plane migrates stored rows and re-projects in the companion CP PR.

Breaking change (release notes)

strategy: weighted and routing.sticky are gone. Managed deployments are migrated by the CP automatically; self-hosted declarative configs must be updated as above. Sticky cohort assignments re-shuffle once when moving to consistent_hash (different hash construction).

Tests

  • Unit: WRR exactness/interleaving/state-reset, ring determinism, weight share, minimal-disruption property (dropping a target moves only its own keys), tier partition/order/cap, hash_on resolution.
  • E2E (consistent-hash-routing-e2e, 6 cases): key affinity + spread; cookie→api_key hash_on chain; backup tier idle while active healthy; fully-down active tier spills in-request with affinity inside the backup pool; cooldown recovery hands traffic back; single-member failure redistributes within its tier only.
  • E2E updates: distribution test now asserts exact 70/30 (smooth WRR is periodic); priority live-edit test (tier swap flips traffic); priority-tier test in routing-strategies. Full suite: 205 files / 620 tests green locally.

Fixes api7/AISIX-Cloud#1206

🤖 Generated with Claude Code

…ed into round_robin
Load-balancing taxonomy rework (AISIX-Cloud#1206), aligned with the
APISIX balancer model: weight and priority are per-target attributes
available under every strategy, and consistent hashing is a strategy of
its own rather than a flag on another one.
- targets[].priority (i32, default 0, higher preferred, negatives for
backups): targets partition into tiers; the strategy orders each tier
independently (per-tier balancer state); tiers concatenate, so a lower
tier only receives traffic when every higher-tier target failed or was
health-filtered. max_fallbacks caps the concatenated walk.
- strategy consistent_hash: ketama-style ring (160 points x weight
units, FNV-1a + splitmix64 finalizer), keyed by the hash_on source
chain (header / cookie / api_key / client_ip; default =
x-aisix-routing-key header, then the caller's API key id). A key's
ring order doubles as its failover order, so a failed target's keys
spread to their ring successors and every other key keeps its mapping.
- round_robin is now smooth WEIGHTED round-robin (the nginx algorithm);
equal weights keep the old declaration-order cycle exactly.
- least_busy now scores (1 + in-flight) / weight.
- REMOVED: the weighted strategy (fold into round_robin) and the sticky
flag (superseded by consistent_hash). The enum value now fails
row-level in the lenient loader; declarative configs must migrate
(weighted -> round_robin keeping weights; weighted+sticky ->
consistent_hash). The control plane migrates stored rows in the
companion PR.
Fixesapi7/AISIX-Cloud#1206
@nic-6443
nic-6443 requested a lite review from CopilotAugust 19, 2026 10:31

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in:5 minutes

Limit details: You’ve used all 2 included reviews currently available. Your 58 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

You can run this review on demand instead of waiting.

On-demand reviews are free until September 18, 2026. After that, they cost $0.25 per reviewed file.

  • Run review for free
How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca85c4da-d5d6-42af-b675-5cb68871d8f8

📥 Commits

Reviewing files that changed from the base of the PR and between 86dd01e and 8492a85.

📒 Files selected for processing (21)
  • README.md
  • crates/aisix-admin/src/openapi.rs
  • crates/aisix-core/src/lib.rs
  • crates/aisix-core/src/models/mod.rs
  • crates/aisix-core/src/models/routing.rs
  • crates/aisix-core/tests/model_schema_characterization.rs
  • crates/aisix-proxy/src/chat.rs
  • crates/aisix-proxy/src/client_ip.rs
  • crates/aisix-proxy/src/count_tokens.rs
  • crates/aisix-proxy/src/health.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/responses.rs
  • crates/aisix-proxy/src/routing.rs
  • crates/aisix-proxy/src/semantic.rs
  • schemas/resources/model.schema.json
  • schemas/resources/routing.schema.json
  • tests/e2e/src/cases/canary-routing-e2e.test.ts
  • tests/e2e/src/cases/consistent-hash-routing-e2e.test.ts
  • tests/e2e/src/cases/routing-priority-edit-e2e.test.ts
  • tests/e2e/src/cases/routing-strategies-e2e.test.ts
  • tests/e2e/src/cases/weighted-routing-distribution-e2e.test.ts

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

@jarvis9443
jarvis9443 merged commit 11497e1 into mainAug 19, 2026
13 checks passed
@jarvis9443
jarvis9443 deleted the feat/lb-taxonomy-chash-priority branch August 19, 2026 10:55
membphis added a commit that referenced this pull request Aug 24, 2026
`add_variant_titles` still assigns titles by position, and that is
deliberate — but it leaves one edit uncovered, so cover it where it
matters.
Positional title assignment is safe for the edits that actually happen.
`title_schema_variants` bails on a length mismatch, so adding or removing
a kind makes every title in that family disappear and
`openapi_titles_schema_variants_for_redoc_tabs` fail loudly; replacing a
variant in place edits the same line the title sits beside. The record
bears this out: #799 inserted a guardrail kind and updated the titles
correctly while silently breaking the descriptions (#1037), and #997
swapped `weighted` for `consistent_hash` with the titles staying right.
The descriptions had no such forcing function, which is the whole reason
the two tables diverged.
That leaves a length-preserving reorder as the only silent edit. The two
families that grow now pin their titles by discriminator in tests — the
guardrail kinds already did; the exporter kinds do now. Verified by
reordering two exporter titles: the new assertion names the offending
kind while `openapi_titles_schema_variants_for_redoc_tabs` stays green,
which is exactly the window being closed.
The remaining families are small, stable string enums, plus `Model`,
whose branches are mutual-exclusion shapes carrying no discriminator to
key on at all. Converting those would split `add_variant_titles` into two
mechanisms for a reorder that has never happened, and whose result is
self-contradictory on sight — the tab would read `Lakera Guard` over a
branch whose `kind` says `openai_moderation`, since kinds and
descriptions no longer travel by position.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jarvis9443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin - #997

Merged
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority
Aug 19, 2026
Merged

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin#997
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority

Conversation

@jarvis9443

Copy link
Copy Markdown
Contributor

What

Load-balancing taxonomy rework for model groups (the AISIX-Cloud#1206 requirement), aligned with the APISIX balancer model: weight and priority are per-target attributes available under every strategy, and consistent hashing is a strategy of its own rather than a flag on another one.

The driving scenario: two pools of direct models A & B — all traffic on A while any A member is healthy, spill to B only when the whole A pool is down, consistent-hash distribution inside both pools:

routing:
strategy: consistent_hashhash_on: # optional; default = [header x-aisix-routing-key, api_key]
- { type: header, name: x-session-id }targets:
- { model: vllm-a1 } # priority 0 = active pool
- { model: vllm-a2 }
- { model: vllm-a3 }
- { model: vllm-b1, priority: -1 } # backup pool (APISIX-style: higher value preferred)
- { model: vllm-b2, priority: -1 }

Changes

  • targets[].priority (i32, default 0, higher preferred, negatives for backups): targets partition into tiers; the strategy orders each tier independently — per-tier balancer state, mirroring APISIX's per-priority pickers; tiers concatenate, so a lower tier only receives traffic when every higher-tier target failed or was health/cooldown-filtered. max_fallbacks caps the concatenated walk, and the first request that discovers a fully-dead active tier still succeeds (in-request tier spill).
  • strategy: consistent_hash: ketama-style ring (160 points × weight units, capped; FNV-1a + splitmix64 finalizer — FNV alone clusters badly on name\0index inputs), keyed by the hash_on source chain (header / cookie / api_key / client_ip; default preserves the existing extraction chain byte-for-byte). A key's ring order doubles as its failover order: a failed target's keys spread to their ring successors, every other key keeps its mapping, and a recovered target takes its keys back.
  • round_robin is now smooth weighted round-robin (the nginx algorithm): exact proportions, interleaved, and equal/absent weights reproduce the old declaration-order cycle exactly.
  • least_busy now scores (1 + in-flight) / weight (the APISIX least_conn formula). failover / least_cost / least_latency accept weight but do not use it (documented in the schema).
  • Removed: the weighted strategy (folded into round_robin) and the sticky flag (superseded by consistent_hash). The retired enum value fails row-level in the lenient loader — a stale row cannot silently change meaning; declarative configs migrate weightedround_robin (weights kept) and weighted+stickyconsistent_hash. The control plane migrates stored rows and re-projects in the companion CP PR.

Breaking change (release notes)

strategy: weighted and routing.sticky are gone. Managed deployments are migrated by the CP automatically; self-hosted declarative configs must be updated as above. Sticky cohort assignments re-shuffle once when moving to consistent_hash (different hash construction).

Tests

  • Unit: WRR exactness/interleaving/state-reset, ring determinism, weight share, minimal-disruption property (dropping a target moves only its own keys), tier partition/order/cap, hash_on resolution.
  • E2E (consistent-hash-routing-e2e, 6 cases): key affinity + spread; cookie→api_key hash_on chain; backup tier idle while active healthy; fully-down active tier spills in-request with affinity inside the backup pool; cooldown recovery hands traffic back; single-member failure redistributes within its tier only.
  • E2E updates: distribution test now asserts exact 70/30 (smooth WRR is periodic); priority live-edit test (tier swap flips traffic); priority-tier test in routing-strategies. Full suite: 205 files / 620 tests green locally.

Fixes api7/AISIX-Cloud#1206

🤖 Generated with Claude Code

…ed into round_robin
Load-balancing taxonomy rework (AISIX-Cloud#1206), aligned with the
APISIX balancer model: weight and priority are per-target attributes
available under every strategy, and consistent hashing is a strategy of
its own rather than a flag on another one.
- targets[].priority (i32, default 0, higher preferred, negatives for
backups): targets partition into tiers; the strategy orders each tier
independently (per-tier balancer state); tiers concatenate, so a lower
tier only receives traffic when every higher-tier target failed or was
health-filtered. max_fallbacks caps the concatenated walk.
- strategy consistent_hash: ketama-style ring (160 points x weight
units, FNV-1a + splitmix64 finalizer), keyed by the hash_on source
chain (header / cookie / api_key / client_ip; default =
x-aisix-routing-key header, then the caller's API key id). A key's
ring order doubles as its failover order, so a failed target's keys
spread to their ring successors and every other key keeps its mapping.
- round_robin is now smooth WEIGHTED round-robin (the nginx algorithm);
equal weights keep the old declaration-order cycle exactly.
- least_busy now scores (1 + in-flight) / weight.
- REMOVED: the weighted strategy (fold into round_robin) and the sticky
flag (superseded by consistent_hash). The enum value now fails
row-level in the lenient loader; declarative configs must migrate
(weighted -> round_robin keeping weights; weighted+sticky ->
consistent_hash). The control plane migrates stored rows in the
companion PR.
Fixesapi7/AISIX-Cloud#1206
@nic-6443
nic-6443 requested a lite review from CopilotAugust 19, 2026 10:31

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in:5 minutes

Limit details: You’ve used all 2 included reviews currently available. Your 58 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

You can run this review on demand instead of waiting.

On-demand reviews are free until September 18, 2026. After that, they cost $0.25 per reviewed file.

  • Run review for free
How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca85c4da-d5d6-42af-b675-5cb68871d8f8

📥 Commits

Reviewing files that changed from the base of the PR and between 86dd01e and 8492a85.

📒 Files selected for processing (21)
  • README.md
  • crates/aisix-admin/src/openapi.rs
  • crates/aisix-core/src/lib.rs
  • crates/aisix-core/src/models/mod.rs
  • crates/aisix-core/src/models/routing.rs
  • crates/aisix-core/tests/model_schema_characterization.rs
  • crates/aisix-proxy/src/chat.rs
  • crates/aisix-proxy/src/client_ip.rs
  • crates/aisix-proxy/src/count_tokens.rs
  • crates/aisix-proxy/src/health.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/responses.rs
  • crates/aisix-proxy/src/routing.rs
  • crates/aisix-proxy/src/semantic.rs
  • schemas/resources/model.schema.json
  • schemas/resources/routing.schema.json
  • tests/e2e/src/cases/canary-routing-e2e.test.ts
  • tests/e2e/src/cases/consistent-hash-routing-e2e.test.ts
  • tests/e2e/src/cases/routing-priority-edit-e2e.test.ts
  • tests/e2e/src/cases/routing-strategies-e2e.test.ts
  • tests/e2e/src/cases/weighted-routing-distribution-e2e.test.ts

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

@jarvis9443
jarvis9443 merged commit 11497e1 into mainAug 19, 2026
13 checks passed
@jarvis9443
jarvis9443 deleted the feat/lb-taxonomy-chash-priority branch August 19, 2026 10:55
membphis added a commit that referenced this pull request Aug 24, 2026
`add_variant_titles` still assigns titles by position, and that is
deliberate — but it leaves one edit uncovered, so cover it where it
matters.
Positional title assignment is safe for the edits that actually happen.
`title_schema_variants` bails on a length mismatch, so adding or removing
a kind makes every title in that family disappear and
`openapi_titles_schema_variants_for_redoc_tabs` fail loudly; replacing a
variant in place edits the same line the title sits beside. The record
bears this out: #799 inserted a guardrail kind and updated the titles
correctly while silently breaking the descriptions (#1037), and #997
swapped `weighted` for `consistent_hash` with the titles staying right.
The descriptions had no such forcing function, which is the whole reason
the two tables diverged.
That leaves a length-preserving reorder as the only silent edit. The two
families that grow now pin their titles by discriminator in tests — the
guardrail kinds already did; the exporter kinds do now. Verified by
reordering two exporter titles: the new assertion names the offending
kind while `openapi_titles_schema_variants_for_redoc_tabs` stays green,
which is exactly the window being closed.
The remaining families are small, stable string enums, plus `Model`,
whose branches are mutual-exclusion shapes carrying no discriminator to
key on at all. Converting those would split `add_variant_titles` into two
mechanisms for a reorder that has never happened, and whose result is
self-contradictory on sight — the tab would read `Lakera Guard` over a
branch whose `kind` says `openai_moderation`, since kinds and
descriptions no longer travel by position.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jarvis9443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin - #997

Merged
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority
Aug 19, 2026
Merged

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin#997
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority

Conversation

@jarvis9443

Copy link
Copy Markdown
Contributor

What

Load-balancing taxonomy rework for model groups (the AISIX-Cloud#1206 requirement), aligned with the APISIX balancer model: weight and priority are per-target attributes available under every strategy, and consistent hashing is a strategy of its own rather than a flag on another one.

The driving scenario: two pools of direct models A & B — all traffic on A while any A member is healthy, spill to B only when the whole A pool is down, consistent-hash distribution inside both pools:

routing:
strategy: consistent_hashhash_on: # optional; default = [header x-aisix-routing-key, api_key]
- { type: header, name: x-session-id }targets:
- { model: vllm-a1 } # priority 0 = active pool
- { model: vllm-a2 }
- { model: vllm-a3 }
- { model: vllm-b1, priority: -1 } # backup pool (APISIX-style: higher value preferred)
- { model: vllm-b2, priority: -1 }

Changes

  • targets[].priority (i32, default 0, higher preferred, negatives for backups): targets partition into tiers; the strategy orders each tier independently — per-tier balancer state, mirroring APISIX's per-priority pickers; tiers concatenate, so a lower tier only receives traffic when every higher-tier target failed or was health/cooldown-filtered. max_fallbacks caps the concatenated walk, and the first request that discovers a fully-dead active tier still succeeds (in-request tier spill).
  • strategy: consistent_hash: ketama-style ring (160 points × weight units, capped; FNV-1a + splitmix64 finalizer — FNV alone clusters badly on name\0index inputs), keyed by the hash_on source chain (header / cookie / api_key / client_ip; default preserves the existing extraction chain byte-for-byte). A key's ring order doubles as its failover order: a failed target's keys spread to their ring successors, every other key keeps its mapping, and a recovered target takes its keys back.
  • round_robin is now smooth weighted round-robin (the nginx algorithm): exact proportions, interleaved, and equal/absent weights reproduce the old declaration-order cycle exactly.
  • least_busy now scores (1 + in-flight) / weight (the APISIX least_conn formula). failover / least_cost / least_latency accept weight but do not use it (documented in the schema).
  • Removed: the weighted strategy (folded into round_robin) and the sticky flag (superseded by consistent_hash). The retired enum value fails row-level in the lenient loader — a stale row cannot silently change meaning; declarative configs migrate weightedround_robin (weights kept) and weighted+stickyconsistent_hash. The control plane migrates stored rows and re-projects in the companion CP PR.

Breaking change (release notes)

strategy: weighted and routing.sticky are gone. Managed deployments are migrated by the CP automatically; self-hosted declarative configs must be updated as above. Sticky cohort assignments re-shuffle once when moving to consistent_hash (different hash construction).

Tests

  • Unit: WRR exactness/interleaving/state-reset, ring determinism, weight share, minimal-disruption property (dropping a target moves only its own keys), tier partition/order/cap, hash_on resolution.
  • E2E (consistent-hash-routing-e2e, 6 cases): key affinity + spread; cookie→api_key hash_on chain; backup tier idle while active healthy; fully-down active tier spills in-request with affinity inside the backup pool; cooldown recovery hands traffic back; single-member failure redistributes within its tier only.
  • E2E updates: distribution test now asserts exact 70/30 (smooth WRR is periodic); priority live-edit test (tier swap flips traffic); priority-tier test in routing-strategies. Full suite: 205 files / 620 tests green locally.

Fixes api7/AISIX-Cloud#1206

🤖 Generated with Claude Code

…ed into round_robin
Load-balancing taxonomy rework (AISIX-Cloud#1206), aligned with the
APISIX balancer model: weight and priority are per-target attributes
available under every strategy, and consistent hashing is a strategy of
its own rather than a flag on another one.
- targets[].priority (i32, default 0, higher preferred, negatives for
backups): targets partition into tiers; the strategy orders each tier
independently (per-tier balancer state); tiers concatenate, so a lower
tier only receives traffic when every higher-tier target failed or was
health-filtered. max_fallbacks caps the concatenated walk.
- strategy consistent_hash: ketama-style ring (160 points x weight
units, FNV-1a + splitmix64 finalizer), keyed by the hash_on source
chain (header / cookie / api_key / client_ip; default =
x-aisix-routing-key header, then the caller's API key id). A key's
ring order doubles as its failover order, so a failed target's keys
spread to their ring successors and every other key keeps its mapping.
- round_robin is now smooth WEIGHTED round-robin (the nginx algorithm);
equal weights keep the old declaration-order cycle exactly.
- least_busy now scores (1 + in-flight) / weight.
- REMOVED: the weighted strategy (fold into round_robin) and the sticky
flag (superseded by consistent_hash). The enum value now fails
row-level in the lenient loader; declarative configs must migrate
(weighted -> round_robin keeping weights; weighted+sticky ->
consistent_hash). The control plane migrates stored rows in the
companion PR.
Fixesapi7/AISIX-Cloud#1206
@nic-6443
nic-6443 requested a lite review from CopilotAugust 19, 2026 10:31

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in:5 minutes

Limit details: You’ve used all 2 included reviews currently available. Your 58 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

You can run this review on demand instead of waiting.

On-demand reviews are free until September 18, 2026. After that, they cost $0.25 per reviewed file.

  • Run review for free
How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca85c4da-d5d6-42af-b675-5cb68871d8f8

📥 Commits

Reviewing files that changed from the base of the PR and between 86dd01e and 8492a85.

📒 Files selected for processing (21)
  • README.md
  • crates/aisix-admin/src/openapi.rs
  • crates/aisix-core/src/lib.rs
  • crates/aisix-core/src/models/mod.rs
  • crates/aisix-core/src/models/routing.rs
  • crates/aisix-core/tests/model_schema_characterization.rs
  • crates/aisix-proxy/src/chat.rs
  • crates/aisix-proxy/src/client_ip.rs
  • crates/aisix-proxy/src/count_tokens.rs
  • crates/aisix-proxy/src/health.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/responses.rs
  • crates/aisix-proxy/src/routing.rs
  • crates/aisix-proxy/src/semantic.rs
  • schemas/resources/model.schema.json
  • schemas/resources/routing.schema.json
  • tests/e2e/src/cases/canary-routing-e2e.test.ts
  • tests/e2e/src/cases/consistent-hash-routing-e2e.test.ts
  • tests/e2e/src/cases/routing-priority-edit-e2e.test.ts
  • tests/e2e/src/cases/routing-strategies-e2e.test.ts
  • tests/e2e/src/cases/weighted-routing-distribution-e2e.test.ts

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

@jarvis9443
jarvis9443 merged commit 11497e1 into mainAug 19, 2026
13 checks passed
@jarvis9443
jarvis9443 deleted the feat/lb-taxonomy-chash-priority branch August 19, 2026 10:55
membphis added a commit that referenced this pull request Aug 24, 2026
`add_variant_titles` still assigns titles by position, and that is
deliberate — but it leaves one edit uncovered, so cover it where it
matters.
Positional title assignment is safe for the edits that actually happen.
`title_schema_variants` bails on a length mismatch, so adding or removing
a kind makes every title in that family disappear and
`openapi_titles_schema_variants_for_redoc_tabs` fail loudly; replacing a
variant in place edits the same line the title sits beside. The record
bears this out: #799 inserted a guardrail kind and updated the titles
correctly while silently breaking the descriptions (#1037), and #997
swapped `weighted` for `consistent_hash` with the titles staying right.
The descriptions had no such forcing function, which is the whole reason
the two tables diverged.
That leaves a length-preserving reorder as the only silent edit. The two
families that grow now pin their titles by discriminator in tests — the
guardrail kinds already did; the exporter kinds do now. Verified by
reordering two exporter titles: the new assertion names the offending
kind while `openapi_titles_schema_variants_for_redoc_tabs` stays green,
which is exactly the window being closed.
The remaining families are small, stable string enums, plus `Model`,
whose branches are mutual-exclusion shapes carrying no discriminator to
key on at all. Converting those would split `add_variant_titles` into two
mechanisms for a reorder that has never happened, and whose result is
self-contradictory on sight — the tab would read `Lakera Guard` over a
branch whose `kind` says `openai_moderation`, since kinds and
descriptions no longer travel by position.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jarvis9443
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin - #997

Merged
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority
Aug 19, 2026
Merged

feat(routing): consistent_hash strategy + priority tiers; fold weighted into round_robin#997
jarvis9443 merged 1 commit into
mainfrom
feat/lb-taxonomy-chash-priority

Conversation

@jarvis9443

Copy link
Copy Markdown
Contributor

What

Load-balancing taxonomy rework for model groups (the AISIX-Cloud#1206 requirement), aligned with the APISIX balancer model: weight and priority are per-target attributes available under every strategy, and consistent hashing is a strategy of its own rather than a flag on another one.

The driving scenario: two pools of direct models A & B — all traffic on A while any A member is healthy, spill to B only when the whole A pool is down, consistent-hash distribution inside both pools:

routing:
strategy: consistent_hashhash_on: # optional; default = [header x-aisix-routing-key, api_key]
- { type: header, name: x-session-id }targets:
- { model: vllm-a1 } # priority 0 = active pool
- { model: vllm-a2 }
- { model: vllm-a3 }
- { model: vllm-b1, priority: -1 } # backup pool (APISIX-style: higher value preferred)
- { model: vllm-b2, priority: -1 }

Changes

  • targets[].priority (i32, default 0, higher preferred, negatives for backups): targets partition into tiers; the strategy orders each tier independently — per-tier balancer state, mirroring APISIX's per-priority pickers; tiers concatenate, so a lower tier only receives traffic when every higher-tier target failed or was health/cooldown-filtered. max_fallbacks caps the concatenated walk, and the first request that discovers a fully-dead active tier still succeeds (in-request tier spill).
  • strategy: consistent_hash: ketama-style ring (160 points × weight units, capped; FNV-1a + splitmix64 finalizer — FNV alone clusters badly on name\0index inputs), keyed by the hash_on source chain (header / cookie / api_key / client_ip; default preserves the existing extraction chain byte-for-byte). A key's ring order doubles as its failover order: a failed target's keys spread to their ring successors, every other key keeps its mapping, and a recovered target takes its keys back.
  • round_robin is now smooth weighted round-robin (the nginx algorithm): exact proportions, interleaved, and equal/absent weights reproduce the old declaration-order cycle exactly.
  • least_busy now scores (1 + in-flight) / weight (the APISIX least_conn formula). failover / least_cost / least_latency accept weight but do not use it (documented in the schema).
  • Removed: the weighted strategy (folded into round_robin) and the sticky flag (superseded by consistent_hash). The retired enum value fails row-level in the lenient loader — a stale row cannot silently change meaning; declarative configs migrate weightedround_robin (weights kept) and weighted+stickyconsistent_hash. The control plane migrates stored rows and re-projects in the companion CP PR.

Breaking change (release notes)

strategy: weighted and routing.sticky are gone. Managed deployments are migrated by the CP automatically; self-hosted declarative configs must be updated as above. Sticky cohort assignments re-shuffle once when moving to consistent_hash (different hash construction).

Tests

  • Unit: WRR exactness/interleaving/state-reset, ring determinism, weight share, minimal-disruption property (dropping a target moves only its own keys), tier partition/order/cap, hash_on resolution.
  • E2E (consistent-hash-routing-e2e, 6 cases): key affinity + spread; cookie→api_key hash_on chain; backup tier idle while active healthy; fully-down active tier spills in-request with affinity inside the backup pool; cooldown recovery hands traffic back; single-member failure redistributes within its tier only.
  • E2E updates: distribution test now asserts exact 70/30 (smooth WRR is periodic); priority live-edit test (tier swap flips traffic); priority-tier test in routing-strategies. Full suite: 205 files / 620 tests green locally.

Fixes api7/AISIX-Cloud#1206

🤖 Generated with Claude Code

…ed into round_robin
Load-balancing taxonomy rework (AISIX-Cloud#1206), aligned with the
APISIX balancer model: weight and priority are per-target attributes
available under every strategy, and consistent hashing is a strategy of
its own rather than a flag on another one.
- targets[].priority (i32, default 0, higher preferred, negatives for
backups): targets partition into tiers; the strategy orders each tier
independently (per-tier balancer state); tiers concatenate, so a lower
tier only receives traffic when every higher-tier target failed or was
health-filtered. max_fallbacks caps the concatenated walk.
- strategy consistent_hash: ketama-style ring (160 points x weight
units, FNV-1a + splitmix64 finalizer), keyed by the hash_on source
chain (header / cookie / api_key / client_ip; default =
x-aisix-routing-key header, then the caller's API key id). A key's
ring order doubles as its failover order, so a failed target's keys
spread to their ring successors and every other key keeps its mapping.
- round_robin is now smooth WEIGHTED round-robin (the nginx algorithm);
equal weights keep the old declaration-order cycle exactly.
- least_busy now scores (1 + in-flight) / weight.
- REMOVED: the weighted strategy (fold into round_robin) and the sticky
flag (superseded by consistent_hash). The enum value now fails
row-level in the lenient loader; declarative configs must migrate
(weighted -> round_robin keeping weights; weighted+sticky ->
consistent_hash). The control plane migrates stored rows in the
companion PR.
Fixesapi7/AISIX-Cloud#1206
@nic-6443
nic-6443 requested a lite review from CopilotAugust 19, 2026 10:31

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

Copy link
Copy Markdown

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your current included review allowance is based on your included PR review attempts over the past 7 days.

Next review available in:5 minutes

Limit details: You’ve used all 2 included reviews currently available. Your 58 included PR review attempts over the past 7 days set your current allowance at 2 reviews per hour.

You can run this review on demand instead of waiting.

On-demand reviews are free until September 18, 2026. After that, they cost $0.25 per reviewed file.

  • Run review for free
How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits within each organization.

For paid Pro and Pro+ reviews, CodeRabbit uses a developer's included PR review attempts over the past 7 days to set the current hourly allowance. At typical activity levels, the full plan allowance applies. Higher sustained activity can lower the allowance until earlier attempts leave the 7-day window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ca85c4da-d5d6-42af-b675-5cb68871d8f8

📥 Commits

Reviewing files that changed from the base of the PR and between 86dd01e and 8492a85.

📒 Files selected for processing (21)
  • README.md
  • crates/aisix-admin/src/openapi.rs
  • crates/aisix-core/src/lib.rs
  • crates/aisix-core/src/models/mod.rs
  • crates/aisix-core/src/models/routing.rs
  • crates/aisix-core/tests/model_schema_characterization.rs
  • crates/aisix-proxy/src/chat.rs
  • crates/aisix-proxy/src/client_ip.rs
  • crates/aisix-proxy/src/count_tokens.rs
  • crates/aisix-proxy/src/health.rs
  • crates/aisix-proxy/src/messages.rs
  • crates/aisix-proxy/src/responses.rs
  • crates/aisix-proxy/src/routing.rs
  • crates/aisix-proxy/src/semantic.rs
  • schemas/resources/model.schema.json
  • schemas/resources/routing.schema.json
  • tests/e2e/src/cases/canary-routing-e2e.test.ts
  • tests/e2e/src/cases/consistent-hash-routing-e2e.test.ts
  • tests/e2e/src/cases/routing-priority-edit-e2e.test.ts
  • tests/e2e/src/cases/routing-strategies-e2e.test.ts
  • tests/e2e/src/cases/weighted-routing-distribution-e2e.test.ts

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

@jarvis9443
jarvis9443 merged commit 11497e1 into mainAug 19, 2026
13 checks passed
@jarvis9443
jarvis9443 deleted the feat/lb-taxonomy-chash-priority branch August 19, 2026 10:55
membphis added a commit that referenced this pull request Aug 24, 2026
`add_variant_titles` still assigns titles by position, and that is
deliberate — but it leaves one edit uncovered, so cover it where it
matters.
Positional title assignment is safe for the edits that actually happen.
`title_schema_variants` bails on a length mismatch, so adding or removing
a kind makes every title in that family disappear and
`openapi_titles_schema_variants_for_redoc_tabs` fail loudly; replacing a
variant in place edits the same line the title sits beside. The record
bears this out: #799 inserted a guardrail kind and updated the titles
correctly while silently breaking the descriptions (#1037), and #997
swapped `weighted` for `consistent_hash` with the titles staying right.
The descriptions had no such forcing function, which is the whole reason
the two tables diverged.
That leaves a length-preserving reorder as the only silent edit. The two
families that grow now pin their titles by discriminator in tests — the
guardrail kinds already did; the exporter kinds do now. Verified by
reordering two exporter titles: the new assertion names the offending
kind while `openapi_titles_schema_variants_for_redoc_tabs` stays green,
which is exactly the window being closed.
The remaining families are small, stable string enums, plus `Model`,
whose branches are mutual-exclusion shapes carrying no discriminator to
key on at all. Converting those would split `add_variant_titles` into two
mechanisms for a reorder that has never happened, and whose result is
self-contradictory on sight — the tab would read `Lakera Guard` over a
branch whose `kind` says `openai_moderation`, since kinds and
descriptions no longer travel by position.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@jarvis9443