# Product Requirements Document (PRD)
## aci-cache — Adaptive Cache Invalidation for Redis

**Version:** 1.0  
**Date:** May 9, 2026  
**Author:** Taaha  
**Status:** Draft  

---

## 1. Product Vision

### 1.1 Problem Statement

Every application that uses Redis for caching faces the same unsolved problem: **cache invalidation is either too aggressive or too lazy.**

- **Static TTL** (what 95% of apps use): Keys expire after a fixed time. During high write rates, users see stale data for the entire TTL window. During low write rates, unnecessary cache misses waste database resources.
- **Manual eager invalidation** (what large teams build): Developers hand-write pub/sub invalidation logic per model, per service. It's error-prone, inconsistent, and never adapts to changing workload patterns.
- **No invalidation** (what some teams accept): "It'll expire eventually" — leading to unpredictable stale data.

**There is no existing package — in any language — that provides adaptive, workload-aware cache invalidation as a drop-in Redis wrapper.**

### 1.2 Solution

**aci-cache** is a lightweight library that wraps a developer's existing Redis client and adds an intelligent invalidation layer that automatically adjusts its behavior based on real-time write patterns:

- **Low write rate** → TTL-based expiry (minimal overhead)
- **Medium write rate** → Batched invalidation (balanced efficiency)
- **High write rate** → Eager per-write invalidation (maximum consistency)

The strategy switches happen automatically, transparently, and require **zero application code changes** beyond the initial 2-line integration.

### 1.3 One-Line Pitch

> **"Drop-in adaptive cache invalidation for Redis. Two lines of code. 71% fewer stale reads."**

---

## 2. Target Users

### 2.1 Primary Persona: Backend Developer ("Amir")

- **Role:** Mid-level backend engineer at a 10-50 person startup
- **Stack:** Python (Django/FastAPI) or Node.js (Express/NestJS), Redis for caching, PostgreSQL as primary DB
- **Pain:** Knows caching is important but doesn't have time to build a sophisticated invalidation system. Currently uses `cache.set(key, value, ex=300)` everywhere and accepts stale reads.
- **Wants:** Something that "just works" — install, wrap Redis, forget about it
- **Will NOT do:** Change their database, deploy extra infrastructure, rewrite their caching layer

### 2.2 Secondary Persona: Platform / DevOps Engineer ("Sara")

- **Role:** Senior engineer responsible for infrastructure at a mid-size company
- **Stack:** Kubernetes, multiple microservices, shared Redis cluster
- **Pain:** Different services have different write patterns. Some need aggressive invalidation, some don't. She can't tune TTLs per-service because workloads change throughout the day.
- **Wants:** A library that adapts per-service without manual tuning
- **Will NOT do:** Deploy a separate caching proxy, add operational complexity

### 2.3 Tertiary Persona: Open-Source Developer ("Zain")

- **Role:** Contributor to open-source projects, building side projects
- **Stack:** Varies, but values simple APIs and minimal dependencies
- **Pain:** Wants best-practice caching without the research
- **Wants:** Well-documented, tested, MIT-licensed library with sensible defaults

---

## 3. User Stories

### 3.1 Core Integration

| ID | Story | Priority |
|----|-------|----------|
| US-1 | As a Python developer, I can `pip install aci-cache` and wrap my existing Redis client in 2 lines so that I get adaptive invalidation without changing my app structure | **P0** |
| US-2 | As a Node.js developer, I can `npm install aci-cache` and wrap my existing ioredis/redis client in 2 lines so that I get adaptive invalidation without changing my app structure | **P0** |
| US-3 | As a developer, I can use `cache.get(key)` and `cache.set(key, value)` exactly like my normal Redis client so that there is zero learning curve | **P0** |
| US-4 | As a developer running 3+ instances of my app behind a load balancer, when one instance writes a key, all other instances automatically invalidate that key from their cache | **P0** |

### 3.2 Configuration

| ID | Story | Priority |
|----|-------|----------|
| US-5 | As a developer, I can use aci-cache with zero configuration and get sensible defaults (thresholds: 10/50, TTL: 10s, batch interval: 2.5s) | **P0** |
| US-6 | As a developer, I can customize thresholds, TTL, and batch intervals via constructor options | **P1** |
| US-7 | As a developer, I can provide a config file (`aci.yaml` or `aci.config.json`) so that I don't hardcode configuration | **P2** |

### 3.3 Observability

