Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Fixed Window Limiter Laravel Package

beyondcode/laravel-fixed-window-limiter

A Laravel rate limiter using fixed time windows instead of the default sliding windows, so limits reset predictably. Useful for API throttling where you need strict, evenly spaced counters per window.

View on GitHub
Deep Wiki
Context7

Technical Evaluation

The decision on the table. Not "do we need rate limiting" — we almost certainly do on several surfaces — but "does this specific implementation's fixed-window semantics close a gap that Laravel's first-party Illuminate\Cache\RateLimiter plus throttle: middleware does not, and is that gap worth owning an archived dependency to get?" The package is a clock-aligned counter: one cache key per identity per window bucket, incremented per request, expiring at the boundary. That model is genuinely valuable for exactly one product scenario — mirroring an external provider's advertised quota ("100/min, resets on the minute") so outbound calls stop before the remote returns 429s, and so customer-facing "remaining / resets at :00" UI matches the partner's mental model. Every other justification resolves to primitives we already have.

Architecture fit — clean but shallow, and shallow is the problem. Cache-store abstraction, RateLimiter-style facade, HTTP middleware: nothing conflicts with a standard Laravel service. But a component this easy to replace cannot justify an ownership premium. We would be accepting permanent maintenance liability for roughly 30–50 lines of behaviour we could own outright.

