Skip to main content

Overview

tif1 implements a sophisticated two-tier caching architecture designed to maximize performance while minimizing redundant network requests. The caching system consists of:
  1. In-Memory LRU Cache: Fast, volatile cache for frequently accessed data with configurable size limits
  2. SQLite Persistent Cache: Durable disk-based storage that survives across sessions
This dual-layer approach ensures that hot data is served from memory (microsecond latency), while cold data can be retrieved from disk (millisecond latency) without hitting the network (second+ latency). The cache is thread-safe and supports both synchronous and asynchronous operations.

Cache Architecture

The cache stores two primary types of data:
  • JSON Data: Session metadata, lap data, driver information, weather data, and race control messages stored in a key-value table
  • Telemetry Data: High-frequency sensor data (speed, throttle, brake, gear, RPM, DRS) stored in a dedicated optimized table with composite indexing on (year, gp, session, driver, lap)
All data is automatically cached when loaded through Session.load() and can be manually managed through the Cache API.

Getting the Cache Instance

get_cache()

Returns the global singleton Cache instance used throughout the library. This is the primary entry point for all cache operations. The cache is initialized lazily on first access and configured based on environment variables and .tif1rc settings. The same instance is shared across all sessions and operations within your application. Returns: Cache - The global cache singleton Thread Safety: The cache instance is thread-safe and can be safely accessed from multiple threads. Example:
Configuration: The cache behavior can be customized through environment variables:
  • TIF1_CACHE_DIR: Custom cache directory (default: ~/.tif1/cache)
  • TIF1_CACHE_ENABLED: Set to "false" to disable caching entirely
  • TIF1_CACHE_READ_ONLY: Set to "true" to prevent writing new cache entries

Cache Management Operations

clear()

Removes all cached data from both the in-memory LRU cache and the persistent SQLite database. This operation is irreversible and will force all subsequent data requests to fetch from the CDN. Use Cases:
  • Clearing corrupted cache data
  • Freeing disk space
  • Forcing a complete data refresh
  • Troubleshooting data inconsistencies
Performance Impact: After clearing the cache, the first load of any session will be significantly slower as all data must be fetched from the network. Example:
Advanced Usage:

has_session_data()

Checks whether the cache contains any data for a specific session without actually loading it. This is a lightweight operation that queries the cache index rather than deserializing data. Parameters:
  • year (int): Season year (e.g., 2024, 2023)
  • gp (str): Grand Prix identifier in snake_case format (e.g., "Monaco_Grand_Prix", "Belgian_Grand_Prix")
  • session (str): Session type - one of "Race", "Qualifying", "Sprint", "Practice_1", "Practice_2", "Practice_3", "Sprint_Qualifying"
Returns: bool - True if any JSON or telemetry data exists for the session, False otherwise Use Cases:
  • Checking cache availability before loading
  • Building cache status dashboards
  • Implementing cache warming strategies
  • Conditional data loading logic
Example:
Batch Checking:

close()

Explicitly closes the SQLite database connection and releases associated resources. This method is called automatically when the Python process exits via an atexit handler, but can be invoked manually for fine-grained resource management. When to Use:
  • Long-running applications that need to release resources
  • Testing scenarios requiring clean cache state
  • Before forking processes (to avoid connection sharing issues)
  • Explicit resource cleanup in context managers
Note: After calling close(), the cache can still be used - it will automatically reconnect to the database on the next operation. Example:
Context Manager Pattern:

General Cache Access (JSON Data)

These methods provide low-level access to the cache’s key-value store for JSON-serializable data. Most users won’t need these methods directly, as Session.load() handles caching automatically. However, they’re useful for advanced use cases like custom data pipelines or cache inspection.

get()

Retrieves cached JSON-serializable data for a specific key. Checks the in-memory LRU cache first, then falls back to SQLite if not found in memory. Parameters:
  • key (str): Cache key in the format "{year}/{gp}/{session}/{file}.json" where file is one of:
    • drivers.json - Driver list and metadata
    • laps.json - Lap timing data
    • weather.json - Weather conditions
    • messages.json - Race control messages
    • session_info.json - Session metadata
Returns: Any | None - The cached data (typically a dict or list), or None if not found Example:
Advanced Usage - Cache Inspection:

