All Products
Search
Document Center

Tair (Redis® OSS-Compatible):Implement bounded counters by using TairString

Last Updated:Mar 28, 2026

TairString's EXINCRBY command enforces hard upper and lower bounds atomically — no Lua scripts required for simple cases. This makes it a direct fit for flash sale inventory tracking and rate limiting, where exceeding a threshold has real consequences.

How it works

EXINCRBY and EXINCRBYFLOAT extend the native Redis INCRBY and INCRBYFLOAT commands with additional options, including MIN and MAX:

OptionDescription
MINRejects the operation if the result would fall below this value
MAXRejects the operation if the result would exceed this value
EXSets expiry in seconds
NXSets the value only if the key does not exist
VERVersion check for optimistic locking
KEEPTTLPreserves the existing TTL

When the bound is violated, EXINCRBY returns "increment or decrement would overflow" instead of updating the key. Handle this response as a signal to stop accepting the request — no secondary check needed.

Each EXINCRBY call is atomic. There is no window between the read and write where two concurrent requests could both see the same remaining value.

TairString supports all native Redis String features except bit operations.

For the full command reference, see TairString commands.

When to use bounded counters

Use EXINCRBY with MIN/MAX when you need a hard threshold that must never be crossed:

ScenarioApproachWhy bounded counters fit
Flash sale inventoryDecrement with MIN 0Stock can never go below zero; no overselling possible
Concurrent request capIncrement with MAX NActive requests can never exceed N
Login attempt limitingIncrement with MAX N + EXBound resets after the window expires
Password change rate limitingIncrement with MAX N + EXPrevents credential stuffing at the counter level

Compared to native Redis `INCRBY` with Lua: Native strings require a Lua script to read, compare, and conditionally update in one atomic step. EXINCRBY handles this in a single command, reducing code complexity and round-trips. Use native strings with Lua if you need more complex conditional logic that goes beyond a fixed min/max threshold.

Flash sale inventory control

To prevent overselling, set the initial stock count as the TairString value and decrement with MIN 0. Once stock reaches zero, EXINCRBY rejects further decrements automatically.

if(EXINCRBY(key_iphone, -1, MIN:0) == "would overflow")
    run_out();

Rate limiting and throttling

Use MAX to cap the counter at an upper bound. The following example limits concurrent requests to 1,000:

if(EXINCRBY(rate_limitor, 1, MAX:1000) == "would overflow")
    traffic_control();

This pattern applies to any scenario with a numeric threshold: queries per second (QPS), concurrent connections, login attempts, or password change frequency.

Java example: concurrent request limiter

The following tryAcquire method is thread-safe. It increments the counter within an expiry window and returns false once the upper bound is reached.

/**
 * Attempts to acquire a slot within the rate limit window.
 *
 * @param key        The rate limit key
 * @param upperBound The maximum allowed count
 * @param interval   The time window in seconds
 * @return true if the slot was acquired; false if the limit is reached
 */
public static boolean tryAcquire(String key, int upperBound, int interval) {
    try (Jedis jedis = jedisPool.getResource()) {
        jedis.eval("if redis.call('exists', KEYS[1]) == 1 "
                + "then return redis.call('EXINCRBY', KEYS[1], '1', 'MAX', ARGV[1], 'KEEPTTL') "
                + "else return redis.call('EXSET', KEYS[1], 0, 'EX', ARGV[2]) end",
            Arrays.asList(key), Arrays.asList(String.valueOf(upperBound), String.valueOf(interval)));
        return true;
    } catch (Exception e) {
        if (e.getMessage().contains("increment or decrement would overflow")) {
            return false;
        }
        e.printStackTrace();
    }
    return false;
}

The Lua script combines two operations atomically:

  • If the key exists: increment with EXINCRBY, applying MAX and preserving the existing TTL with KEEPTTL.

  • If the key does not exist: initialize it to 0 with EXSET and set the expiry window with EX.

Handling failures in production: The tryAcquire method catches overflow as an expected control-flow signal. For other exceptions — such as a Redis connection failure — the method currently logs the error and returns false, which fails closed (blocks requests). Decide whether your application should fail open (allow requests through) or fail closed (block requests) when TairString is unavailable, and update the exception handler accordingly.

What's next

  • TairString commands — full reference for EXINCRBY, EXINCRBYFLOAT, EXSET, and all supported options