proxywhirl.cache.tiers ====================== .. py:module:: proxywhirl.cache.tiers .. autoapi-nested-parse:: Cache tier implementations for multi-tier caching strategy. Defines: - CacheTier: Abstract base class for cache tier implementations - MemoryCacheTier: L1 in-memory cache using OrderedDict - FileCacheTier: L2 JSONL file cache with encryption and file locking - SQLiteCacheTier: L3 database cache with full persistence Classes ------- .. autoapisummary:: proxywhirl.cache.tiers.CacheTier proxywhirl.cache.tiers.DiskCacheTier proxywhirl.cache.tiers.JsonlCacheTier proxywhirl.cache.tiers.MemoryCacheTier proxywhirl.cache.tiers.SQLiteCacheTier proxywhirl.cache.tiers.TierType Module Contents --------------- .. py:class:: CacheTier(config, tier_type) Bases: :py:obj:`abc.ABC` Abstract base class for cache tier implementations. Defines the interface that all cache tiers (L1, L2, L3) must implement, including graceful degradation on repeated failures. .. attribute:: config Configuration for this tier .. attribute:: tier_type Type of tier (L1/L2/L3) .. attribute:: enabled Whether tier is operational .. attribute:: failure_count Consecutive failures for degradation tracking .. attribute:: failure_threshold Failures before auto-disabling tier Initialize cache tier with configuration. :param config: Configuration for this tier :param tier_type: Type of tier (L1/L2/L3) .. py:method:: cleanup_expired() :abstractmethod: Remove all expired entries in bulk. :returns: Number of entries removed .. py:method:: clear() :abstractmethod: Clear all entries, return count of removed entries. :returns: Number of entries removed .. py:method:: delete(key) :abstractmethod: Remove entry by key, return True if existed. :param key: Cache key to delete :returns: True if entry existed and was deleted, False if not found .. py:method:: get(key) :abstractmethod: Retrieve entry by key, None if not found or expired. :param key: Cache key to lookup :returns: CacheEntry if found and valid, None otherwise .. py:method:: handle_failure(error) Handle tier operation failure for graceful degradation. Increments failure count and disables tier if threshold exceeded. Called by implementations when operations fail. :param error: Exception that occurred .. py:method:: keys() :abstractmethod: Return list of all keys. :returns: List of cache keys .. py:method:: put(key, entry) :abstractmethod: Store entry, return True if successful. :param key: Cache key for entry :param entry: CacheEntry to store :returns: True if stored successfully, False otherwise .. py:method:: reset_failures() Reset failure count on successful operation. Re-enables tier if previously disabled and resets failure counter. Implementations should call this after successful operations. .. py:method:: size() :abstractmethod: Return current number of entries. :returns: Number of entries in tier .. py:class:: DiskCacheTier(config, tier_type, cache_dir, encryptor = None) Bases: :py:obj:`CacheTier` L2 SQLite-based cache with encryption and indexed lookups. Optimized for >10K entries using SQLite with B-tree indexes instead of JSONL. Provides O(log n) lookups vs O(n) for JSONL, achieving <10ms reads for 10K+ entries. Uses a lightweight SQLite database with: - Primary key index on cache key for fast lookups - Encrypted credentials stored as BLOB - Efficient bulk operations (cleanup, size, keys) - File-based persistence without complex sharding - Persistent connection pooling for performance (RES-005) Thread Safety: Uses a threading.Lock to protect connection access. The connection is created with check_same_thread=False to allow multi-threaded access. Initialize SQLite-based L2 cache. :param config: Tier configuration :param tier_type: Type of tier (should be L2_FILE) :param cache_dir: Directory for cache database :param encryptor: Credential encryptor for username/password .. py:method:: cleanup_expired() Remove all expired entries from SQLite L2 database using indexed SQL DELETE. Performs bulk deletion of expired entries in a single SQL operation. Uses the expires_at index for efficient identification. :returns: Number of expired entries that were removed, 0 on error. Side Effects: - Acquires persistent connection via thread-safe _get_connection(). - Calculates current timestamp in UTC. - Executes DELETE with WHERE clause using expires_at index. - Commits transaction before returning. - Resets failure counter on success. Thread Safety: Thread-safe via connection lock in _get_connection(). Performance: O(m log n) where m is number of expired entries and n is total entries. The idx_l2_expires_at index makes this significantly faster than full table scan. :raises Exception: Caught and handled via handle_failure(), returns 0. .. rubric:: Example >>> tier = DiskCacheTier(config, TierType.L2_FILE, cache_dir) >>> removed = tier.cleanup_expired() >>> print(f"Removed {removed} expired entries") Removed 42 expired entries .. py:method:: clear() Clear all entries from SQLite L2 cache database. Performs bulk deletion of all cache entries. More efficient than iterating through individual deletes. :returns: Number of entries that were cleared, 0 on error. Side Effects: - Acquires persistent connection via thread-safe _get_connection(). - Counts total entries before deletion. - Executes DELETE FROM without WHERE clause (removes all rows). - Commits transaction before returning. - Resets failure counter on success. Thread Safety: Thread-safe via connection lock in _get_connection(). Performance: O(n) where n is the number of entries, but executes as a single SQL operation with automatic index cleanup. :raises Exception: Caught and handled via handle_failure(), returns 0. .. rubric:: Example >>> tier = DiskCacheTier(config, TierType.L2_FILE, cache_dir) >>> tier.size() 1000 >>> cleared = tier.clear() >>> print(f"Cleared {cleared} entries") Cleared 1000 entries >>> tier.size() 0 .. py:method:: close() Close the persistent SQLite connection and release database resources. Should be called when the cache tier is no longer needed to properly release database resources and file locks. Safe to call multiple times. Side Effects: - Acquires connection lock to ensure thread safety. - Closes active SQLite connection if present. - Sets internal connection to None to prevent reuse. - Suppresses any exceptions during close to ensure cleanup completes. Thread Safety: Thread-safe via internal lock. Multiple threads can safely call this method concurrently. .. rubric:: Example >>> tier = DiskCacheTier(config, TierType.L2_FILE, cache_dir) >>> # ... use tier ... >>> tier.close() # Clean up resources >>> tier.close() # Safe to call again .. py:method:: delete(key) Remove entry from SQLite database by cache key. Uses SQL DELETE with primary key lookup for O(log n) performance. :param key: Cache key to delete. :returns: True if entry existed and was deleted, False if not found or on error. Side Effects: - Acquires persistent connection via thread-safe _get_connection(). - Executes DELETE query with parameterized key (SQL injection safe). - Commits transaction before returning. - Does NOT reset failure counter (only success paths do). Thread Safety: Thread-safe via connection lock in _get_connection(). :raises Exception: Caught and handled via handle_failure(), returns False. .. py:method:: get(key) Retrieve entry from SQLite database with O(log n) indexed lookup. :param key: Cache key to lookup :returns: CacheEntry if found and valid, None otherwise .. py:method:: keys() Return list of all cache keys from SQLite L2 database. Retrieves all cache keys without loading full entry data. Useful for cache inspection, debugging, and bulk operations. :returns: List of all cache keys in database, empty list on error. Side Effects: - Acquires persistent connection via thread-safe _get_connection(). - Executes SELECT key query (fetches only key column, not full rows). - Loads all keys into memory as a list. - Resets failure counter on success. Thread Safety: Thread-safe via connection lock in _get_connection(). Performance: O(n) where n is number of entries. For large caches (>10K entries), consider using size() to check count before calling. :raises Exception: Caught and handled via handle_failure(), returns []. .. warning:: For very large caches (>100K entries), this may consume significant memory. Consider pagination or streaming approaches if needed. .. py:method:: migrate_from_jsonl(jsonl_dir = None) Migrate existing JSONL shard files to SQLite L2 cache. This method provides a migration path from the old JSONL-based L2 cache to the new SQLite-based implementation. It reads all shard_*.jsonl files from the specified directory and imports them into the SQLite database. :param jsonl_dir: Directory containing shard_*.jsonl files. Defaults to self.cache_dir if not specified. :returns: Number of entries successfully migrated .. rubric:: Example >>> tier = DiskCacheTier(config, TierType.L2_FILE, cache_dir) >>> migrated = tier.migrate_from_jsonl() >>> print(f"Migrated {migrated} entries from JSONL to SQLite") .. py:method:: put(key, entry) Store entry in SQLite database with INSERT OR REPLACE. :param key: Cache key for entry :param entry: CacheEntry to store :returns: True if stored successfully, False otherwise .. py:method:: size() Return current number of entries in SQLite L2 cache database. Uses SQL COUNT(*) for efficient O(1) size calculation via table metadata. :returns: Number of cache entries currently stored, 0 on error. Side Effects: - Acquires persistent connection via thread-safe _get_connection(). - Executes SELECT COUNT(*) query (reads table metadata, not rows). - Resets failure counter on success. Thread Safety: Thread-safe via connection lock in _get_connection(). Performance: O(1) - SQLite maintains row count in table metadata. :raises Exception: Caught and handled via handle_failure(), returns 0. .. py:class:: JsonlCacheTier(config, tier_type, cache_dir, encryptor = None, num_shards = 16) Bases: :py:obj:`CacheTier` L2 JSONL file-based cache with sharding and encryption. Provides persistent caching using JSONL (JSON Lines) files with: - Sharded storage for better I/O performance - File locking for concurrent access safety - Encrypted credentials at rest - Simple text format for debugging and portability Best suited for: - Smaller cache sizes (<10K entries) - Environments where SQLite is unavailable - Cases requiring human-readable cache files - Simple deployment without database dependencies For larger caches (>10K entries), consider DiskCacheTier (SQLite-based) which provides O(log n) lookups vs O(n) for JSONL. Initialize JSONL-based L2 cache. :param config: Tier configuration :param tier_type: Type of tier (should be L2_FILE) :param cache_dir: Directory for JSONL shard files :param encryptor: Credential encryptor for username/password :param num_shards: Number of shard files (default: 16) .. py:method:: cleanup_expired() Remove all expired entries from all shards. :returns: Number of entries removed .. py:method:: clear() Clear all JSONL shard files. :returns: Number of entries cleared .. py:method:: delete(key) Remove entry from JSONL shard. :param key: Cache key to delete :returns: True if entry existed and was deleted, False if not found .. py:method:: get(key) Retrieve entry from JSONL shard. :param key: Cache key to lookup :returns: CacheEntry if found and valid, None otherwise .. py:method:: keys() Return list of all keys. :returns: List of cache keys .. py:method:: put(key, entry) Store entry in JSONL shard with encrypted credentials. :param key: Cache key for entry (should match entry.key) :param entry: CacheEntry to store :returns: True if stored successfully, False otherwise .. py:method:: size() Return current number of entries. :returns: Number of entries in index .. py:class:: MemoryCacheTier(config, tier_type, on_evict = None) Bases: :py:obj:`CacheTier` L1 in-memory cache using OrderedDict for LRU tracking. Provides O(1) lookups with automatic LRU eviction when max_entries exceeded. Initialize memory cache with LRU tracking. :param config: Tier configuration :param tier_type: Type of tier (L1/L2/L3) :param on_evict: Optional callback when entry is evicted (key, entry) .. py:method:: cleanup_expired() Remove all expired entries from memory cache in bulk. :returns: Number of expired entries that were removed. Side Effects: Deletes all entries where is_expired property is True. .. py:method:: clear() Clear all entries from memory cache. :returns: Number of entries that were removed. Side Effects: Empties the OrderedDict, releasing all cached entries. .. py:method:: delete(key) Remove entry from memory cache by key. :param key: Cache key to delete. :returns: True if entry existed and was deleted, False if not found. Side Effects: Removes entry from OrderedDict if present. .. py:method:: get(key) Retrieve entry from memory cache, updating LRU order. :param key: Cache key to lookup. :returns: CacheEntry if found, None otherwise. Updates LRU order on hit by moving the accessed entry to the end of the OrderedDict. Side Effects: Moves accessed entry to end of LRU queue (most recently used position). .. py:method:: keys() Return list of all cache keys in memory tier. :returns: List of cache keys in LRU order (oldest to newest). .. py:method:: put(key, entry) Store entry in memory cache with automatic LRU eviction. :param key: Cache key for the entry. :param entry: CacheEntry object to store. :returns: True if stored successfully, False on error. Side Effects: - Removes existing entry if key already exists (update operation). - Evicts least recently used entry if max_entries exceeded. - Calls on_evict callback if provided when eviction occurs. - Resets failure counter on success. :raises Exception: Caught and handled via handle_failure(), returns False. .. py:method:: size() Return current number of entries in memory cache. :returns: Count of entries currently stored in the OrderedDict. .. py:class:: SQLiteCacheTier(config, tier_type, db_path, encryptor = None) Bases: :py:obj:`CacheTier` L3 SQLite database cache with encrypted credentials. Provides durable persistence with SQL indexing for fast lookups. Initialize SQLite-based L3 cache with health monitoring. :param config: Tier configuration settings. :param tier_type: Type of tier (should be L3_SQLITE). :param db_path: Path to SQLite database file. :param encryptor: Optional credential encryptor for username/password. Creates default CredentialEncryptor if not provided. Side Effects: - Creates parent directories for database if they don't exist. - Initializes database schema with cache_entries and health_history tables. - Creates indexes for expires_at, source, health_status, and last_accessed. .. py:method:: cleanup_expired() Remove all expired entries in bulk using SQL DELETE. This is significantly more efficient than iterating through all entries, reducing cleanup from O(n) to O(1) for expired entries. :returns: Number of entries removed .. py:method:: clear() Clear all entries from SQLite database. :returns: Number of entries that were deleted. Side Effects: - Opens new SQLite connection for operation. - Deletes all rows from cache_entries table. - Cascades to delete all health_history records via FOREIGN KEY. - Commits transaction before returning. - Resets failure counter on success. :raises Exception: Caught and handled via handle_failure(), returns 0. .. py:method:: delete(key) Remove entry from SQLite database by key. :param key: Cache key to delete. :returns: True if entry existed and was deleted, False if not found. Side Effects: - Opens new SQLite connection for deletion. - Cascades to delete related health_history records via FOREIGN KEY. - Commits transaction before returning. :raises Exception: Caught and handled via handle_failure(), returns False. .. py:method:: get(key) Retrieve entry from SQLite database with decrypted credentials. :param key: Cache key to lookup. :returns: CacheEntry if found with decrypted username/password, None otherwise. Side Effects: - Opens new SQLite connection for query. - Decrypts username_encrypted and password_encrypted BLOBs. - Converts UNIX timestamps to datetime objects. - Resets failure counter on success. :raises Exception: Caught and handled via handle_failure(), returns None. .. py:method:: keys() Return all cache keys from SQLite database. :returns: List of all cache keys, empty list on error. Side Effects: - Opens new SQLite connection for query. - Resets failure counter on success. :raises Exception: Caught and handled via handle_failure(), returns []. .. py:method:: put(key, entry) Store entry in SQLite database with encrypted credentials. :param key: Cache key for the entry. :param entry: CacheEntry object to store with all fields including health monitoring data. :returns: True if stored successfully, False on error. Side Effects: - Opens new SQLite connection for write. - Encrypts username and password fields as BLOBs. - Uses INSERT OR REPLACE (upsert) to handle updates. - Sets created_at and updated_at to current timestamp. - Commits transaction before returning. - Resets failure counter on success. :raises Exception: Caught and handled via handle_failure(), returns False. .. py:method:: size() Return current number of entries in SQLite database. :returns: Count of entries in cache_entries table, 0 on error. Side Effects: - Opens new SQLite connection for query. - Resets failure counter on success. :raises Exception: Caught and handled via handle_failure(), returns 0. .. py:class:: TierType Bases: :py:obj:`str`, :py:obj:`enum.Enum` Cache tier types. Initialize self. See help(type(self)) for accurate signature.