gproxy-cache
English | 简体中文
Independent TTL state, atomic coordination and invalidation notifications. No Store entities, routing policies, database connections, or implicit memory fallback.
| Feature | Backend |
|---|---|
memory (default) |
Process-local shared state; native and WASM |
redis |
Native async Redis/Valkey with TCP/TLS and independent subscribers |
| No features | Public contracts only; implement Cache for another backend |
use ;
use Duration;
# async
With redis, construct RedisCache::connect(url, RedisOptions::new("deployment")).
Clones share a multiplexed command connection; independently constructed clients
with the same Redis database and namespace share state. Data and Pub/Sub channels
include an encoded namespace and database number (Redis Pub/Sub itself ignores
SELECT databases). Keys and topics are encoded without delimiter ambiguity. The
adapter targets a single Redis endpoint; it does not implement Sentinel discovery
or cluster redirection. Redis requires permissions for scripts, hashes, sorted
sets, expiry, TIME, and Pub/Sub. Real integration validation uses Redis 8; Valkey
and TLS paths have not been exercised against live servers in this change.
Operations
| Operation | Semantics |
|---|---|
get / put / delete |
Opaque bytes, mandatory positive TTL, fresh version on every put |
compare_exchange |
Expected version or absence; replace with fresh version/TTL, or delete |
counter / increment |
Exact nonnegative integers through i64::MAX; atomic ceiling check and increment |
decrement |
Match counter generation before subtracting; underflow fails without mutation |
acquire_permit |
At most the requested number of live holders; independent TTL per holder |
acquire_lease |
Same permit domain, with limit 1; suitable for refresh coordination |
renew_permit / release_permit |
Only the matching, still-live owner can act |
publish / subscribe |
Application-defined byte payloads; lossy invalidation hints |
KV, counters, permits and topics have separate domains, so the same logical key may exist in each. Operations are atomic per key, not across keys. Empty values are valid; empty/oversized keys or values and invalid TTLs return errors. TTLs round up to milliseconds, and use a shared representability bound (about 142,000 years). Memory uses a monotonic clock; Redis expiration/permits use server time.
Counters have a fixed window: only creation establishes TTL. Later increments
and decrements preserve it, including at zero. A newly created window has a fresh
generation so late decrements cannot touch it. Decrement is not an idempotent
receipt: a caller must release a charge exactly once and reconcile ambiguous
responses. Financial settlement and its durable idempotency ledger belong in Store.
Redis Lua compares decimal strings and obtains final values with HGET, so values
above 2^53 do not pass through floating-point arithmetic.
Permits require all callers for one resource to use the same limit. No automatic renewal or release-on-drop occurs: expired holders cannot revive their permit, and a short holder cannot shorten another holder's expiry. The caller must stop protected work when ownership is lost. Random owner tokens prevent stale renewal or release; they are not monotonic fencing tokens and cannot stop an already-running remote side effect. Pair durable mutations with Store version/CAS checks. Redis failover or eviction can lose coordination state: use an appropriate no-eviction and durability configuration; this is not a consensus lock implementation.
A timeout, disconnect or cancellation after dispatch may have committed. The adapter never blindly retries a failed mutation or switches to local memory. redis-rs reconnects command connections for subsequent calls; a NOSCRIPT reply can load the script because the rejected attempt did not execute it. Check state or use durable operation receipts before retrying non-idempotent work.
Notifications and multiple instances
# use ;
# async
subscribe returns after registration. Each new subscription first yields
ResyncRequired; an application must load authoritative data after subscribing.
Slow consumers receive ResyncRequired when the bounded delivery queue overruns.
Redis disconnect closes the subscription explicitly; recreate it and reload.
Dropping a Redis subscription aborts its reader task. recv is cancellation-safe.
Memory subscribers are notified across clones of the same cache, not separate
processes or independently constructed caches. WASM memory is isolate-local.
There is no replay, acknowledgement, exactly-once guarantee or atomicity between Store commits and publish. Publish with zero subscribers succeeds. A process may crash after a database commit but before publishing, so consumers must periodically compare the durable configuration revision too. Store must update that revision in the same transaction as configuration, and load the rows/revision consistently. Core/manage owns that integration, revision schema, reload compilation and event payloads; they are not implemented by this crate. See design/cache.md. Do not publish decrypted credentials or bearer tokens.
Memory capacity and limits
MemoryOptions defaults to 100,000 live keys and 256 active topics. Limits
defaults to 1,024 key bytes, 1 MiB per value/message, 10,000 permits per key, and
256 messages in each subscription delivery queue. These are count/per-item bounds,
not an allocator-level total RAM budget; configure them for the workload. Redis
also validates per-operation limits, while server-side memory capacity is managed
by Redis. Limit configuration must agree across clients sharing a namespace.
When full, Memory reclaims expired keys, then sheds live values and then live
counters, soonest to expire first, an eighth of the capacity at a time. It never
evicts a permit: only a cache full of live permits returns Capacity. Expiry is enforced on access;
purge_expired() optionally reclaims cold expired entries. There is no janitor
thread or Tokio runtime requirement for Memory operations. Tokio sync is used
only for async notifications; the application drives polling.
Validation
GPROXY_CACHE_REDIS_URL=redis://127.0.0.1:6379
Redis tests are explicitly ignored without opt-in. Supply a test server; the fault-proxy test needs anonymous TCP Redis; the database-isolation test needs at least two databases. Tests use unique namespaces and never FLUSHDB/FLUSHALL or kill server clients. The network-failure test closes only its own local relay. See VALIDATION.md for executed checks.