| ID | Story | Priority |
|----|-------|----------|
| US-8 | As a developer, I can check `cache.strategy` to see the current active strategy at any time | **P0** |
| US-9 | As a developer, I can access `cache.stats` to see write rate, hit/miss ratio, and strategy switch history | **P1** |
| US-10 | As a DevOps engineer, I can export metrics to Prometheus so that I can monitor cache health in Grafana | **P2** |
| US-11 | As a developer, I can run `aci dashboard` (CLI) to see a live terminal dashboard showing real-time stats | **P2** |

### 3.4 Advanced

| ID | Story | Priority |
|----|-------|----------|
| US-12 | As a developer, I can register custom invalidation strategies as plugins | **P2** |
| US-13 | As a developer, I can use aci-cache with Redis Cluster (not just standalone Redis) | **P2** |
| US-14 | As a developer using Python async (asyncio), I can use `await cache.get(key)` with full async support | **P1** |
| US-15 | As a developer, I can namespace my cache keys so that multiple services sharing one Redis don't conflict | **P1** |

---

## 4. Feature Requirements

### 4.1 P0 — Must Have (v0.1 Launch)

#### F1: Redis Client Wrapper
- Wraps standard Redis client (`redis-py` for Python, `ioredis` for Node.js)
- Exposes `get`, `set`, `delete`, `flush` methods
- Passes through all other Redis commands unchanged
- Thread-safe (Python) / Event-loop safe (Node.js)

#### F2: Adaptive Controller
- Background thread/timer that monitors write rate
- Calculates writes-per-second over a configurable sliding window (default: 5 seconds)
- Switches strategy based on configurable thresholds:
  - `write_rate > high_threshold (50)` → Eager
  - `write_rate < low_threshold (10)` → TTL
  - Otherwise → Batched
- Strategy switch is atomic and takes effect immediately

#### F3: Three Built-in Strategies
- **TTL:** Standard Redis TTL expiry, no pub/sub messages. Lowest overhead.
- **Eager:** Publishes invalidation message to pub/sub channel on every `set` call. Lowest staleness.
- **Batched:** Buffers written keys, flushes invalidation message every N seconds (default: 2.5s). Balanced.

#### F4: Cross-Instance Pub/Sub Invalidation
- All instances subscribe to a shared Redis pub/sub channel
- When an invalidation message arrives, the key is deleted from the local instance's cache
- Messages are JSON-encoded with: `{action, keys[], strategy, timestamp, source_instance}`
- Handles pub/sub disconnection gracefully (auto-reconnect with backoff)

#### F5: Sensible Defaults
- Works with zero configuration
- Default thresholds: low=10, high=50
- Default TTL: 10 seconds
- Default batch interval: 2.5 seconds
- Default pub/sub channel: `aci:invalidation`
- Default sliding window: 5 seconds

### 4.2 P1 — Should Have (v0.2)

#### F6: Custom Configuration
- Constructor-based configuration with full type validation
- Environment variable fallbacks (`ACI_HIGH_THRESHOLD`, `ACI_LOW_THRESHOLD`, etc.)
- Config file support (YAML for Python, JSON for Node.js)

#### F7: Statistics API
- `cache.stats` returns: write_rate, read_count, write_count, hit_rate, miss_rate, stale_read_estimate, strategy_history
- Thread-safe / concurrent-safe stat collection
- Optional: periodic stats logging

#### F8: Key Namespacing
- `cache = AdaptiveCache(redis, namespace="user-service")`
- All keys are automatically prefixed: `aci:user-service:actual_key`
- Pub/sub channel is also namespaced: `aci:user-service:invalidation`

#### F9: Async Support (Python)
- `AsyncAdaptiveCache` class wrapping `redis.asyncio`
- Same API as sync version but fully `await`-able
- Uses `asyncio.Task` instead of `threading.Thread` for controller

### 4.3 P2 — Nice to Have (v1.0)

#### F10: Prometheus Metrics Exporter
#### F11: CLI Dashboard (`aci dashboard`)
#### F12: Custom Strategy Plugin Interface
#### F13: Redis Cluster Support
#### F14: Multi-language SDK parity (Go, Java, Ruby)

---

## 5. Competitive Analysis