Integration feasibility — mechanically trivial, practically constrained. MIT license, small surface, Composer-installable. The blocker is vintage: last release January 2020, illuminate/* and PHP constraints targeting the Laravel 6/7 era. Running it on Laravel 10–12 requires --ignore-platform-reqs, a Composer alias, or a fork with widened constraints. Each of those silently converts "add a dependency" into "own a code artifact" — a resourcing decision that must be funded before the roadmap commits, not discovered at implementation time.

Technical risk, ranked by product blast radius.

  • Unverified atomicity (highest severity). The canonical fixed-window defect is a non-atomic INCR followed by EXPIRE. A crash between the two leaves a key with no TTL and permanently throttles that identity. The symptom is a support ticket, not an alert — a product-visible failure with no telemetry path. Must be confirmed by source inspection (Lua or MULTI) before any production exposure.
  • Abandonment. Archived, 67 stars, zero dependents, no changelog since 2020, no security response channel, no compatibility patches. Treat as a reference implementation, not a supplier.
  • No production corroboration. Zero dependents means nobody else has exercised Redis Cluster key-slot behaviour for multi-key scripts, predis vs phpredis divergence, or connection-failure behaviour. We would be the first.
  • Implicit policy. If the limiter fails open on Redis error, an availability-over-safety decision is baked into a dependency nobody owns and nobody documented. That is a decision for security and support to make explicitly, not to inherit by accident.

Key questions for the team.

  1. Which requirement does native RateLimiter::for() + throttle: fail — exact clock alignment, cross-service shared counters, or response-header shape?
  2. Is fixed required, or is sliding acceptable? If sliding is acceptable, the case for this package collapses entirely.
  3. Intended behaviour when Redis is unavailable: fail open or fail closed — documented and signed off by whom?
  4. Does any path need multi-key atomicity that breaks under Redis Cluster?
  5. Who is the named owner of a fork, and what is the removal trigger and date?
  6. Opportunity cost versus ~30–50 lines of in-house Lua (INCR + conditional PEXPIRE) with our own tests?

Integration Approach

Stack fit. Positive on infrastructure — Redis is already provisioned, nothing new to run, no new operational surface. Negative on governance — an archived dependency in the graph triggers security review, license-scanning exceptions, dependency-policy exceptions, and a recurring "is this still safe?" conversation at every audit. Net: adopt only if a concrete semantic gap is written down and agreed by product, security, and the owning engineer.

Frame it as a three-way decision, not a purchase.

Option Upfront cost Ongoing cost When it wins
Native RateLimiter + Redis store Near zero Near zero Default case; sliding/decay semantics acceptable
In-house Lua limiter (~30–50 LOC) 1–3 days incl. tests Low, well understood, ours Exact clock-aligned windows genuinely required
Fork this package 1–2 days to widen constraints Permanent ownership Already embedded in a legacy path

The honest TCO comparison is dominated by the third column, which is why the middle row almost always beats the bottom row. Present this table to stakeholders as the decision — do not present the package as the only route to fixed windows.

Migration path. Given zero dependents and a 2020 release, the most likely finding is that nothing in our estate uses it and no work is required. Sequence it as containment, not adoption:

  1. Inventory. Confirm usage before spending anything.
  2. Map semantics. If fixed windows are genuinely needed, translate key naming, prefix, and TTL scheme onto RateLimiter::for() with the Redis store, or onto the in-house Lua script.
  3. Preserve boundary alignment at cutover. If the replacement resets counters mid-window, users see a burst in the first minute after deploy — that reads as a product regression and lands on support. Align window phase and keep key prefixes stable.
  4. Fork only if neither path preserves semantics — then vendor into our namespace and treat as first-party code with an owner, tests, and an ADR.

Compatibility. Any retained code must be validated against PHP 8.1–8.3, Laravel 10–12, Redis 6/7, and both predis and phpredis. That matrix is not covered upstream; it is entirely ours. This is the single most important scoping input: the integration is a day, the compatibility matrix is a recurring commitment that outlives the sprint.

Sequencing. Decide native vs. in-house → 1–2 day spike proving atomic increment and fail-mode → pin and vendor only if forking → canary one low-risk endpoint behind a feature flag → compare 429 rates and headers against the incumbent for a full week → record the outcome in an ADR with rationale and removal trigger.

Legitimate near-term scope where I would not block a short-term bridge: internal-only endpoints, webhook throttling, login/OTP attempt caps, expensive export/report endpoints, and outbound third-party API protection. None carry SLA, billing, or fairness commitments. Deliberately out of scope: per-tenant quota hierarchies, billing-linked limits, public API contracts, and anything where the limit is a customer-facing promise.

Operational Impact

Maintenance. There is no upstream to lean on. Every Laravel major, every PHP minor, and every Redis client bump becomes our work — triage, patch, test, redeploy. That is a recurring line item, not a one-time cost, and it must be budgeted with a named owner. If nobody will own it, do not adopt it; that rule alone resolves most of this evaluation.

Support. No security response path means vulnerabilities in this component have no vendor to escalate to and no CVE pipeline to watch. Audits will ask; we will need a standing exception with a rationale. Worse, the failure signatures here are support-ticket-shaped rather than alert-shaped: a permanently throttled identity from a TTL-less key, or a boundary burst that lets a caller momentarily reach ~2x the configured limit. Neither trips an alert. Both generate user-visible complaints, and the on-call path has no runbook.

Scaling. The key model is one key per identity per window, TTL-bounded, so memory is self-limiting — that is a real advantage over unbounded counters. But high-cardinality identities plus window turnover produce steady key churn, and cache-store eviction policy becomes a silent correctness input: under memory pressure, evicted keys reset quotas early and the limiter fails open without a log line. Multi-region or multi-store deployments need a documented stance on whether counters are global or per-region, and Redis Cluster requires confirming the increment-and-expiry path is single-slot.

Failure modes to specify and test before exposure.

Failure Observable symptom Required decision
Redis unavailable All requests allowed or all rejected Fail open vs. fail closed — sign off explicitly
Crash between INCR and EXPIRE Identity throttled permanently Verify atomicity; add TTL-repair
Window boundary Traffic briefly ~2x limit Accept for non-critical surfaces only
Cache eviction under pressure Quota resets early, silently Pin eviction policy; alert on evictions
Clock skew across app nodes Misaligned windows, uneven bursts Confirm time source is the cache, not app servers

Each row is a product decision disguised as an engineering detail. The TPM's job is to get them decided in writing before code ships, not after the first incident review.

Ramp-up. Learning the API is an afternoon; that is not the cost. The real ramp-up is ownership capability — writing the boundary-burst and atomicity tests, building the compatibility matrix, establishing the runbook, and training on-call to recognize "quota stuck at zero" as this component. Budget 3–5 engineer-days of enablement for anything beyond a throwaway spike.

Exit plan. Whatever we decide, record the removal trigger and date in the ADR. For a component this small, replacement is cheap — the only thing that makes it expensive is discovering it embedded in a critical path two years from now with no owner and no tests.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core