Skip to content

Add proposals for client-identity rate limiting (write and query paths)#7681

Open
krunaljain wants to merge 4 commits into
cortexproject:masterfrom
krunaljain:proposal/client-identity-rate-limiting
Open

Add proposals for client-identity rate limiting (write and query paths)#7681
krunaljain wants to merge 4 commits into
cortexproject:masterfrom
krunaljain:proposal/client-identity-rate-limiting

Conversation

@krunaljain

Copy link
Copy Markdown

What this PR does

Adds two companion design proposals to docs/proposals/:

  • client-identity-rate-limiting.md: proposes a second, optional rate-limiting dimension below the tenant on the write (ingestion) path, keyed on a new trusted X-User-ID header, following the same trust model Cortex already uses for X-Scope-OrgID.
  • client-identity-query-rate-limiting.md: a companion follow-on applying the same identity mechanism to query-frontend rate limiting on the read path.

Motivation

Cortex enforces ingestion and query limits at the tenant level only. In practice a single tenant is often shared by multiple independent services or teams, and today's limiters cannot distinguish a well-behaved writer/querier from a noisy or misbehaving one sharing the same X-Scope-OrgID. These proposals add an opt-in, additive mechanism to throttle by client identity below the tenant, without changing existing tenant-level behavior for anyone who doesn't configure it.

Both proposals are:

  • Fully opt-in and backward compatible (disabled by default; zero behavior change unless explicitly configured).
  • Additive to, not a replacement for, existing tenant-level limits.
  • Scoped with explicit rollout plans (introduced as experimental features, phased local-then-global enforcement).

Design highlights

  • Identity comes from a new, generic, gateway-set X-User-ID header, trusted the same way Cortex already trusts X-Scope-OrgID (i.e. no new authentication mechanism is introduced).
  • Per-client tracking is bounded via least-recently-used eviction (matching Cortex's existing tenant-federation regex-resolver cache pattern), rather than an unbounded map, to avoid a memory-growth/DoS vector.
  • The write-path and query-path proposals share the same identity mechanism and header, but are kept as two separate documents given their different enforcement points (distributor vs. query-frontend) and to keep review scope focused.

Status

Both are filed as proposals for discussion (Status: Proposed). No code changes are included in this PR; implementation would follow as separate PRs once the design is reviewed.

Checklist

  • Docs-only change; no code, tests, or CHANGELOG entry needed.
  • Commit signed off (DCO).

Krunal Jain added 3 commits July 10, 2026 11:10
Cortex's ingestion and query rate limits are enforced at the tenant
level only. When multiple independent services or teams share a
tenant, one noisy or misbehaving client can exhaust the whole
tenant's budget for everyone else, and there is no way to distinguish
who, within a tenant, is responsible.

Adds two companion design proposals:

- client-identity-rate-limiting.md: write-path (distributor)
  enforcement, keyed on a new trusted X-User-ID header, following the
  same trust model Cortex already uses for X-Scope-OrgID.
- client-identity-query-rate-limiting.md: the read-path (query-frontend)
  follow-on, reusing the same header and identity approach.

Both are opt-in, additive to existing limits, and covered as
experimental features on the proposed rollout plan.

Signed-off-by: Krunal Jain <kjain25@apple.com>
Both proposals now specify global enforcement (dividing the per-client
limit across healthy replicas via the existing ring/distributor-count
mechanism) rather than local-only with global as a follow-up.

- Write path: rename section 'Local enforcement is less precise' ->
  'Global enforcement'; update enforcement description and rollout plan
- Query path: rename section 'Local enforcement precision...' ->
  'Global enforcement and interaction with the results cache'; update
  frontend/scheduler-split section, rate-limiter description, rollout
  plan, and open-questions cross-reference

Signed-off-by: Krunal Jain <kjain25@apple.com>
The previous commit said 'global enforcement' but implied Cortex's
existing divide-by-replica-count approximation. This updates both
proposals to describe the intended gossip-based mechanism:

- Each replica tracks a local per-client counter
- Counters are gossiped to peers via the existing memberlist transport
- Enforcement is against the cluster-wide sum, not a per-replica share

This is more accurate than divide-by-N: it handles uneven load
distribution and replica count changes without reconfiguration.
The gossip convergence lag (order of memberlist gossip interval) is
noted as an acceptable property of this best-effort limit.

Signed-off-by: Krunal Jain <kjain25@apple.com>
@krunaljain krunaljain force-pushed the proposal/client-identity-rate-limiting branch from 5141edf to 19f20b7 Compare July 10, 2026 18:11

@friedrichg friedrichg left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks!. I have a couple of suggestions/questions

Comment thread docs/proposals/client-identity-rate-limiting.md Outdated
Comment thread docs/proposals/client-identity-query-rate-limiting.md Outdated
Comment thread docs/proposals/client-identity-query-rate-limiting.md Outdated
Comment thread docs/proposals/client-identity-rate-limiting.md Outdated
denial-of-service vector: a caller able to set arbitrary `X-User-ID` values could grow that map
without limit simply by rotating identities.

Cortex already solves an analogous problem elsewhere: the tenant-federation regex resolver bounds

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The resolver in tenant federation improves latency, when the cache is full, we just follow back to being slow.

This we are talking about limiting traffic. My main question is what happens when the cache is full, do we just accept everything. I am not sure that is what we want.

Maybe there is something I am missing. can you clarify?

@krunaljain krunaljain Jul 10, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

When the cache is full, we intend to implement an LRU strategy to evict the earliest cache entry. This implies we might miss enforcing rate limiting for some long running users as an edge case. That said, the cache size is configurable for users.

…enforcement design

- Replace 'global enforcement via gossip' with ring-based distributed
  enforcement: shard (tenant, client) onto the existing ring so one
  replica owns the authoritative counter per key; works on any ring
  backend (Consul/Etcd/memberlist), not memberlist-only.
- Rename 'global' counter language throughout both proposals to
  'distributed per-tenant' to avoid confusion with cross-tenant scope.
- Move tracked_clients_limit from a process-wide cap to a per-tenant
  override, consistent with every other Cortex limit.
- Clarify LRU eviction semantics under rate limiting: eviction resets
  a client's counter (allowing a brief burst), which is the intentional
  tradeoff over refusing new clients when the cap is full.

Signed-off-by: Krunal Jain <kjain25@apple.com>
@krunaljain krunaljain requested a review from friedrichg July 10, 2026 22:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants