feat(filter): add ring hash, subset, zone-aware, and priority LB strategies - #949
feat(filter): add ring hash, subset, zone-aware, and priority LB strategies#949abdallahsamabd wants to merge 1 commit into
Conversation
|
PR too large: 1859 lines added (limit: 750, excludes Cargo files, tests, docs, examples, and benchmarks). Please split into smaller PRs. Add |
294b972 to
8237f65
Compare
8237f65 to
73f5ea9
Compare
praxis-bot
left a comment
There was a problem hiding this comment.
PR Review
Adds ring hash, subset, zone-aware, and priority LB strategies with config types, unit tests, integration tests, and example configs.
Overall: Well-structured implementation. The four strategies follow established patterns (health-aware selection, panic mode fallback, weighted endpoints). Config types use deny_unknown_fields and sensible defaults. Test coverage is thorough with both unit and integration tests. One correctness bug in the xxHash64 implementation and two convention issues.
| Severity | Count |
|---|---|
| Critical | 0 |
| Large | 1 |
| Medium | 2 |
Note: tests/integration/tests/suite/examples/retry_policy.rs appears unrelated to this PR's LB feature work. Consider splitting it into its own PR for cleaner history.
| fn xxhash64(s: &str) -> u64 { | ||
| const PRIME1: u64 = 0x9E37_79B1_85EB_CA87; | ||
| const PRIME2: u64 = 0x14DE_F9DE_A2F7_9CD6; | ||
| const PRIME3: u64 = 0x0165_6738_5AF0_6C85; |
There was a problem hiding this comment.
[Large] The PRIME3 constant 0x0165_6738_5AF0_6C85 does not match the xxHash64 specification (XXH_PRIME64_3 = 0x0165_667B_19E3_779F in xxhash.h). This causes the xxhash hash function to produce non-standard output despite being documented as "xxHash 64-bit" in the config enum. The incorrect constant affects tail processing for any input whose length mod 8 is between 4 and 7, which covers roughly half of all virtual node keys.
Change to:
const PRIME3: u64 = 0x0165_667B_19E3_779F;There was a problem hiding this comment.
fixed in the next push. The constant was wrong; corrected to 0x1656_67B1_9E37_79F9 per xxhash.h. Tests updated and passing
| /// Hash function to use for the ring. Defaults to FNV-1a. | ||
| #[serde(default)] | ||
| pub hash_function: HashFunction, | ||
|
|
There was a problem hiding this comment.
[Medium] virtual_nodes (here), min_local_healthy_pct (line 193), and overprovisioning_factor (line 218) are constrained numerics with no parse-time validation. Per project conventions, use #[serde(try_from)] with newtype wrappers:
virtual_nodesmust be>= 1: a value of 0 creates an empty ring, silently routing all requests through the panic-mode fallback.min_local_healthy_pctmust be<= 100: values above 100 make the zone-aware strategy always spill to all endpoints, defeating its purpose.overprovisioning_factormust be>= 1: a value of 0 causeshealthy_count * 0 >= total * 100to always be false, making every tier fail the capacity check.
| | [random.yaml](configs/traffic-management/random.yaml) | Selects an upstream endpoint at random, weighted by endpoint weight | | ||
| | [rate-limiting.yaml](configs/traffic-management/rate-limiting.yaml) | Token bucket rate limiter with per-IP or global modes | | ||
| | [redirect.yaml](configs/traffic-management/redirect.yaml) | Returns a 3xx redirect without contacting any upstream | | ||
| | [ring-hash.yaml](configs/traffic-management/ring-hash.yaml) | Configurable ring-hash with pluggable hash function and virtual node density | |
There was a problem hiding this comment.
[Medium] subset-lb.yaml and zone-aware.yaml break the table's alphabetical ordering. subset-lb (su) should come after static-response (st), and zone-aware (z) should be the last entry, after weighted-load-balancing (w). Move both lines to their correct positions.
There was a problem hiding this comment.
now generated via cargo xtask sync-example-readme --fix which enforces ordering automatically
9133c5e to
c3954eb
Compare
…tegies Signed-off-by: Abdallah Samara <abdallahsamabd@gmail.com>
c3954eb to
45c39a4
Compare
praxis-bot
left a comment
There was a problem hiding this comment.
Incremental Review (post-fix)
The PRIME3 fix from the previous review was applied correctly, and the examples/README.md ordering is now alphabetical. However, the xxHash64 implementation still has three incorrect prime constants.
| Severity | Count |
|---|---|
| Large | 1 |
| Medium | 0 |
| Small | 0 |
| const PRIME2: u64 = 0x14DE_F9DE_A2F7_9CD6; | ||
| const PRIME3: u64 = 0x1656_67B1_9E37_79F9; | ||
| const PRIME4: u64 = 0x27D4_EB2F_1656_67C5; | ||
| const PRIME5: u64 = 0x1656_67B1_9E37_79F9; |
There was a problem hiding this comment.
[Large] PRIME3 was fixed, but three other xxHash64 constants are still incorrect. Most visibly, PRIME5 is identical to PRIME3 (0x1656_67B1_9E37_79F9), which is always wrong -- no hash algorithm reuses the same constant for two different purposes.
Full comparison against the xxHash64 specification:
| Constant | Code value | Canonical value | Status |
|---|---|---|---|
| PRIME1 | 0x9E37_79B1_85EB_CA87 |
0x9E37_79B1_85EB_CA87 |
correct |
| PRIME2 | 0x14DE_F9DE_A2F7_9CD6 |
0xC2B2_AE3D_27D4_EB4F |
wrong |
| PRIME3 | 0x1656_67B1_9E37_79F9 |
0x1656_67B1_9E37_79F9 |
correct |
| PRIME4 | 0x27D4_EB2F_1656_67C5 |
0x85EB_CA77_C2B2_AE63 |
wrong (has PRIME64_5 value) |
| PRIME5 | 0x1656_67B1_9E37_79F9 |
0x27D4_EB2F_1656_67C5 |
wrong (duplicates PRIME3) |
Note that the same wrong PRIME2 is also used in xxh64_round and the wrong PRIME4 in xxh64_merge_round. All six occurrences need updating.
Consider adding a reference-vector test (hash a known string, assert the exact u64 output) to catch constant errors at compile time.
praxis-bot
left a comment
There was a problem hiding this comment.
PR Review
Adds ring hash, subset, zone-aware, and priority LB strategies with config types, validation, unit tests, integration tests, and example configs.
Overall: Solid implementation. The four strategies follow established patterns, config types use deny_unknown_fields and sensible defaults, the validation module catches invalid bounds, and test coverage is thorough. Two issues found: a behavioral gap in subset fallback and field ordering convention violations.
| Severity | Count |
|---|---|
| Critical | 0 |
| Large | 0 |
| Medium | 2 |
| if let Some(strategy) = &self.subset_strategy { | ||
| let result = strategy.select(hash_key, health); | ||
| if result.is_some() { | ||
| return result; |
There was a problem hiding this comment.
[Medium] When all subset endpoints are unhealthy, the inner strategy's panic mode returns an unhealthy endpoint, making result.is_some() true and bypassing the fallback_policy. This means fallback_policy: any_endpoint never activates on health degradation -- only on an empty selector match.
Scenario: three canary endpoints all fail health checks; two stable endpoints are healthy. The subset's inner RoundRobin panic-routes to an unhealthy canary instead of falling back to a healthy stable endpoint.
Add a health-aware check before returning the subset result. For example, store the subset endpoint indices at construction and skip to fallback when all of them are unhealthy:
if let Some(strategy) = &self.subset_strategy {
if !self.all_subset_unhealthy(health) {
let result = strategy.select(hash_key, health);
if result.is_some() {
return result;
}
}
}Also add a test: construct a Subset with health state where all subset endpoints are marked unhealthy and fallback_policy: AnyEndpoint, then assert the selected endpoint comes from outside the subset.
| pub struct RingHashOpts { | ||
| /// Name of the request header to use as the hash key. | ||
| #[serde(default)] | ||
| pub header: Option<String>, |
There was a problem hiding this comment.
[Medium] Three of the four new config structs have non-alphabetical field ordering, violating the project convention ("Struct fields: name first, then alphabetical").
RingHashOpts — current: header, hash_function, virtual_nodes; correct: hash_function, header, virtual_nodes.
SubsetOpts (line 155) — current: selector, inner_strategy, fallback_policy; correct: fallback_policy, inner_strategy, selector.
ZoneAwareOpts (line 187) — current: local_zone, inner_strategy, min_local_healthy_pct; correct: inner_strategy, local_zone, min_local_healthy_pct.
Reorder each struct's fields alphabetically.
What does this PR do?
Implements the four remaining load balancing algorithms from the proposal (#913):
any_endpointorno_endpoint).Also extends the
Endpointconfig withmetadata,zone, andpriorityfields to support the composite strategies. Includes example configs, integration tests, and unit tests for all new algorithms.Which issue(s) does this relate to?
Part of #109
Checklist
git commit -s)make lint && make testpasses locallyDoes this introduce a breaking change?
No. The new
metadata,zone, andpriorityfields onEndpoint::Weightedall have serde defaults ({},null,null) so existing configs continue to work unchanged.