Catalog PyPI
PACKAGE EXPLORERView in registry

aci-cache

PyPI

Adaptive cache invalidation for Redis. Drop-in wrapper, zero config.

adaptivecacheinvalidationpubsubredis
Versions collected1
Direct dependencies5From release 0.1.1
Last observed20 Oct, 18:13 UTC

README

Version 0.1.1 documentation

aci-cache

CI PyPI npm License: MIT

Drop-in Redis cache wrapper that automatically adapts its invalidation strategy to your write traffic.

Most caching libraries make you choose a strategy upfront and stick with it. aci-cache monitors your write rate in real time and switches between strategies automatically — so you get low overhead at low traffic and strong consistency at high traffic, with no manual tuning.


How it works

The library tracks writes per second in a sliding window. Based on configurable thresholds, it picks one of three strategies:

Write RateStrategyBehaviour
< 10 writes/secTTLRely on Redis key expiry. Zero overhead.
10–50 writes/secBatchedBuffer keys, flush a single Pub/Sub message every 2.5s.
> 50 writes/secEagerPublish an invalidation message on every write.

In a multi-instance deployment, one instance runs as the controller (monitors rate, decides strategy) and broadcasts decisions to all others via a Redis Pub/Sub channel. All instances subscribe and apply the strategy change locally — keeping caches consistent across your entire fleet.


Installation

Python

pip install aci-cache

Node.js

npm install aci-cache

Quick start

Python

import redis
from aci_cache import AdaptiveCache

r = redis.Redis(host="localhost", port=6379)
cache = AdaptiveCache(r)

cache.set("user:42", '{"name": "Alice"}')
data = cache.get("user:42")  # '{"name": "Alice"}'

print(cache.strategy)        # "ttl" | "batched" | "eager"
print(cache.stats.total_writes)

Async (asyncio)

import redis.asyncio as aioredis
from aci_cache import AsyncAdaptiveCache

async def main():
    r = aioredis.Redis()
    async with AsyncAdaptiveCache(r) as cache:
        await cache.set("session:99", "active")
        print(await cache.get("session:99"))

Node.js (TypeScript)

import Redis from "ioredis";
import { AdaptiveCache } from "aci-cache";

const cache = new AdaptiveCache({ redis: new Redis() });
await cache.start();

await cache.set("user:42", JSON.stringify({ name: "Alice" }));
const data = await cache.get("user:42");

console.log(cache.strategy);           // "ttl" | "batched" | "eager"
console.log(cache.statsSnapshot.totalWrites);

await cache.stop();

Multi-instance setup

Run one instance as the controller. All others follow.

Python

# Instance 1 — controller (monitors write rate, publishes decisions)
controller_cache = AdaptiveCache(redis.Redis(), is_controller=True)

# Instance 2, 3, … — followers
follower_cache = AdaptiveCache(redis.Redis(), is_controller=False)

Node.js

// Instance 1
const cache = new AdaptiveCache({ redis, isController: true });

// Instances 2+
const cache = new AdaptiveCache({ redis, isController: false });

Configuration

All parameters have defaults. Override what you need.

ParameterDefaultDescription
high_threshold / highThreshold50Writes/sec that triggers eager mode
low_threshold / lowThreshold10Writes/sec that drops back to TTL mode
default_ttl / defaultTtl10Key TTL in seconds
batch_interval / batchInterval2.5Seconds between batch flushes
controller_interval / controllerInterval3Seconds between controller rate checks
sliding_window / slidingWindow5Write rate measurement window in seconds
namespaceNone / undefinedKey/channel prefix for multi-service Redis
is_controller / isControllerTrue / trueWhether this instance runs the controller

Python example

cache = AdaptiveCache(
    redis.Redis(),
    high_threshold=100,
    low_threshold=20,
    default_ttl=30,
    namespace="user-service",
    is_controller=True,
)

Node.js example

const cache = new AdaptiveCache({
    redis,
    highThreshold: 100,
    lowThreshold: 20,
    defaultTtl: 30,
    namespace: "user-service",
    isController: true,
});

Observability

stats = cache.stats
print(stats.total_reads)        # int
print(stats.cache_hits)         # int
print(stats.cache_misses)       # int
print(stats.total_writes)       # int
print(stats.current_strategy)   # "ttl" | "batched" | "eager"
print(stats.write_rate)         # float (writes/sec)

# React to strategy switches
cache.on_switch(lambda from_s, to_s: print(f"{from_s} → {to_s}"))

Repository layout

aci-cache/
├── src/aci_cache/          # Python package source
│   ├── cache.py            # AdaptiveCache (sync)
│   ├── async_cache.py      # AsyncAdaptiveCache (asyncio)
│   ├── config.py           # CacheConfig
│   ├── controller.py       # Background rate monitor
│   ├── subscriber.py       # Pub/Sub listener
│   ├── tracker.py          # Sliding-window write rate tracker
│   ├── stats.py            # Stats collector
│   └── strategies/         # TTL, Eager, Batched strategy classes
├── tests/                  # Python test suite (99 tests)
├── node/                   # Node.js/TypeScript package
│   ├── src/                # TypeScript source (mirrors Python)
│   └── tests/              # TypeScript test suite (63 tests)
├── pyproject.toml          # Python package config
└── .github/workflows/      # CI (test) + CD (publish on tag)

Development

Python

python -m venv venv && source venv/bin/activate
pip install -e ".[dev]" pytest-asyncio
pytest tests/ -v

Node.js

cd node
npm install
npm test
npm run build

Releasing a new version

  1. Bump version in pyproject.toml and node/package.json
  2. Commit and tag:
git commit -am "feat: v0.2.0"
git tag v0.2.0
git push origin main --tags

GitHub Actions will run all tests, publish to PyPI and npm, and create a GitHub Release automatically.


License

MIT — see LICENSE.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.1.18 May 20265

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 18:13 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:e9e8ab8366ba6c45b56977e5ff1392b4e95acff1b200af1229c4ffda403fc250