Commit 4bcfe15
authored
feat: stream-synchronized local cache (SyncCache) (#39)
* feat: stream-synchronized local cache (SyncCache)
Add SyncCache backend that keeps a local in-memory dict synchronized
across pods via Redis Streams. Reads are purely local (zero network
latency); writes update local dict and broadcast via XADD. A daemon
thread consumes the stream via XREAD BLOCK to apply remote changes.
Suitable for read-heavy, write-light workloads (config, feature flags).
* refactor: non-blocking publish, remove stream replay, fix pod isolation
- Make _publish() non-blocking via ThreadPoolExecutor(max_workers=1)
so writes return instantly without waiting for Redis round-trip
- Remove stream replay on startup (cache fills naturally from usage)
- Add _STORAGE_KEY option for test pod isolation
- Add _flush_publishes() helper for deterministic cross-instance tests
* fix: consumer thread self-healing, poison message skip, health tracking
- _ensure_consumer checks is_alive() and auto-restarts dead threads
instead of silently falling out of sync once _initialized=True
- Advance _last_id before _apply_message so corrupt messages are
skipped, not retried forever (poison message livelock)
- Track _last_read_time; expose consumer_alive, last_read_age_seconds,
last_stream_id, pod_id in info()["sync"] for ops visibility
- Document eventual consistency race conditions on add/incr
* feat: write-through to transport, atomic add/incr/decr via Redis
- All mutations (set, delete, delete_many, touch, clear) write-through
to the transport cache (non-blocking, best-effort) so Redis stays in
sync with local state
- add() routes through transport.add() (Redis SET NX) for cross-pod
atomicity — only one pod succeeds when racing on the same key
- incr()/decr() route through transport.incr() (Redis INCR) for atomic
cross-pod increments — no more lost updates
- _flush_publishes() acts as a fence before atomic ops to ensure pending
write-throughs have reached the transport
- Fix shutdown() order: stop consumer thread before executor
- Add __del__ to prevent thread/executor leaks on GC
- Rename key → made_key throughout to preserve user key for transport
* refactor: SyncCache extends LocMemCache, admin improvements
- SyncCache now inherits from LocMemCache instead of BaseCache,
reusing all local storage logic (OrderedDict, expiry, culling, locking)
- Remove custom make_key — Django's default produces the same format
- Set _cachex_support to "cachex" (full admin support)
- Add _cachex_location for custom admin display
("stream:cache:sync [transport: ...]")
- Cache.location model property checks _cachex_location attribute
before falling back to LOCATION config key
- Cache list view defaults to settings definition order instead of
alphabetical; clicking "Name" column sorts explicitly
- Add SyncCache + dedicated transport to full example
* feat: configurable stream replay on startup (REPLAY option)
New OPTIONS["REPLAY"] (default 0) replays the last N stream entries
via XREVRANGE on startup to warm the local cache. A restarting pod
no longer starts with an empty cache — it picks up recent state from
the stream before the consumer thread begins live consumption.
* refactor: drop add/incr/decr support, remove write-through
add(), incr(), and decr() now raise NotSupportedError — their atomic
semantics (check-and-set, atomic increment) are incompatible with
eventual consistency. Users needing these should use the transport
cache directly.
This removes all write-through infrastructure (no more Redis writes
on set/delete/touch/clear) making SyncCache a pure local-first cache
with stream-only synchronization. get_or_set() is overridden to use
set() instead of add().
The remaining API (get, set, get_many, set_many, delete, delete_many,
touch, clear, get_or_set) is fully eventually consistent and covers
the needs of packages like django-cachalot.
* fix: stale comment, recreate executor after shutdown reuse1 parent 3e03089 commit 4bcfe15
File tree
8 files changed
+1401
-11
lines changed- django_cachex
- admin
- cache
- examples/full
- full
- tests/cache
8 files changed
+1401
-11
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
63 | | - | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
64 | 72 | | |
65 | 73 | | |
66 | 74 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
92 | 92 | | |
93 | 93 | | |
94 | 94 | | |
95 | | - | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
96 | 103 | | |
97 | 104 | | |
98 | 105 | | |
99 | | - | |
| 106 | + | |
100 | 107 | | |
101 | 108 | | |
102 | 109 | | |
| |||
164 | 171 | | |
165 | 172 | | |
166 | 173 | | |
167 | | - | |
| 174 | + | |
168 | 175 | | |
169 | 176 | | |
170 | 177 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
18 | 18 | | |
19 | 19 | | |
20 | 20 | | |
| 21 | + | |
21 | 22 | | |
22 | 23 | | |
23 | 24 | | |
| |||
27 | 28 | | |
28 | 29 | | |
29 | 30 | | |
| 31 | + | |
30 | 32 | | |
31 | 33 | | |
32 | 34 | | |
| |||
0 commit comments