Windows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallOutdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchTo enforce one quota across multiple Node.js instances, keep the quota state in shared Redis rather than in each process. A strict sliding-window log can store one sorted-set member per admitted request, remove entries outside the active interval, count what remains, and admit the next request only if the count is below the limit. Put those operations in one short Lua script: otherwise simultaneous requests can each read the same old count and collectively exceed the quota.
Why a distributed limiter needs shared state
A counter held in a Node.js process applies only to requests that reach that process. With multiple instances, traffic can be spread across them and each local counter can admit its own allowance. A shared Redis key gives those instances one state transition to consult.
As an Amazon Associate I earn from qualifying purchases.
Choose the key scope according to the resource and the threat you are controlling. Common dimensions include a user, IP address, API key, tenant, or model. A per-IP quota and a per-account quota answer different questions; neither is universally correct. Make the scope and policy visible in the key namespace, for example rl:{tenant-42}:api-read:v1. Use a stable, unambiguous identifier for the subject, and avoid putting credentials or other secrets directly into key names.
How the strict sliding-window log works
For a limit of L requests in a window of W milliseconds, represent every admitted request as a sorted-set member. Its score is the request time; its member is a unique event ID. At time now, use the interval (now - W, now]: remove scores less than or equal to the cutoff, count the remaining members, then add the new event only if the count is below L.
#1 Best Overall
The open left boundary matters. An event exactly at now - W is expired under this convention. A timestamp alone is not a safe member ID: separate requests can arrive in the same millisecond, and a sorted set cannot hold two identical members. Keep the timestamp as the score and add a unique ID, such as a UUID, as the member.
Here is the Redis-side transition. It obtains time from Redis so application instances do not make the decision using clocks that may differ. The key is the only Redis key touched by this script.
Rank #2
-- KEYS[1]: sorted-set key
-- ARGV[1]: positive integer window in milliseconds
-- ARGV[2]: positive integer request limit
-- ARGV[3]: unique event ID generated by the caller
local window = tonumber(ARGV[1])
local limit = tonumber(ARGV[2])
local eventId = ARGV[3]
if not window or window < 1 or window % 1 ~= 0 then
return redis.error_reply('window must be a positive integer')
end
if not limit or limit < 1 or limit % 1 ~= 0 then
return redis.error_reply('limit must be a positive integer')
end
if not eventId or eventId == '' then
return redis.error_reply('event ID is required')
end
local time = redis.call('TIME')
local now = tonumber(time[1]) * 1000 + math.floor(tonumber(time[2]) / 1000)
local cutoff = now - window
-- Active interval is (cutoff, now]. Remove the inclusive expired boundary.
redis.call('ZREMRANGEBYSCORE', KEYS[1], '-inf', cutoff)
local count = redis.call('ZCARD', KEYS[1])
if count < limit then
redis.call('ZADD', KEYS[1], now, eventId)
-- Inactive subjects should not retain an empty quota key indefinitely.
redis.call('PEXPIRE', KEYS[1], window)
return {1, limit - count - 1, 0}
end
-- The oldest retained event determines when another request can fit.
local oldest = redis.call('ZRANGE', KEYS[1], 0, 0, 'WITHSCORES')
local retryAfter = math.max(1, tonumber(oldest[2]) + window - now + 1)
return {0, 0, retryAfter}
The script returns three integers: allowed (1 or 0), remaining quota immediately after this decision, and retry delay in milliseconds. A denied request does not add an event. The extra millisecond in the denial calculation reflects the chosen inclusive-pruning boundary at millisecond precision; clients should still treat the delay as guidance and retry through the limiter rather than assuming admission.
Call the script from TypeScript
The TypeScript layer should validate policy inputs, construct the scoped key, create a unique event ID, call Redis, and translate the reply into an application result. The small interface below is a deliberate adapter boundary: Redis client libraries and versions differ in how they express EVAL arguments and decode replies. Implement this interface using the API of the exact client version deployed by your service.
Rank #3
import { randomUUID } from 'node:crypto';
interface RateLimitRedis {
// Implement with the selected client's EVAL invocation.
// Return the three integer elements produced by the Lua script.
evalRateLimitScript(
script: string,
key: string,
args: [windowMs: string, limit: string, eventId: string]
): Promise<unknown>;
}
type RateLimitResult = {
allowed: boolean;
remaining: number;
retryAfterMs: number;
};
function positiveSafeInteger(value: number, name: string): void {
if (!Number.isSafeInteger(value) || value < 1) {
throw new RangeError(`${name} must be a positive safe integer`);
}
}
function decodeReply(reply: unknown): [number, number, number] {
if (!Array.isArray(reply) || reply.length !== 3) {
throw new Error('Unexpected Redis rate-limit reply');
}
const values = reply.map(Number);
if (values.some(value => !Number.isSafeInteger(value))) {
throw new Error('Invalid Redis rate-limit reply values');
}
return values as [number, number, number];
}
async function consumeQuota(
redis: RateLimitRedis,
script: string,
subjectId: string,
windowMs: number,
limit: number
): Promise<RateLimitResult> {
positiveSafeInteger(windowMs, 'windowMs');
positiveSafeInteger(limit, 'limit');
if (!subjectId) throw new Error('subjectId is required');
// Encode or hash subjectId if its raw form should not appear in Redis keys.
const key = `rl:{${subjectId}}:api-read:v1`;
const [allowed, remaining, retryAfterMs] = decodeReply(
await redis.evalRateLimitScript(script, key, [
String(windowMs), String(limit), randomUUID()
])
);
if ((allowed !== 0 && allowed !== 1) || remaining < 0 || retryAfterMs < 0) {
throw new Error('Invalid Redis rate-limit decision');
}
return { allowed: allowed === 1, remaining, retryAfterMs };
}
Wire the script text and adapter to your chosen Redis client, and verify the argument ordering, script invocation, return decoding, and Cluster behavior against that library’s documentation and the Redis version in your deployment. The snippet is an implementation pattern, not a claim that a particular package/version was tested. Map a denied result to the response convention used by your API, commonly an HTTP 429 response with a retry hint; do not let the HTTP handler make a second, independent quota decision.
Why Lua, and what atomicity does—and does not—mean
Pruning, counting, deciding, and conditionally inserting must be one indivisible operation for a key. If a client sends separate prune, count, and add commands, two concurrent requests can both observe one remaining slot and both take it. Redis guarantees a script’s atomic execution, so another command cannot interleave with this transition on the server.
Rank #4
Atomicity protects this state change; it does not make Redis infallible or eliminate deployment failure modes. Scripts also block Redis while they run. Keep the script bounded: this one touches one key, performs a range prune and count, and reads at most the oldest member when denied. The log retains at most the configured number of active admitted requests during steady policy operation, but changing limits, bursts around expiry, and high per-key traffic still warrant memory and latency monitoring. Avoid adding key scans or unbounded loops.
Choose the algorithm for the quota you need
There is no single best Redis rate-limiting algorithm for every API. The choice depends on how exact the boundary must be, how much state you can retain, whether bursts are acceptable, and whether individual request history matters.
Best Value
| Algorithm | State and behavior | Good fit |
|---|---|---|
| Sliding-window log | One sorted-set member per retained admitted request; exact rolling count for the defined interval, with storage proportional to retained events (O(n)). | Strict boundary accuracy, manageable per-key traffic, or a need to inspect event-level history. |
| Sliding-window counter | Current and previous window counters; weights the previous bucket to smooth the boundary. Much less state, but an estimate rather than an exact request log. | General, high-volume quotas where a practical memory/accuracy trade-off is preferable to exact event history. |
| Fixed window | A counter for a discrete interval; simple and inexpensive, but can allow a burst across adjacent window boundaries. | When simplicity matters more than strict rolling-window semantics. |
| Token bucket | Tracks a refillable allowance and configured capacity, allowing controlled bursts while enforcing a sustained rate. | When the API should permit bursts up to a defined capacity. |
The sliding-window counter is often the next option when a sorted-set log’s request-level state is too costly. If implementing it with two Redis keys in a script, arrange for both keys to share a Redis Cluster slot. A hash tag in the key, such as rl:{tenant-42}:current and rl:{tenant-42}:previous, places the tagged keys together. The single-key log above does not have that two-key placement requirement.
Key retention, Cluster placement, and deployment details
- Retention: The script refreshes the key expiry to one window after an admitted event. A denied request adds no event and does not extend the expiry. Sorted-set pruning is still necessary: expiry cleans up inactive keys, while pruning removes expired members from active keys before counting.
- Scope changes: Include a policy/version component in the namespace when changing the meaning of a quota. If a limit is lowered without changing the key, old active members remain relevant and may temporarily keep the subject over the new limit.
- Cluster: A script using one key can be routed by that key. Multi-key scripts require keys in the same slot in Redis Cluster; use a consistent hash tag only for the shared routing dimension, not an entire keyspace-wide tag that would concentrate unrelated traffic on one shard.
- Clock: Using Redis server time inside the script gives all application instances a common time source for the decision. It does not make clocks across failover or deployment environments a substitute for validating the behavior of the actual Redis service.
- Privacy: Event IDs need uniqueness, not personal meaning. Keep member values opaque and avoid storing request payloads or sensitive identifiers in a rate-limit log.
Choose an explicit Redis-outage policy
If Redis times out, is unavailable, or rejects the script, the service cannot reliably determine the shared quota. Decide what each endpoint should do instead of silently changing to a process-local counter, which no longer enforces the same distributed policy.
- Fail closed: Reject or defer requests when quota state cannot be checked. This favors quota protection, but a Redis incident can reduce API availability.
- Fail open: Continue serving requests without enforcement. This favors availability, but quotas can be exceeded during the outage.
- Make the policy operational: Set a bounded Redis call timeout, record limiter errors separately from ordinary denials, and alert on them. Apply different outage choices where the consequences differ; the appropriate choice is a product and reliability decision, not a property of Lua atomicity.
Do not report a Redis failure as an ordinary quota denial: the service does not know that the user is over limit. Preserve that distinction in logs and metrics so operators can tell policy enforcement from infrastructure failure.
Quick wins for a faster PC:
Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