| Tool | What It Does | Why aci-cache Is Different |
|------|-------------|---------------------------|
| **Redis (raw)** | Static TTL expiry | No adaptive behavior, no cross-instance coordination |
| **django-cacheops** | ORM-level cache invalidation for Django | Django-only, doesn't adapt to workload, ORM-coupled |
| **cacheback** | Async cache refresh for Django | Django-only, refresh-on-miss only, not invalidation |
| **node-cache-manager** | Multi-store cache abstraction | No invalidation strategy, no pub/sub, no adaptation |
| **Apollo Server Cache** | GraphQL-specific caching | GraphQL-only, static policies |
| **Varnish** | HTTP-level caching proxy | Infrastructure-level, not application-level |
| **CDN (Cloudflare, Fastly)** | Edge caching | Different layer entirely, doesn't help with app-level Redis cache |

> [!IMPORTANT]
> **There is no direct competitor.** No existing package in any language provides adaptive, workload-aware cache invalidation as a drop-in Redis wrapper. This is a greenfield opportunity.

---

## 6. Success Metrics

### 6.1 Adoption Metrics (6 months post-launch)

| Metric | Target |
|--------|--------|
| PyPI weekly downloads | 500+ |
| npm weekly downloads | 1,000+ |
| GitHub stars | 500+ |
| Production users (self-reported) | 10+ |

### 6.2 Technical Metrics

| Metric | Target |
|--------|--------|
| Latency overhead per operation | < 1ms |
| Memory overhead per instance | < 5MB |
| Strategy switch time | < 100ms |
| Pub/Sub reconnect time | < 3 seconds |
| Stale read reduction vs static TTL | > 60% |

### 6.3 Developer Experience Metrics

| Metric | Target |
|--------|--------|
| Time from `install` to working integration | < 5 minutes |
| Lines of code to integrate | ≤ 5 |
| Dependencies added | ≤ 2 (Redis client + aci-cache) |

---

## 7. Release Roadmap

```mermaid
gantt
    title aci-cache Release Timeline
    dateFormat  YYYY-MM-DD
    
    section Python (pip)
    v0.1 Core Library           :py1, 2026-05-12, 14d
    v0.2 Stats + Async + Config :py2, after py1, 10d
    v1.0 Production Release     :py3, after py2, 10d
    
    section Node.js (npm)
    v0.1 Core Library           :js1, after py1, 14d
    v0.2 Stats + Config         :js2, after js1, 10d
    v1.0 Production Release     :js3, after js2, 10d
    
    section Marketing
    GitHub + README + Docs Site :m1, 2026-05-12, 7d
    Reddit/HN Launch Post      :m2, after py1, 1d
    npm Launch Post             :m3, after js1, 1d
    Blog Post with Benchmarks   :m4, after js1, 5d
```

### Phase 1: Python v0.1 (2 weeks)
- Core `AdaptiveCache` wrapper
- Controller, 3 strategies, pub/sub
- PyPI package published
- README + quickstart docs

### Phase 2: Node.js v0.1 (2 weeks)
- Core `AdaptiveCache` wrapper (TypeScript)
- Same feature set as Python v0.1
- npm package published

### Phase 3: Both v0.2 (1-2 weeks each)
- Stats API, key namespacing
- Async support (Python), config files
- Expanded test suite

### Phase 4: v1.0 (1-2 weeks each)
- Prometheus exporter, CLI dashboard
- Plugin interface, Redis Cluster support
- Full documentation site

---

## 8. Risks and Mitigations

| Risk | Likelihood | Impact | Mitigation |
|------|-----------|--------|------------|
| Latency overhead deters adoption | Medium | High | Benchmark extensively, keep overhead < 1ms, document results |
| Redis Pub/Sub message loss | Low | Medium | TTL acts as safety net — even if pub/sub fails, keys expire naturally |
| Thread safety bugs (Python) | Medium | High | Comprehensive threading tests, use `threading.Lock` consistently |
| Strategy oscillation at threshold boundaries | Low | Low | Implement hysteresis (±5 dead band) in v0.2 |
| Low adoption | Medium | High | Focus on developer experience, write great docs, post on HN/Reddit with benchmark data |
| Competing solution appears | Low | Medium | Move fast, build community, first-mover advantage |

---

## 9. Out of Scope (Explicit Non-Goals)

- **Not a Redis replacement.** aci-cache wraps Redis, it doesn't replace it.
- **Not a caching framework.** It doesn't tell you what to cache or how to structure keys.
- **Not a database cache layer.** It doesn't auto-cache database queries (unlike django-cacheops).
- **Not a distributed cache itself.** Each instance has its own Redis; aci-cache coordinates invalidation between them.
- **Not multi-store.** It works with Redis only. Not Memcached, not in-memory, not DynamoDB.

---

**END OF PRD**