set()

Stores JSON-serializable data in the cache with the specified key. Updates both the in-memory LRU cache and the persistent SQLite database. Parameters:
  • key (str): Cache key in the format "{year}/{gp}/{session}/{file}.json"
  • data (Any): JSON-serializable data to cache (dict, list, str, int, float, bool, None)
Serialization: Data is serialized using orjson for performance. Ensure your data contains only JSON-compatible types. Example:
Bulk Caching:

Telemetry Cache Access

Telemetry data (high-frequency sensor readings) is stored in a dedicated SQLite table optimized for lap-by-lap queries. This separation improves performance for telemetry-heavy workloads and enables efficient batch operations.

Telemetry Data Structure

Each telemetry entry contains time-series data for a single lap:
  • Time - Elapsed time in seconds from session start
  • Speed - Vehicle speed in km/h
  • RPM - Engine revolutions per minute
  • Gear - Current gear (0-8, where 0 is neutral)
  • Throttle - Throttle position (0-100%)
  • Brake - Brake pressure (0-100%)
  • DRS - DRS status (0=closed, 1=open, 2-14=various states)
  • Distance - Distance traveled in meters
  • X, Y, Z - 3D position coordinates

get_telemetry()

Retrieves telemetry data for a specific driver’s lap. Returns None if the telemetry is not cached. Parameters:
  • year (int): Season year (e.g., 2024)
  • gp (str): Grand Prix identifier (e.g., "Monaco_Grand_Prix")
  • session (str): Session type (e.g., "Race", "Qualifying")
  • driver (str): Three-letter driver code (e.g., "VER", "HAM", "LEC")
  • lap (int): Lap number (1-indexed)
Returns: Any | None - Telemetry data structure (typically a dict with arrays), or None if not found Performance: Single telemetry lookup is optimized with a composite index on (year, gp, session, driver, lap). Example:
Analyzing Cached Telemetry:

set_telemetry()

Stores telemetry data for a specific driver’s lap in the dedicated telemetry table. Parameters:
  • year (int): Season year
  • gp (str): Grand Prix identifier
  • session (str): Session type
  • driver (str): Three-letter driver code
  • lap (int): Lap number
  • data (Any): Telemetry data structure to cache
Serialization: Data is serialized using orjson before storage. Example:

get_telemetry_batch()

Retrieves multiple telemetry entries in a single optimized batch query. This is significantly more efficient than calling get_telemetry() multiple times, as it uses a single SQL query with an IN clause. Parameters:
  • year (int): Season year
  • gp (str): Grand Prix identifier
  • session (str): Session type
  • driver_laps (list[tuple[str, int]]): List of (driver_code, lap_number) tuples to fetch
Returns: dict[tuple[str, int], Any] - Dictionary mapping (driver, lap) tuples to telemetry data. Missing entries are not included in the result. Performance Benefits:
  • Single database query instead of N queries
  • Reduced Python-SQLite round trips
  • Optimized for bulk telemetry analysis
  • Ideal for comparing multiple drivers/laps
Example - Basic Batch Fetch:
Example - Comparing Driver Performance:
Example - Fastest Lap Analysis:

Async Methods

All cache read/write operations have async variants for use in async contexts. These methods are essential for building high-performance async applications that need to interact with the cache without blocking the event loop.

Async Method Overview

Implementation Details

Async methods use asyncio.to_thread() to execute synchronous SQLite operations in a thread pool, preventing event loop blocking. This approach maintains thread safety while providing async compatibility.

get_async()

Asynchronously retrieves cached JSON data for a specific key. Example:

set_async()

Asynchronously stores JSON data in the cache. Example:

get_telemetry_async()

Asynchronously retrieves telemetry data for a specific driver’s lap. Example:

set_telemetry_async()

Asynchronously stores telemetry data for a specific driver’s lap. Example:

get_telemetry_batch_async()

Asynchronously retrieves multiple telemetry entries in a single optimized batch query. Example - Parallel Telemetry Fetching:
Example - High-Performance Data Pipeline:

Cache Instance Attributes

The Cache instance exposes several read-only attributes for inspecting cache configuration and state.

cache_dir

