Skip to content

feat: add load-balance hash-key to pin a session on the inbound user - #3133

Open
jianzhichun wants to merge 1 commit into
MetaCubeX:Alphafrom
jianzhichun:feat/load-balance-hash-key
Open

feat: add load-balance hash-key to pin a session on the inbound user#3133
jianzhichun wants to merge 1 commit into
MetaCubeX:Alphafrom
jianzhichun:feat/load-balance-hash-key

Conversation

@jianzhichun

Copy link
Copy Markdown

Problem

Both hashing strategies of a load-balance group derive their key from addresses:

strategy key
consistent-hashing destination (eTLD+1, or destination IP)
sticky-sessions source IP + destination

That assumes one client's traffic to one destination is one unit of work.

It is not, for a client whose single unit of work walks several destinations — an entry URL, a redirect to another domain, then that domain's CDN. The hash moves with the host, so the egress IP changes underneath a session the destination is tracking, and any state bound to the first address (a cookie issued to that IP, an authenticated session) is dead on the next request. The group is supposed to be the thing that holds such work on one member, and today it cannot.

sticky-sessions does not solve it either: a client like this is usually one local source address, so its concurrent jobs share a key and cannot be told apart, while the destination half still moves the pin.

What this adds

An opt-in hash-key option on load-balance groups. hash-key: user keys on the authenticated inbound user instead of on an address:

proxy-groups:
  - name: pool
    type: load-balance
    strategy: consistent-hashing   # or sticky-sessions
    hash-key: user
    use: [my-provider]

The client varies the proxy-auth username per unit of work, and every request carrying that username lands on the same member for as long as it stays healthy.

The inbound user is the one identity the client itself controls and can vary per job, and IN-USER rules already match on it, so this asks nothing new of the config format.

Routing each job through an IN-USER rule instead is not equivalent: that enumerates users statically in the config, and these identities are minted per job.

Behaviour

  • Unset by default. When hash-key is absent the code path is unchanged, so no existing config behaves differently.
  • An unauthenticated request falls back to the strategy's own key rather than to a constant, which would herd every anonymous request onto one member.
  • round-robin rejects hash-key at parse time instead of ignoring it — it hashes nothing, so accepting the option would promise stickiness it cannot provide.
  • An unrecognised value is rejected at parse time.

Tests

adapter/outboundgroup/loadbalance_test.go, 6 cases, including two that assert the current behaviour is what it is: the default key demonstrably moves across destinations for one user, and sticky-sessions demonstrably cannot separate two jobs sharing a source address.

Also checked end to end with mihomo -t: hash-key: user loads on both hashing strategies, hash-key: nonsense fails with unsupported hash-key: nonsense, round-robin + hash-key fails with round-robin does not hash, and a group without the option still loads.

Notes

No new dependency. Documentation lives in MetaCubeX/Meta-Docs; happy to open the matching docs PR if this direction is acceptable.

Both hashing strategies derive their key from addresses: consistent-hashing
from the destination, sticky-sessions from source plus destination. That
assumes one client's traffic to one destination is one unit of work.

It is not, for a client whose single unit of work walks several destinations —
an entry URL, a redirect to another domain, then that domain's CDN. The hash
moves with the host, so the egress IP changes underneath a session the
destination is tracking, and any state bound to the first address (a cookie
issued to that IP, an authenticated session) is dead on the next request.
Source plus destination does not help: such a client is usually one local
address, so its concurrent jobs share a key and cannot be told apart, while
the destination half still moves the pin.

`hash-key: user` keys on the authenticated inbound user instead. It is the
one identity the client itself controls and can vary per job, and `IN-USER`
rules already match on it, so nothing new is asked of the config format.
Routing every such job through an IN-USER rule is not equivalent: that
enumerates users statically, and these identities are minted per job.

Opt-in and unset by default, so no existing config changes behaviour. An
unauthenticated request keeps the strategy's own key rather than collapsing
every anonymous request onto one member, and round-robin rejects the option
outright rather than silently ignoring stickiness it cannot provide.
@wwqgtxx

wwqgtxx commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

This feature is quite interesting, but:

The name user isn't ideal; it lacks consistency with IN-USER.
The docs/config.yaml file within the project also needs to be updated.
Additionally, this PR modifies the behavior of both consistent-hashing and sticky-sessions. It is questionable whether these strategies remain sound after the change to the key definition; perhaps introducing a new strategy for this requirement would be better?

Sign up for free to 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