**[Home](README.md)** › **Getting Started** # Getting Started with cachekit > **Smart caching that just works - from simple to advanced** --- ## Table of Contents - [Quick Start](#quick-start) - [Progressive Disclosure](#progressive-disclosure-choose-your-level) - [Installation](#installation) - [Choose Your Backend](#choose-your-backend) - [What Makes cachekit Different](#what-makes-cachekit-different) - [Common Pitfalls](#common-pitfalls-to-avoid) - [Configuration](#configuration) - [Testing Your Setup](#testing-your-setup) - [Troubleshooting](#troubleshooting) --- ## Quick Start CacheKit provides a unified caching API with pluggable backends. You write `@cache` once; the backend (Redis, CacheKit Cloud, Memcached, or File / L1-only) is selected from your environment. See [Choose Your Backend](#choose-your-backend) for the full menu. With nothing configured, `@cache` caches in L1 only, in process, like `lru_cache`. It makes no connection attempt and logs no warning: ```python from cachekit import cache @cache # nothing configured: L1-only, per process def expensive_function(): return do_work() ``` That cache is not shared between processes, holds values by reference, and is chosen at the function's first call ([details](backends/README.md#4-nothing-configured-l1-only)). To share it across processes, select a backend from the environment before the first call. For a local Redis: ```bash # 1. Run Redis docker run -p 6379:6379 redis # 2. Set Redis URL export REDIS_URL="redis://localhost:6379" ``` The same `@cache` now caches in L1 and Redis. > [!TIP] > **What you get out of the box:** > - Full control over infrastructure > - On-premise deployment > - Use existing Redis clusters > - No external dependencies --- ## Progressive Disclosure: Choose Your Level ### Level 1: "Just Make It Work" > [!NOTE] > **90% of users can stop here.** This covers most use cases. ```python from cachekit import cache @cache # Default: 1 hour TTL (3600s) def expensive_function(): return do_expensive_computation() # First call: computes and caches result = expensive_function() # Second call: lightning fast from cache result = expensive_function() ``` **Prerequisites**: Redis running + `REDIS_URL` environment variable set **Defaults**: 1 hour TTL, no namespace, compression enabled --- ### Level 2: Basic Customization ```python # Short-lived: API responses, session data @cache(ttl=300) # 5 minutes def get_exchange_rates(): return fetch_live_rates() # Long-lived: reference data, configs @cache(ttl=86400) # 24 hours def get_country_list(): return fetch_countries() # Namespaced: isolate cache domains, enable bulk invalidation @cache(ttl=3600, namespace="users") def get_user_profile(user_id): return build_profile(user_id) ``` > [!TIP] > **Namespace benefits**: Logical grouping in Redis keys, easier debugging with `redis-cli KEYS "users:*"`, > bulk operations via pattern matching (backend-side only — other processes' L1 entries are untouched and > expire via TTL) **New powers unlocked**: TTL tuning for data freshness, namespace isolation --- ### Level 3: Serializer Selection > [!IMPORTANT] > cachekit uses **StandardSerializer (MessagePack)** by default for multi-language cache compatibility. ```python # Default: StandardSerializer - language-agnostic @cache(ttl=3600) def compute_results(): return {"data": [1, 2, 3], "timestamp": datetime.now()} # For Python-only with NumPy/pandas: AutoSerializer @cache(ttl=1800, serializer="auto") def analyze_data(): import numpy as np return np.array([1, 2, 3, 4, 5]) # For JSON APIs: OrjsonSerializer (JSON-native) — requires: pip install 'cachekit[json]' @cache(ttl=900, serializer="orjson") def get_api_response(): return {"status": "ok", "data": "response"} # For DataFrames: ArrowSerializer (the default serializer refuses them) @cache(ttl=7200, serializer="arrow") def get_large_dataset(): import pandas as pd return pd.read_csv("large_file.csv") ```
Serializer Decision Guide | Serializer | Language Support | Best For | |:-----------|:----------------:|:---------| | **StandardSerializer** | Python, PHP, JS, Java, Go | Multi-language compatible, general-purpose | | **AutoSerializer** | Python only | NumPy/pandas/UUID/set support | | **OrjsonSerializer** | Python, PHP, JS, Java, Go | JSON-optimized, fast JSON workloads | | **ArrowSerializer** | Python, JS, Java, Go | DataFrame-optimized (NOT PHP compatible) |
--- ### Level 4: Advanced Control ```python from cachekit import cache # Namespace for cache organization and TTL refresh @cache( ttl=7200, namespace="tenant_data", refresh_ttl_on_get=True ) def get_tenant_data(tenant_id, request): return process_tenant_request(tenant_id, request) # TTL refresh for long-lived cache entries @cache( ttl=86400, refresh_ttl_on_get=True, ttl_refresh_threshold=0.3 ) def get_user(user_id): return fetch_user(user_id) ``` **New powers unlocked**: TTL refresh, threshold control, namespace organization --- ### Level 5: Production Reliability Features ```python from cachekit import cache from cachekit.config.decorator import CircuitBreakerConfig @cache( ttl=3600, namespace="critical_service", refresh_ttl_on_get=True, ttl_refresh_threshold=0.3, circuit_breaker=CircuitBreakerConfig(enabled=True) ) def critical_business_function(request_id: str): """Production-ready function with reliability features.""" return process_business_logic(request_id) ``` > [!TIP] > **Or use intent-based presets** for common configurations: > ```python notest > @cache.production # All reliability features enabled > @cache.secure # Encryption + reliability > @cache.minimal # Maximum speed, minimal overhead > ``` **New powers unlocked**: Circuit breaker, backpressure control, comprehensive monitoring --- ## Installation ### Quick Install ```bash # With pip pip install cachekit # With uv (recommended - faster) uv add cachekit # Start Redis if you haven't docker run -p 6379:6379 redis ``` ### Environment Setup ```bash # Redis (12-factor; with no selector set at all, plaintext decorators cache L1-only) export REDIS_URL="redis://localhost:6379" # Optional: explicit Redis configuration export CACHEKIT_REDIS_URL="redis://localhost:6379" export CACHEKIT_CONNECTION_POOL_SIZE=50 # CacheKit Cloud (closed beta). CACHEKIT_API_KEY is also a backend selector: set it # INSTEAD of CACHEKIT_REDIS_URL, never both (two selectors leave caches that rely on env # auto-detection uncached). # export CACHEKIT_API_KEY=ck_your_api_key ``` --- ## Choose Your Backend Four paths depending on your situation: | Backend | When to use | Setup effort | |:--------|:------------|:-------------| | **Redis** (recommended) | Production, existing infra, full control | Medium — run Redis yourself | | **CacheKit Cloud** (closed beta) | Skip Redis ops, managed edge caching | Low — API key only | | **Memcached** (optional) | High-throughput, existing Memcached infra | Low — `pip install cachekit[memcached]` | | **File / L1-only** | Local dev, tests, no external deps | None | ### Redis (Recommended) Self-hosted, full control. The recommended path for production; the default, with nothing configured, is L1 only. Everything in this guide applies. ```bash docker run -p 6379:6379 redis export REDIS_URL="redis://localhost:6379" ``` ```python from cachekit import cache @cache(ttl=3600) def get_user(user_id: int): return fetch_from_db(user_id) ``` ### CacheKit Cloud > [!NOTE] > **CacheKit Cloud is in closed beta: sign-up is by waitlist at [dash.cachekit.io](https://dash.cachekit.io)** Managed edge caching — no Redis to run. Set your API key and switch the decorator to `@cache.io()`. ```bash # CacheKit Cloud (closed beta) export CACHEKIT_API_KEY=ck_your_api_key ``` ```python notest from cachekit import cache @cache.io(ttl=3600) # CacheKit Cloud is in closed beta: join the waitlist at https://dash.cachekit.io def get_user(user_id: int): return fetch_from_db(user_id) ``` Everything else — TTL, namespaces, serializers — works the same as with Redis. Swap `@cache` for `@cache.io()` and you're done. ### Memcached (Optional) High-throughput in-memory caching with consistent hashing. No persistence — data is lost on restart. ```bash pip install cachekit[memcached] # Default: connects to 127.0.0.1:11211 ``` ```python notest from cachekit import cache from cachekit.backends.memcached import MemcachedBackend backend = MemcachedBackend() @cache(backend=backend, ttl=3600) def get_user(user_id: int): return fetch_from_db(user_id) ``` Everything else — TTL, namespaces, serializers — works the same as with Redis. See the [Backend Guide](backends/README.md) for multi-server configuration. ### File / L1-Only (Dev and Testing) No external dependencies. Pure in-process memory cache. Not suitable for multi-pod or production. ```python from cachekit import cache @cache(ttl=60, backend=None) # L1-only: in-memory, no Redis needed def get_user(user_id: int): return fetch_from_db(user_id) ``` --- ## What Makes cachekit Different ### It Never Breaks Your App ```python @cache def critical_function(): return important_data() # Redis is down? No problem! # Function executes normally. Down from the start: nothing is cached until it is back. # Lost later: results still go to L1 (in-process). result = critical_function() # Works even if Redis is offline ``` ### Multi-Pod Safe by Default - Distributed locking prevents cache stampedes - All pods see consistent data - No local memory cache surprises ### Optimizes What Actually Matters | Optimization | Impact | Focus | |:-------------|:------:|:------| | Connection pooling | No published timing | cachekit focuses here | | Network calls | One round trip per L2 operation | Accepted tradeoff | | Serialization | No published timing; the [instruction budgets](performance.md#instruction-budgets) count its instructions | Instruction budget gated in CI | | SIMD hashing | No published timing | Removed | --- ## Common Pitfalls to Avoid > [!WARNING] > **Don't Fight the Network** > > With a networked L2 backend, every L1 miss pays a network round trip, which depends on your network and region ([not measured here](performance.md#not-measured-here)). The default serializer is already optimized. ```python @cache # Default serializer is perfect - optimize elsewhere def fetch_data(key): return important_data() ``` > [!CAUTION] > **Don't Add Local Caching** > > This breaks multi-pod consistency: > ```python > # WRONG: Breaks multi-pod consistency > local_cache = {} > @cache > def get_data(key): > if key in local_cache: # NO! Pods will disagree > return local_cache[key] > ``` > [!WARNING] > **Don't Expect Batching** > > Decorators work per-function, not across calls: > ```python > # WRONG: Can't optimize across multiple calls > @cache > def batch_process(items): > return [process(item) for item in items] > ``` --- ## Configuration ### Environment Variables Create a `.env` file in your project root: ```bash # Redis Configuration CACHEKIT_REDIS_URL=redis://localhost:6379/0 CACHEKIT_CONNECTION_POOL_SIZE=50 CACHEKIT_SOCKET_TIMEOUT=1.0 CACHEKIT_SOCKET_CONNECT_TIMEOUT=1.0 # Cache Configuration CACHEKIT_MAX_VALUE_SIZE=104857600 CACHEKIT_ARROW_COMPRESSION=zstd # Logging LOG_LEVEL=INFO CACHE_METRICS_ENABLED=true ``` ### Python Configuration ```python from cachekit import cache @cache(ttl=3600, namespace="myapp") def cached_function(): return expensive_operation() ``` --- ## Testing Your Setup ### Basic Functionality Test ```python import time from cachekit import cache @cache(ttl=60, backend=None) # L1-only (no Redis needed) def test_function(value): time.sleep(0.1) # Simulate expensive operation return f"processed_{value}" # First call - executes function start = time.time() result1 = test_function("test") duration1 = time.time() - start print(f"First call: {duration1:.3f}s, Result: {result1}") # Second call - L1 cache hit start = time.time() result2 = test_function("test") duration2 = time.time() - start print(f"Second call: {duration2:.3f}s, Result: {result2}") assert result1 == result2 assert duration2 < duration1 / 2 # Cache hit should be much faster print("Caching working correctly!") ``` ### Performance Test ```bash # Run the built-in performance tests pytest tests/performance/ -v # Or run the serializer benchmarks (saves a local baseline) make benchmark ``` --- ## Troubleshooting ### Redis Connection Issues ```python import redis try: client = redis.Redis(host='localhost', port=6379, db=0) client.ping() print("Redis connection successful") except redis.ConnectionError: print("Redis connection failed - check if Redis is running") ```
Common Issues & Solutions **1. Redis not running** ```bash # Start Redis (macOS) brew services start redis # Start Redis (Linux) sudo systemctl start redis # Or run Redis in Docker docker run -d -p 6379:6379 redis:latest ``` **2. Import errors** ```bash # Make sure package is installed uv add cachekit # Or reinstall uv remove cachekit && uv add cachekit ``` **3. Performance issues** ```python from cachekit import cache @cache(ttl=60) def test_function(): return "working" result = test_function() print(f"Cache working: {result}") ```
--- ## Production Considerations
Connection Pooling ```python import redis.connection redis_client = redis.Redis( connection_pool=redis.ConnectionPool( host='redis.company.com', port=6379, db=0, max_connections=50, socket_keepalive=True, health_check_interval=30 ) ) ```
Error Handling ```python from cachekit import cache @cache(ttl=3600) def robust_function(data): try: return process_data(data) except Exception as e: logger.error(f"Processing failed: {e}") raise ```
Monitoring ```python from cachekit import cache @cache(ttl=3600, namespace="monitoring_example") def monitored_function(): return expensive_operation() # Use the cached function multiple times to build statistics for i in range(3): result = monitored_function() print(f"Call {i+1}: {result}") ```
--- ## Next Steps | Direction | Resource | |:----------|:---------| | **Previous** | [Documentation Home](README.md) | | **Next** | [API Reference](api-reference.md) - Complete decorator parameters | ### Deep Dives | Topic | Description | |:------|:------------| | [Data Flow Architecture](data-flow-architecture.md) | How cachekit works internally | | [Performance Guide](performance.md) | Real benchmarks and optimization | | [Comparison to Alternatives](comparison.md) | How cachekit stacks up | ### Guides & Features | Guide | Description | |:------|:------------| | [Configuration Guide](configuration.md) | Detailed configuration and tuning | | [Serializer Guide](serializers/README.md) | Choose the right serializer | | [Backend Guide](backends/README.md) | Custom storage backends | | [Circuit Breaker](features/circuit-breaker.md) | Failure protection | | [Zero-Knowledge Encryption](features/zero-knowledge-encryption.md) | Client-side encryption | | [Prometheus Metrics](features/prometheus-metrics.md) | Production observability | ### Troubleshooting | Resource | Description | |:---------|:------------| | [Troubleshooting Guide](troubleshooting.md) | Solutions for common errors | | [Error Reference](error-codes.md) | Exceptions cachekit raises or logs | ---
**[Documentation Home](README.md)** · **[GitHub Issues](https://github.com/cachekit-io/cachekit-py/issues)** · **[Discussions](https://github.com/cachekit-io/cachekit-py/discussions)**