Catalog PyPI
Back to packageRELEASE DETAILS

0.1.1

PyPI

aci-cache

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

adaptivecacheinvalidationpubsubredis
Published8 May 2026
Direct dependencies5
Transitive dependenciesNot calculated

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.

Declared dependencies 5

Native requirements
5 direct targetsNot calculated transitive count
Package / targetRequirementRelationship
redis
Environment
{
  "extras": "",
  "marker": null,
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
redis>=4.0runtime
fakeredis
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
fakeredis>=2.0; extra == "dev"runtime
mypy
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
mypy>=1.0; extra == "dev"runtime
pytest
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
pytest>=7.0; extra == "dev"runtime
ruff
Environment
{
  "extras": "",
  "marker": "extra == \"dev\"",
  "requires_python": ">=3.9",
  "evaluation": "NOT_EVALUATED"
}
ruff>=0.1.0; extra == "dev"runtime

Distribution files

2 collected
aci_cache-0.1.1-py3-none-any.whl

bdist_wheel · 22.1 KB · Python py3

Source file
aci_cache-0.1.1.tar.gz

sdist · 20.6 KB · Python source

Source file

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_version:a5e38b44eb4b3723f387a51d4975dc3539cc0a199946f5963889e01dcd881ea9

Other collected versions

View all collected versions →