Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk-feature-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ The contract a storage backend must satisfy per SDK (bytes in / bytes out; seria
| Backpressure | ✅ | ✅ Semaphore + bounded queue, decision recorded (LAB-729)¹³ | ⚠️ Refresh cap only — deliberate, decision recorded (LAB-519) | ❌ |
| Distributed locking | ✅ Redis + SaaS backends | ✅ Redis + SaaS + Workers (`LockableBackend`; LAB-426) | ✅ Redis + SaaS backends; wired into `wrap()` cold miss (opt-in `stampede.distributedLock`, LAB-519) | ❌ |
| L1/L2 dual-layer cache | ✅ | ✅ moka (native) / `l1` feature | ✅ | ❌ |
| Cache stampede prevention | ✅ Async decorators via backend lock; sync path none by design | ✅ Cold-miss single-flight wired into every `#[cachekit]` expansion — per-key in-process gate plus a distributed fill lock on lock-capable backends (the `single_flight` call in `expand`'s generated body, `cachekit-macros/src/lib.rs`; `SingleFlight::acquire` in `flight.rs`; LAB-518)¹³ | ✅ Cold-miss single-flight + SWR version tokens (LAB-519) | ❌ |
| Cache stampede prevention | ⚠️ In-process single-flight from `cachekit` 0.23.0+ ([cachekit-py#547](https://github.com/cachekit-io/cachekit-py/pull/547)): concurrent cold misses on one key run the function once, per event loop on the async path (with a backend, `@cache(backend=None)` or `@cache.local()`) and per process on the sync path (`@cache(backend=None)` or `@cache.local()`); an async herd with a backend makes one call's backend requests, so on a lockable backend it takes the distributed lock once. That backed sharing holds only for a result that serializes: a joined caller that gets no envelope it can decode takes its own trip, so a herd whose result the serializer rejects runs the function once per caller, and in multi-tenant encryption a caller whose tenant cannot be extracted shares nothing. **Not the sync path with a backend:** it takes no lock and does not coalesce yet, so every concurrent miss runs the function. Verified 2026-10-24 on the published 0.23.0 and 0.24.1 wheels: each tag's `tests/unit/test_single_flight.py` passes against its own wheel, and a cold herd of 20 calls on one key ran the function once in each covered mode, 20 times (40 GET, 20 lock) on the async path with a backend when the result does not serialize, and 20 times on the sync path with a backend (0.22.0: 20 times in every mode) | ✅ Cold-miss single-flight wired into every `#[cachekit]` expansion — per-key in-process gate plus a distributed fill lock on lock-capable backends (the `single_flight` call in `expand`'s generated body, `cachekit-macros/src/lib.rs`; `SingleFlight::acquire` in `flight.rs`; LAB-518)¹³ | ✅ Cold-miss single-flight + SWR version tokens (LAB-519) | ❌ |
| Cross-instance L1 invalidation (pub/sub) | ✅ 0.22.0+ — opt-in listener (`CACHEKIT_INVALIDATION_LISTENER_ENABLED`) over a Redis pub/sub channel, publish automatic on the tenant-scoped Redis backend ([cachekit-py#470](https://github.com/cachekit-io/cachekit-py/pull/470), [#471](https://github.com/cachekit-io/cachekit-py/pull/471), [#472](https://github.com/cachekit-io/cachekit-py/pull/472); wheel inspected 2026-10-05)⁹ | ❌ | ✅ Opt-in `invalidation` config over a Redis pub/sub channel (`invalidation/redis-channel.ts`) | ❌ |
| TTL management | ✅ Redis + SaaS + File; Memcached refresh-only (see note) | ✅ Redis + SaaS + File + Workers (`TtlInspectable`); Memcached refresh-only (LAB-429/426) | ✅ Redis + SaaS + File (`TTLBackend`); Memcached refresh-only (LAB-430) | ❌ |
| Stale-while-revalidate (client L1) | ⚠️ L1-only mode (`backend=None`) only, and only with a TTL: the `production` and `dev` presets supply one by default from `cachekit` 0.20.0, a bare `@cache(backend=None)` still needs `ttl=`¹⁰ | ✅ Refresh-ahead within the L1 TTL: an entry past `swr_threshold_ratio` of its TTL is still served while one single-flight background refresh runs, and is never served after the TTL (LAB-728)¹⁰ ¹³ | ✅ `getWithSwr` — refresh-ahead within the L1 TTL: version tokens + background refresh, `maxConcurrentRefreshes` cap, never served after the TTL¹⁰; on Workers requires a bound `ExecutionContext` (see [Cache Backends](#cache-backends) note ¹) | ❌ |
Expand Down
Loading