Overview
tif1 implements a sophisticated, production-grade multi-layer caching system designed to minimize network requests, reduce latency, and maximize data access performance. The caching architecture is built on the principle of locality of reference and employs multiple storage tiers to balance speed, capacity, and persistence.
The caching system is critical to tif1’s performance characteristics. Without caching, every data access would require a network round-trip to the CDN, introducing latency of 500ms-3s per request. With the multi-layer cache, subsequent accesses can be served in microseconds from memory or milliseconds from disk, representing a 1000-10000x performance improvement for cached data.
Why Multi-Layer Caching?
The multi-layer approach provides several key advantages:- Speed Hierarchy: Different storage tiers offer different speed/capacity tradeoffs. Memory is fastest but limited; disk is slower but abundant.
- Persistence: In-memory caches are lost on process restart, while disk caches survive across sessions.
- Sharing: Process-local memory caches can’t be shared, while disk caches enable multi-process coordination.
- Graceful Degradation: If one cache layer fails, the system falls back to the next layer automatically.
- Optimal Resource Usage: Hot data stays in fast memory; warm data lives on disk; cold data is fetched on-demand.
Performance Impact
Real-world performance improvements with caching enabled:- First access (cold cache): 2-3 seconds (network fetch + processing)
- Second access (warm cache): 20-100ms (disk read + decompression)
- Third access (hot cache): <1ms (memory read)
- Overall speedup: 100-3000x for cached data
Cache Architecture
Thetif1 caching system consists of two primary layers working in concert, with an optional third layer for distributed deployments. Each layer serves a specific purpose in the performance hierarchy.
Cache Flow Detailed Explanation
When you request data (e.g.,session.laps), the system follows this precise flow:
-
Memory Cache Lookup (Layer 1)
- Check if data exists in the in-process LRU cache
- If found: Return immediately (~1ms latency)
- If not found: Proceed to Layer 2
-
SQLite Cache Lookup (Layer 2)
- Query SQLite database for cached entry
- If found: Decompress Parquet blob, deserialize to DataFrame (~20-100ms)
- Store result in Memory Cache for future access
- Return data
- If not found: Proceed to CDN fetch
-
CDN Fetch (Network Layer)
- Construct CDN URL from request parameters
- Fetch JSON data via HTTP (with retry logic)
- Parse JSON and construct DataFrame (~500ms-3s)
- Compress and store in SQLite cache
- Store in Memory Cache
- Return data
-
Cache Population
- Every successful fetch populates both cache layers
- Subsequent requests benefit from cached data
- Cache entries include metadata (timestamps, size, access count)
Cache Hierarchy Benefits
Layer 1: Memory Cache (LRU)
The first and fastest cache layer is an in-memory LRU (Least Recently Used) cache implemented using Python’sfunctools.lru_cache decorator with custom enhancements. This cache stores recently accessed DataFrames and metadata objects directly in process memory.
Architecture & Implementation
The memory cache uses a doubly-linked list combined with a hash map for O(1) access and O(1) eviction:- Hash Map: Provides constant-time lookups by cache key
- Doubly-Linked List: Maintains access order for LRU eviction
- Thread-Safe: Uses locks to ensure thread-safe access in multi-threaded environments
- Automatic Eviction: When capacity is reached, least recently used items are evicted automatically
Characteristics
- Access Speed: Sub-millisecond (typically 0.1-1ms)
- Default Capacity: 100 items (configurable up to 1000+)
- Scope: Process-specific (not shared across processes or threads)
- Lifetime: Cleared when process exits or cache is manually cleared
- Memory Overhead: ~50-100 bytes per entry plus data size
- Eviction Policy: Least Recently Used (LRU)
- Thread Safety: Yes (with internal locking)
Configuration Options
What Gets Cached
The memory cache stores the following data types:-
Session Metadata
- Event information (name, location, date)
- Session type and timing
- Circuit information
- Size: ~1-5 KB per session
-
Lap DataFrames
- Complete lap timing data for all drivers
- Sector times, compound information
- Size: ~2-5 MB per session (20 drivers × 50-70 laps)
-
Telemetry DataFrames
- High-frequency sensor data (speed, throttle, brake, gear, RPM, DRS)
- Sampled at ~10-50 Hz
- Size: ~10-20 MB per session (all drivers)
-
Weather Data
- Track temperature, air temperature, humidity, pressure
- Rainfall status and intensity
- Size: ~100-500 KB per session
-
Race Control Messages
- Flags, penalties, safety car periods
- Driver messages and notifications
- Size: ~50-200 KB per session
Cache Key Generation
Cache keys are deterministically generated from request parameters to ensure consistency:Memory Cache Behavior
Cache Hit Scenario:Memory Management
The memory cache automatically manages memory usage:Performance Characteristics
Benchmark results for memory cache operations:- Metadata: ~50-100 bytes (key, timestamps, access count)
- Data: Actual DataFrame size (2-20 MB typical)
- Total: Data size + ~100 bytes
Best Practices for Memory Cache
Size Configuration
Size Configuration
- Interactive analysis: 100-200 items (default)
- Batch processing: 50-100 items (lower memory footprint)
- Real-time dashboards: 200-500 items (maximize hit rate)
- Memory-constrained: 20-50 items or disable entirely
Multi-Process Considerations
Multi-Process Considerations
- Separate caches: Processes don’t share memory cache
- SQLite coordination: Use SQLite cache for cross-process sharing
- Warm-up: Each process should warm its own cache
- Memory multiplication: Total memory = cache_size × num_processes
Memory Pressure
Memory Pressure
- Monitor system memory usage
- Reduce cache size if memory is constrained
- Disable memory cache in low-memory environments
- Rely on SQLite cache for persistence
Layer 2: SQLite Persistent Cache
The second cache layer is a SQLite database that provides persistent, disk-based storage for cached data. This layer bridges the gap between fast but volatile memory cache and slow but reliable network fetches.Architecture & Implementation
The SQLite cache is implemented as a single-file database with optimized schema and indexes:- Storage Format: Single SQLite database file with BLOB storage
- Compression: Zstandard (zstd) compression for 60-80% size reduction
- Serialization: Apache Parquet format for efficient DataFrame storage
- Indexing: B-tree indexes on key and access time for fast lookups
- Transactions: ACID-compliant transactions for data integrity
- Concurrency: WAL (Write-Ahead Logging) mode for concurrent reads/writes
- Vacuum: Automatic space reclamation on cleanup operations
Characteristics
- Access Speed: 20-100ms (disk I/O + decompression)
- Capacity: Unlimited (disk-limited, typically 100MB-10GB)
- Scope: Shared across all processes accessing the same cache directory
- Lifetime: Survives process restarts, system reboots
- Persistence: Permanent until manually cleared or expired
- Concurrency: Multiple readers, single writer (SQLite WAL mode)
- Compression Ratio: 60-80% size reduction with zstd
- Thread Safety: Yes (SQLite handles locking)
Cache Location & Configuration
Default cache location varies by platform:Database Schema
The cache database uses an optimized schema designed for fast lookups and efficient storage:Data Storage Pipeline
Data is stored using a multi-step pipeline optimized for space and speed:Storage (Write Path)
-
DataFrame → Parquet Bytes
- Convert pandas/polars DataFrame to Apache Parquet format
- Parquet provides columnar storage with built-in compression
- Preserves data types, indexes, and metadata
- Time: ~10-50ms for typical DataFrame
-
Compress with Zstandard
- Apply zstd compression (level 3 default)
- Achieves 60-80% size reduction
- Fast compression (~500 MB/s)
- Time: ~5-20ms for typical data
-
Store in SQLite BLOB
- Insert compressed bytes into SQLite BLOB column
- Atomic transaction ensures data integrity
- Update metadata (timestamps, size, access count)
- Time: ~5-30ms depending on disk speed
Retrieval (Read Path)
-
Query SQLite by Key
- B-tree index lookup (O(log n))
- Retrieve compressed BLOB
- Update access metadata
- Time: ~1-5ms
-
Decompress with Zstandard
- Decompress zstd bytes to Parquet
- Fast decompression (~2 GB/s)
- Time: ~2-10ms
-
Parquet → DataFrame
- Parse Parquet bytes to DataFrame
- Restore data types and indexes
- Time: ~10-50ms
Storage Efficiency
Compression ratios for different data types:- Uncompressed: ~12 GB
- Compressed: ~2.5 GB
- Savings: ~9.5 GB (79% reduction)
Cache Operations
Reading from Cache
Cache Statistics
Clearing Cache
Advanced Cache Queries
Concurrency & Thread Safety
The SQLite cache handles concurrent access safely:- Multiple readers: Unlimited concurrent reads (no blocking)
- Single writer: Writes are serialized (SQLite limitation)
- Read-write: Readers don’t block writers in WAL mode
- Deadlock prevention: Automatic retry with exponential backoff
- Lock timeout: 30 seconds default (configurable)
Performance Tuning
Optimize SQLite cache performance:Best Practices for SQLite Cache
Cache Location
Cache Location
- Local development: Use default
~/.tif1/cache/ - Shared server: Use shared directory (e.g.,
/shared/cache/tif1/) - Docker: Mount volume for persistence
- Cloud: Use fast SSD storage (not network drives)
- CI/CD: Use temporary directory, clear after tests
Disk Space Management
Disk Space Management
- Set maximum cache size limit
- Enable automatic cleanup
- Clear old entries periodically
- Monitor disk usage with alerts
- Use compression level 3-9 for space savings
Multi-Process Coordination
Multi-Process Coordination
- Use shared cache directory
- Enable WAL mode (default)
- Set appropriate lock timeout
- Handle lock timeout errors gracefully
- Consider process-specific memory caches
Backup & Recovery
Backup & Recovery
- Backup cache database periodically
- Test cache restoration
- Handle corruption gracefully (auto-rebuild)
- Use checksums for integrity verification
- Keep cache separate from application data
Cache Operations & Workflows
Understanding how to effectively use the cache system is crucial for optimal performance. This section covers common operations, workflows, and patterns.Reading from Cache
The cache system operates transparently - you don’t need to explicitly check or manage cache hits/misses. The system automatically handles the cache hierarchy:Cache Statistics & Monitoring
Monitor cache performance and health:Clearing Cache
Multiple strategies for cache cleanup:Cache Inspection
Inspect cache contents and metadata:Cache Invalidation Strategies
Cache invalidation is one of the hardest problems in computer science.tif1 provides multiple strategies to ensure cache freshness while maintaining performance.
Manual Invalidation
Explicitly bypass cache for specific requests:Automatic Invalidation
The cache system automatically invalidates entries in several scenarios:1. Schema Version Changes
When the data structure changes (e.g., new columns added), old cache entries are automatically invalidated:2. CDN Freshness Checks
The cache system can check CDN for data updates using HTTP headers:3. Corruption Detection
Corrupted cache entries are automatically detected and removed:Time-Based Invalidation (TTL)
Set time-to-live for cache entries:Event-Based Invalidation
Invalidate cache when specific events occur:Selective Invalidation
Invalidate specific subsets of cache:Cache Versioning
Handle cache versioning across library updates:Best Practices for Cache Invalidation
When to Invalidate
When to Invalidate
- After library update: Schema might have changed
- When data is updated: New race results available
- On corruption: Detected errors in cached data
- For debugging: Testing data pipeline changes
- Periodic cleanup: Remove old/unused entries
- Before critical operations: Ensure fresh data
Invalidation Strategies
Invalidation Strategies
- Manual: For debugging and testing
- TTL: For data that changes predictably
- Event-based: For real-time data updates
- Freshness checks: For critical data accuracy
- Selective: For targeted invalidation
- Automatic: For schema/version changes
Performance Considerations
Performance Considerations
- Frequent invalidation: Fresh data, slower performance
- Rare invalidation: Fast performance, stale data risk
- Selective invalidation: Best of both worlds
- Freshness checks: Add latency but ensure accuracy
- TTL: Good balance for most use cases
Monitoring Invalidation
Monitoring Invalidation
- Monitor invalidation frequency
- Track cache miss rate after invalidation
- Measure performance impact
- Log invalidation events
- Alert on excessive invalidation
Cache Warming Strategies
Cache warming is the process of pre-populating the cache with data before it’s needed. This eliminates cold-start latency and ensures optimal performance from the first request.Why Warm the Cache?
Cache warming provides several benefits:- Eliminate Cold Start: First requests are as fast as subsequent ones
- Predictable Performance: No sudden latency spikes from cache misses
- Better User Experience: Dashboards and applications load instantly
- Reduced CDN Load: Batch fetching is more efficient than on-demand
- Offline Capability: Pre-cached data works without network access
Warm Entire Season
Pre-cache all races for a complete season:Warm Specific Events
Pre-cache specific events or races:Warm Specific Data Types
Pre-cache only specific data types:Warm by Driver
Pre-cache data for specific drivers:Scheduled Cache Warming
Automatically warm cache on a schedule:Parallel Cache Warming
Maximize warming speed with parallel execution:Smart Cache Warming
Intelligently warm cache based on usage patterns:Cache Warming Best Practices
When to Warm
When to Warm
- Before race weekend: Warm upcoming event data
- Off-peak hours: Minimize CDN load (e.g., 2-4 AM)
- After data updates: When new race results are available
- Application startup: For dashboards and services
- Before analysis: Pre-warm data you’ll need
- Periodic refresh: Weekly or monthly for historical data
What to Warm
What to Warm
- Hot data: Current season, recent races
- Frequently accessed: Popular events (Monaco, Silverstone)
- Critical data: Race results, qualifying times
- User-specific: Data for favorite drivers/teams
- Predictable access: Upcoming race weekends
- Complete sessions: All data types for consistency
Warming Strategies
Warming Strategies
- Full warming: All data for all sessions (slow, complete)
- Selective warming: Specific events or data types (fast, targeted)
- Incremental warming: Warm as needed (balanced)
- Parallel warming: Multiple sessions at once (fastest)
- Scheduled warming: Automatic periodic warming (hands-off)
- Smart warming: Based on access patterns (efficient)
Performance Considerations
Performance Considerations
- Parallel execution: Use async/await or threading
- Rate limiting: Don’t overwhelm CDN (max 10-20 concurrent)
- Error handling: Continue on failures, log errors
- Progress tracking: Monitor warming progress
- Resource limits: Consider memory and disk space
- Network bandwidth: Warming uses significant bandwidth
Monitoring Warming
Monitoring Warming
- Warming time: How long does it take?
- Success rate: How many sessions succeed?
- Cache hit rate: Does warming improve hit rate?
- Storage usage: How much disk space used?
- CDN requests: How many requests made?
- Error patterns: Which sessions fail consistently?
Cache Performance Analysis
Understanding cache performance is crucial for optimization. This section provides detailed performance metrics, benchmarks, and analysis techniques.Benchmark Results
Comprehensive performance measurements across different scenarios:Access Latency by Cache State
Throughput Measurements
Cache Hit Rates by Usage Pattern
Memory Usage Patterns
Typical memory footprint per cached session:By Data Type
By Session Type
Memory Cache Capacity Planning
Disk Usage Patterns
SQLite cache storage requirements:By Season
Growth Over Time
Performance Profiling
Profile cache performance in your application:Performance Optimization Tips
Memory Cache Optimization
Memory Cache Optimization
- Increase cache size: More items = higher hit rate
- Warm frequently accessed data: Pre-load hot data
- Monitor hit rate: Aim for >80% for interactive use
- Clear unused entries: Free memory for hot data
- Use appropriate data types: Avoid caching large objects
SQLite Cache Optimization
SQLite Cache Optimization
- Use SSD storage: 10-100x faster than HDD
- Increase SQLite cache size: More memory = faster queries
- Enable WAL mode: Better concurrency (default)
- Vacuum periodically: Reclaim space, improve performance
- Use appropriate compression: Balance speed vs size
Network Optimization
Network Optimization
- Warm cache proactively: Avoid cold starts
- Use parallel fetching: Fetch multiple sessions at once
- Enable retry logic: Handle transient failures
- Monitor CDN performance: Track fetch times
- Use CDN geographically close: Reduce latency
Application-Level Optimization
Application-Level Optimization
- Batch requests: Fetch multiple sessions together
- Reuse session objects: Avoid redundant fetches
- Profile cache access: Identify bottlenecks
- Monitor cache metrics: Track hit rates and latency
- Implement cache warming: Pre-load predictable data
Performance Monitoring
Set up comprehensive cache monitoring:Cache Maintenance & Operations
Proper cache maintenance ensures optimal performance, prevents disk space issues, and maintains data integrity.Monitoring Cache Size
Track cache growth and disk usage:Automatic Cleanup Configuration
Configure automatic cache cleanup to prevent unbounded growth:Manual Cleanup Strategies
Implement custom cleanup logic:Cache Integrity Verification
Verify cache integrity and detect corruption:Cache Backup & Restore
Backup and restore cache data:Scheduled Maintenance
Automate cache maintenance tasks:Cache in Production Environments
Deployingtif1 with caching in production requires careful consideration of architecture, scalability, and reliability.
Shared Cache Architecture
For multi-process applications, configure a shared cache:Docker Deployment
Configure caching for Docker containers:Kubernetes Deployment
Deploy with persistent cache in Kubernetes:Read-Only Cache
For read-only deployments (e.g., serverless, immutable infrastructure):Cache Replication
Replicate cache across servers or regions:High-Availability Setup
Configure cache for high availability:Load Balancing
Distribute cache load across multiple instances:Monitoring & Alerting
Set up production monitoring:Performance Tuning for Production
Optimize cache for production workloads:Best Practices for Production
Deployment Architecture
Deployment Architecture
- Shared cache: Use shared storage for multi-process apps
- Persistent volumes: Mount cache on persistent storage
- Replication: Replicate cache across regions/zones
- Failover: Configure fallback cache locations
- Sharding: Distribute cache load across shards
- Read replicas: Use read-only caches for scaling reads
Resource Planning
Resource Planning
- Disk space: 5-10GB per season of data
- Memory: 2-4GB for memory cache + application
- CPU: Minimal (compression/decompression)
- Network: Bandwidth for initial cache warming
- IOPS: SSD recommended for SQLite cache
Monitoring & Alerting
Monitoring & Alerting
- Cache size: Alert when approaching limits
- Hit rate: Alert when below threshold (70%)
- Disk space: Alert when low (<10% free)
- Integrity: Alert on corruption
- Performance: Track access latency
- Errors: Monitor cache operation failures
Maintenance
Maintenance
- Daily: Verify integrity, clear old entries
- Weekly: Vacuum database, rebuild indexes
- Monthly: Full backup, cleanup old seasons
- Quarterly: Review and optimize configuration
- Yearly: Archive old data, plan capacity
Security
Security
- Permissions: Restrict cache directory access
- Encryption: Encrypt cache at rest (if needed)
- Network: Secure cache replication channels
- Audit: Log cache access for compliance
- Backup: Encrypt backups, secure storage
Troubleshooting Cache Issues
Common cache problems and their solutions.Cache Corruption
If you encounter cache errors or corrupted data:Cache Not Working
If cache doesn’t seem to be working:Performance Issues
If cache is slow:Memory Issues
If experiencing memory problems:Disk Space Issues
If running out of disk space:Connection Issues
If experiencing SQLite connection problems:Common Error Messages
'database is locked'
'database is locked'
- Increase lock timeout:
config.cache_lock_timeout = 60 - Enable WAL mode (should be default):
config.cache_wal_mode = True - Close other processes accessing cache
- Use separate cache directories for different processes
'Failed to decompress'
'Failed to decompress'
- Clear corrupted entry:
cache.clear_corrupted() - Verify cache integrity:
cache.verify_integrity() - Clear all cache:
cache.clear() - Restore from backup
'Invalid Parquet format'
'Invalid Parquet format'
- Clear corrupted entry
- Check schema version compatibility
- Update tif1 library
- Clear cache after library update
'Permission denied'
'Permission denied'
- Fix permissions:
chmod 755 ~/.tif1/cache - Change cache directory:
config.cache_dir = "/writable/path" - Run with appropriate user permissions
'No space left on device'
'No space left on device'
- Clear old entries:
cache.clear_old(days=30) - Clear large entries:
cache.clear_largest(count=50) - Enable auto cleanup:
config.cache_auto_cleanup = True - Move cache to larger disk
Debug Mode
Enable debug logging for troubleshooting:Best Practices Summary
Follow these best practices for optimal cache performance and reliability.Configuration Best Practices
Keep Cache Enabled
Use Appropriate Cache Size
Enable Auto Cleanup
Use SSD Storage
Operational Best Practices
Warm Critical Data
Monitor Cache Health
Regular Maintenance
Handle Errors Gracefully
Development Best Practices
Profile Cache Access
Batch Requests
Reuse Session Objects
Test Cache Behavior
Production Best Practices
Use Shared Cache
Persistent Storage
Backup Regularly
Monitor & Alert
Performance Optimization Checklist
Enable Memory Cache
Use SSD Storage
Tune SQLite Settings
Optimize Compression
Warm Cache Proactively
Monitor Hit Rate
Regular Maintenance
Profile & Optimize
Security Best Practices
File Permissions
File Permissions
- Owner: read/write/execute (rwx)
- Group: read/execute (r-x)
- Others: none (---)
- Command:
chmod 750 ~/.tif1/cache
Encryption at Rest
Encryption at Rest
- Use encrypted filesystem (LUKS, BitLocker)
- Or encrypt cache database with SQLCipher
- Or use application-level encryption
Access Control
Access Control
- Use separate cache directories per user/application
- Set file ownership appropriately
- Use SELinux/AppArmor for additional isolation
Audit Logging
Audit Logging
- Enable cache debug logging
- Log cache operations to audit trail
- Monitor for suspicious access patterns
Conclusion
Thetif1 caching system is a sophisticated, production-grade solution designed to maximize performance while maintaining data integrity and reliability. By understanding and properly configuring the multi-layer cache architecture, you can achieve:
- 100-3000x performance improvement for cached data access
- Minimal network usage through intelligent caching
- Predictable performance with cache warming
- Scalability through shared cache and replication
- Reliability through automatic corruption detection and cleanup
Key Takeaways
- Two-Layer Architecture: Memory cache (fast, volatile) + SQLite cache (persistent, shared)
- Automatic Management: Transparent cache operations, automatic cleanup, corruption detection
- High Performance: Sub-millisecond memory access, 20-100ms disk access, 60-80% compression
- Production-Ready: Shared cache, replication, monitoring, high availability
- Easy to Use: Works transparently, minimal configuration required
Getting Started
For most users, the default configuration works well:Next Steps
- Learn More: Read the Cache API Reference for detailed API documentation
- Optimize Performance: Follow the Best Practices Guide for optimization tips
- Deploy to Production: See Caching Strategy for production best practices
- Monitor Cache: Set up monitoring to track cache health
- Get Help: Join our Discord or GitHub Discussions