distkit
Counters

LaxCounter

Buffered, eventually consistent counting for high-throughput paths.

LaxCounter trades immediate consistency for speed. Increments accumulate in a local DashMap and are flushed to Redis in batched pipelines every allowed_lag (default ~20 ms). The hot path never blocks on the network, so writes are effectively free.

Use it for analytics, telemetry, and high-frequency metrics where a few milliseconds of cross-process lag is acceptable.

Construct

use distkit::{DistkitRedisKey, counter::{CounterOptions, LaxCounter, CounterTrait}};

let prefix = DistkitRedisKey::try_from("my_app".to_string())?;
let counter = LaxCounter::new(CounterOptions::new(prefix, conn));

Like the strict counter, new returns an Arc. Constructing it spawns a background flush task.

Reads see your own writes

let key = DistkitRedisKey::try_from("impressions".to_string())?;

counter.inc(&key, 1).await?;        // local atomic add - sub-microsecond
let val = counter.get(&key).await?; // local view: remote_total + pending_delta

get returns remote_total + pending_delta, so within the same process a read always includes increments you haven't flushed yet. Across processes, another instance won't see your increment until the next flush lands.

Tuning the lag

allowed_lag on CounterOptions controls the flush interval. Lower it for tighter consistency, raise it to batch more aggressively:

use std::time::Duration;

let mut options = CounterOptions::new(prefix, conn);
options.allowed_lag = Duration::from_millis(50);
let counter = LaxCounter::new(options);

The background task cleans up after itself

The flush task holds a Weak reference to the counter. When the last Arc<LaxCounter> is dropped, the task notices and stops - there is nothing to shut down manually.

set, del, clear stay immediate

set, del, and clear are not buffered; they apply right away (after the pending delta is accounted for). Only inc/dec ride the buffer.