The directory where the cache database and related files are stored. This is typically ~/.tif1/cache but can be customized via the TIF1_CACHE_DIR environment variable or .tif1rc configuration. Example:

db_path

The full filesystem path to the SQLite database file (cache.sqlite). This file contains all persistent cache data. Use Cases:
  • Backing up the cache database
  • Checking database file size
  • Manually inspecting cache with SQLite tools
Example:

read_only

Indicates whether the cache is operating in read-only mode. When True, the cache will serve existing data but will not write new entries to disk. Use Cases:
  • Running in environments with read-only filesystems
  • Preventing cache pollution during testing
  • Analyzing existing cache without modifications
Configuration: Set via TIF1_CACHE_READ_ONLY=true environment variable or in .tif1rc. Example:

Cache Statistics and Monitoring

While the Cache class doesn’t expose built-in statistics methods, you can query the SQLite database directly for monitoring purposes.

Example - Cache Size Analysis

Example - Session Coverage Report


Performance Considerations

Cache Hit Rates

The cache is most effective when:
  • Loading the same session multiple times
  • Analyzing historical data that doesn’t change
  • Working with telemetry-heavy workloads
  • Running batch analyses across multiple sessions
Typical Performance:
  • Memory cache hit: ~1-10 microseconds
  • SQLite cache hit: ~1-10 milliseconds
  • CDN fetch (cache miss): ~500-2000 milliseconds

Memory Management

The in-memory LRU cache has a default size limit to prevent excessive memory usage. When the limit is reached, least-recently-used entries are evicted (but remain in SQLite). Memory Usage Estimates:
  • Session metadata: ~10-50 KB per session
  • Lap data: ~50-200 KB per session
  • Telemetry data: ~500 KB - 5 MB per session (depending on lap count)

Disk Space

The SQLite database grows as more data is cached. Typical sizes:
  • Single session (no telemetry): ~100-500 KB
  • Single session (with telemetry): ~5-50 MB
  • Full season (all sessions, all telemetry): ~5-20 GB
Disk Space Management:

Optimization Tips

  1. Use Batch Operations: get_telemetry_batch() is significantly faster than multiple get_telemetry() calls
  2. Selective Loading: Only load the data you need (e.g., session.load(laps=True, telemetry=False))
  3. Async for Concurrency: Use async methods when fetching data for multiple sessions in parallel
  4. Cache Warming: Pre-load frequently accessed sessions during off-peak hours
  5. Read-Only Mode: Use read-only mode in production environments to prevent cache pollution
Example - Cache Warming Script:

Common Use Cases

Use Case 1: Cache Status Dashboard

Use Case 2: Selective Cache Clearing

Use Case 3: Cache Export/Import

Use Case 4: Cache Validation


Caching Strategy

Cache architecture

Config

Cache configuration

Performance

Optimization

Troubleshooting

Cache Not Working

Symptoms: Data is fetched from CDN every time, even for previously loaded sessions. Solutions:
  1. Check if caching is enabled:
  2. Verify cache directory is writable:
  3. Check environment variables:

Database Locked Errors

Symptoms: sqlite3.OperationalError: database is locked Causes:
  • Multiple processes accessing the cache simultaneously
  • Long-running transactions
  • Improper connection handling
Solutions:
  1. Ensure proper connection cleanup:
  2. Avoid concurrent writes from multiple processes
  3. Use read-only mode for read-heavy workloads

Cache Corruption

Symptoms: Errors when reading cached data, invalid JSON, or database integrity errors. Solutions:
  1. Clear and rebuild the cache:
  2. Delete the database file manually:
  3. Verify database integrity:

High Memory Usage

Symptoms: Python process consuming excessive RAM. Causes:
  • Large in-memory LRU cache
  • Loading many sessions with telemetry
Solutions:
  1. Reduce memory cache size (requires code modification)
  2. Load data selectively:
  3. Process data in batches:

Slow Cache Performance

Symptoms: Cache reads are slower than expected. Solutions:
  1. Use batch operations for telemetry:
  2. Optimize database (vacuum):
  3. Check disk I/O performance:

Caching Strategy

Understand the two-tier cache architecture

Config API

Configure cache behavior and location

Performance Guide

Optimization techniques and best practices

Core API

Learn how sessions interact with the cache
Last modified on May 8, 2026