Add Limiter#peek read-without-increment API

What does this MR do and why?

Adds Labkit::RateLimit::Limiter#peek — the read-without-increment counterpart to #check. Peek is needed to unblock cohort 3 of the GitLab ApplicationRateLimiter migration to labkit, where call sites already use a peek-then-check pattern that today routes peek through the legacy strategy because labkit only supports incrementing reads.

Implementation

Limiter#peek(identifier) delegates to a new Evaluator#peek path that mirrors Evaluator#check except:

  • Evaluator#read_with_ttl pipelines GET + TTL with no EXPIRE call.
  • A missing Redis key (GET => nil, TTL => -2) reports count: 0, matched: true, exceeded: false, remaining: resolved_limit, reset_at: now + period.
  • Matching, fail-open-on-error, and Identifier-vs-Hash passthrough semantics are identical to #check.

evaluate_rule and the new peek_rule share a build_result helper that constructs the Result from (rule, resolved_limit, resolved_period, count, ttl).

Metrics

Peek does not emit per-call metrics. calls_total, limit_gauge, and period_gauge remain owned by #check. errors_total is shared so Redis failures during peek are still surfaced.

Rationale: peek is observational, not transactional. A separate peeks_total counter (an earlier iteration of this MR) was removed because the typical peek-then-check pattern would double-count otherwise-equivalent work, and operators would read peek volume as if it represented real rate-limit activity.

Tests

163 rate_limit specs pass. New coverage:

  • Limiter#peek dispatch (Identifier passthrough, no INCR, no EXPIRE).
  • Evaluator#peek (Redis interaction, missing key, under/at/over threshold, ttl > 0 vs ttl < 0 reset_at, non-matching rule, error path, callable limit/period resolved at peek time).
  • Metrics deliberately unchanged; assertions verify peek does not touch calls_total, limit_gauge, or period_gauge, and does bump errors_total on Redis failure.

Merge request reports

Loading
Loading