Skip to main content
Global counters let regions share rate limit usage without turning every request into a synchronous global write. The model is a grow-only counter, or G-Counter, with one component per region and window cell.

G-Counter model

A G-Counter is a conflict-free replicated data type for counts that only increase. It splits one logical count into independently owned components. The global value is the sum of all components. For rate limiting, each region owns one component for each window cell. The component identity is:
(workspace, namespace, identifier, duration, sequence, region)
Each region writes only its own component. Region A never writes Region B’s component. A component only moves forward inside a sequence. The merge rule is simple:
component = max(component, observedRegionalCount)
global = sum(all components)
That gives the counter four useful properties.
PropertyEffect
MonotonicAn older delayed publish cannot move a component backward
IdempotentPublishing or importing the same value twice is harmless
CommutativeRegions can publish and import in different orders
AssociativeComponents can be aggregated before import

Importing without feedback loops

A region imports components in two different ways. Its own component is a regional lower bound and can raise the local regional count. Foreign components are summed separately and added to decisions, but they are never published again.
Region A decision        = A regional count + imported count from B and C
Region A publish         = A regional count only
Region A own-row import  = max(A regional count, A published component)
This separation prevents double counting. If Region A published A + B, then Region B could import that value and count its own traffic again. If Region A folded its own component into imported global count instead of regional count, it would count its own traffic twice during local decisions. Own-row import is only a safety net. The regional origin remains the source of truth for in-region convergence, and importing an own component does not make the local entry fresh against that origin.

Why it fits rate limiting

Usage inside one fixed window cell is grow-only. Requests add cost. The cell does not need to decrement while it is active. Sliding-window behavior comes from reading two cells, weighting the previous one, and letting old cells expire.
current = currentRegionalCount + currentGlobalCount
previous = previousRegionalCount + previousGlobalCount
effective = current + (previous * previousWindowWeight) + cost
The G-Counter resets by identity. A new fixed window has a new sequence, so it has a new set of components. Old components stop mattering when they can no longer contribute to the sliding-window calculation.

Publish threshold

The limit is not part of the G-Counter merge rule. It only decides when a regional count is worth publishing. Small counts are usually irrelevant to remote decisions, so they stay regional. Once a region uses a meaningful fraction of the limit, the count is published and can affect later decisions elsewhere. This keeps cross-region writes proportional to useful signal rather than total request volume. The request path still makes local decisions immediately, and global convergence catches up for identifiers that are approaching their limit.

Invariants

Global counters rely on these invariants:
  • Each region owns only its own component.
  • Component values are merged with max, never replacement by an older value.
  • A region’s own imported component can only raise regional count.
  • Foreign imported count is read by the request path but never republished.
  • Own components and foreign components stay separate during import.
  • A sequence is immutable once it ages out. New windows use new component identities.