Skip to main content

Overview

tif1 implements a two-tier caching architecture that reduces 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 serves hot data from memory (microsecond latency). Cold data is retrieved from disk (millisecond latency) without a network request (second+ latency). The cache is thread-safe and supports 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 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 in the 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: OS-dependent; see the platform defaults in the configuration reference)
  • 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 the cache is cleared, the first load of any session is slower. All data must be fetched from the network again. 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 (for example, 2024, 2023)
  • gp (str): Grand Prix identifier in snake_case format (for example, "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 runs automatically when the Python process exits, via an atexit handler. Invoke it 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 close(), the cache can still be used. It reconnects to the database automatically 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 will not need these methods directly, as Session.load() handles caching automatically. However, they are 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 with orjson for performance. Ensure that the data contains only JSON-compatible types. Example:
Bulk Caching:

set_raw()

Stores an already-serialized JSON blob without re-encoding. The async fetch pipeline uses this when validation left the payload byte-identical to the HTTP body. Blobs of 4 KB or larger are compressed at the SQLite boundary only (zstd level 1; legacy zlib rows stay readable via magic-byte detection). The in-memory LRU keeps the plain blob. Small rows stay plain TEXT so legacy cache files remain readable. Parameters:
  • key (str): Cache key in the format "{year}/{gp}/{session}/{file}.json"
  • blob (bytes | bytearray | memoryview): UTF-8 JSON bytes
Example:

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 (for example, 2024)
  • gp (str): Grand Prix identifier (for example, "Monaco_Grand_Prix")
  • session (str): Session type (for example, "Race", "Qualifying")
  • driver (str): Three-letter driver code (for example, "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 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 enable high-performance async applications 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. By default, tif1 follows FastF1’s OS-specific layout: Windows %LOCALAPPDATA%/Temp/tif1, macOS ~/Library/Caches/tif1, and Linux/other POSIX ~/.cache/tif1 when ~/.cache exists (otherwise ~/.tif1). It can be customized via TIF1_CACHE_DIR or .tif1rc. 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:

Module-Level Cache API

Besides the Cache class, tif1.cache exposes the building blocks of the memory tiers.

LRUCache

Thread-safe bounded LRU cache. It is the single LRU implementation backing the Cache in-memory tiers and the process-global backend lap caches. Reads support a lock-free fast path (get(key, ordered=False)); writes refresh LRU order and evict oldest-first at maxsize. Methods: get(key, *, ordered=True), set(key, value), clear(), pop(key, default=None), plus in, len(), and iteration support.

get_backend_lap_cache

Return the process-global lap cache for the given backend, keyed by "{year}_{gp}_{session}_laps". One cache per backend; shared across sessions in the same process.

clear_lap_cache

Clear both process-global backend lap caches (pandas and polars).

Cache.get_entry / Cache.set_entry

Kind-based access to the in-memory tiers: "json" (path-keyed payloads), "telemetry_payload" (raw (driver, lap) payloads), and "telemetry_df" (materialized telemetry DataFrames). get()/set() on Cache are the string-keyed convenience wrappers over these.

SessionMemo

Per-session memo tier held as Session._memo. It consolidates the session-scoped caches behind the same kind-based interface. It also owns the fastest-lap-reference memo, the persistent-cache probe result (has_session_data), and per-driver telemetry failure tracking. Not internally synchronized. The owning session’s access patterns are single-threaded per event loop.

Cache Statistics and Monitoring

The Cache class does not expose built-in statistics methods. 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 does not 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 faster than multiple get_telemetry() calls
  2. Selective Loading: Only load the data that is needed (for example, 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 September 10, 2026