distkit
Counters

Conditional & batch operations

Atomic compare-and-write and ordered batch operations on counters.

Both counters support conditional writes and batches. All of them are atomic on the Redis side, so there is no read-modify-write window for another instance to slip through.

Conditional writes

inc_if and set_if apply the change only when the current value satisfies a CounterComparator. They return (new, old); when the condition fails, new == old and nothing was written.

use distkit::CounterComparator;

counter.set(&key, 10).await?;

// 10 == 10 -> applies. Returns (15, 10).
assert_eq!(counter.inc_if(&key, CounterComparator::Eq(10), 5).await?, (15, 10));

// 15 is not < 10 -> no-op. Returns (15, 15).
assert_eq!(counter.inc_if(&key, CounterComparator::Lt(10), 5).await?, (15, 15));

// Nil always matches. Returns (20, 15).
assert_eq!(counter.inc_if(&key, CounterComparator::Nil, 5).await?, (20, 15));

Batch operations

Batches act on many keys in a single call and preserve input order in the result.

// Read several keys at once.
let snapshot = counter.get_all(&[&k1, &k2, &k3]).await?;
// -> Vec<(&DistkitRedisKey, i64)>

// Increment several at once.
let updated = counter.inc_all(&[(&k1, 1), (&k2, 5)]).await?;
// -> Vec<(&DistkitRedisKey, i64)>  (key, new)

Conditional batches

inc_all_if and set_all_if combine the two: each entry carries its own comparator, and each result is (key, new, old).

counter.set(&k1, 10).await?;

let results = counter
    .inc_all_if(&[
        (&k1, CounterComparator::Eq(10), 5),
        (&k2, CounterComparator::Nil, 2),
    ])
    .await?;

assert_eq!(results, vec![(&k1, 15, 10), (&k2, 2, 0)]);

Here k1 matched (10 == 10) and became 15; k2 started at 0 and Nil always applies, so it became 2. As with single conditional writes, an entry whose comparison fails comes back with new == old.