# How the Cache Controller Optimizes Performance in Music Assistant

> Discover how the CacheController optimizes Music Assistant performance. Learn about SQLite persistence, JSON serialization, and automatic cleanup for reduced I/O and memory pressure.

- Repository: [Music Assistant/server](https://github.com/music-assistant/server)
- Tags: performance
- Published: 2026-06-14

---

**The CacheController optimizes Music Assistant by persisting frequently-used data in a local SQLite database with JSON serialization, indexed lookups, automatic cleanup, and background vacuuming to minimize I/O latency and memory pressure.**

The cache controller is a critical performance component in the `music-assistant/server` repository that eliminates redundant network requests and expensive computations. By storing serialized data locally with intelligent indexing and maintenance routines, the system ensures sub-millisecond access to provider metadata, album art, and playlist structures while preventing database bloat.

## Persistent Storage Architecture

### SQLite Database Initialization

The `CacheController` initializes its storage layer in [`music_assistant/controllers/cache/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/cache/controller.py) through the `_setup_database()` method. This routine creates the `cache.db` file, executes pending migrations via the database helper, and triggers `_check_oversized_cache()` to verify the database remains within safe limits. The implementation spans lines 6-13 and establishes the foundation for durable, high-speed data persistence without external dependencies.

### Table Schema and Indexing Strategy

Database tables are defined in `__create_database_tables()` with columns for `category`, `key`, `provider`, `expires`, and checksum data. The `__create_database_indexes()` method then adds multi-column indexes to these fields, enabling **O(log n)** lookup complexity for retrieval operations. These schema definitions appear at lines 61-84 and 88-108 respectively, ensuring that even with millions of cached entries, query latency remains minimal and predictable.

## Data Serialization and Lookup Optimization

### JSON Serialization for Portability

All cached values undergo JSON serialization to ensure storage efficiency and cross-platform compatibility. The `CacheController.set()` method converts Python objects using `json_dumps` before insertion (lines 70-101), while `CacheController.get()` deserializes stored strings via `async_json_loads` during retrieval (lines 98-130). This approach guarantees that only lightweight, portable structures occupy disk space, reducing I/O overhead compared to native Python pickling.

### Sub-Millisecond Query Performance

The multi-column indexes on `category`, `key`, and `provider` allow the SQLite engine to execute pinpoint lookups without full table scans. As implemented in the source code, this indexing strategy eliminates network latency for frequently accessed metadata, delivering data from local storage faster than remote API calls while maintaining ACID compliance for cache consistency.

## Cache Lifecycle Management

### Automatic Expiration and Cleanup

Stale entries are purged automatically via the `auto_cleanup()` method, which removes rows where `expires < now` and `allow_expired_cache` is disabled. This task runs daily at 04:00 UTC, registered through `_register_cleanup_task()` (lines 33-44), preventing accumulation of obsolete data that could degrade query performance over time.

### Oversized Cache Protection

To prevent I/O slowdowns from excessive file growth, `_check_oversized_cache()` monitors the total size of `cache.db` plus its WAL and SHM companion files against the `MAX_CACHE_DB_SIZE_MB` threshold. Defined in [`music_assistant/controllers/cache/constants.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/cache/constants.py) with a default of **50 MiB**, this safeguard triggers a warning log when exceeded (lines 85-104), prompting administrative attention before disk performance degrades.

### Background Vacuuming and Maintenance

The controller schedules periodic database vacuuming to reclaim storage space from deleted rows. This background maintenance compacts the SQLite file when sufficient reclaimable space exists, maintaining consistent read/write throughput and preventing fragmentation-related slowdowns that typically afflict long-running database applications.

## Bypass and Refresh Patterns

### Stale-While-Revalidate Support

The cache implements a "stale-while-revalidate" pattern through the `allow_expired_cache` flag. When enabled during `CacheController.set()` (lines 71-78), expired entries remain available as fallback data while fresh content is fetched asynchronously. This ensures the user interface never blocks on network latency, even during cache refresh cycles.

### Temporary Bypass Logic

For scenarios requiring fresh data regardless of cache state, the `handle_refresh` context manager temporarily overrides the `BYPASS_CACHE` context variable. Implemented at lines 76-84 in [`music_assistant/controllers/cache/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/cache/controller.py), this utility allows specific code blocks to force cache misses—useful when synchronizing provider metadata or executing user-triggered refresh commands.

## Practical Implementation

The following example demonstrates storage, retrieval, and bypass patterns using the cache controller:

```python

# Assume `mass` is the running MusicAssistant instance

cache = mass.cache

# Store album metadata with 24-hour expiration

await cache.set(
    key="album:12345",
    data={"title": "Dreams", "artist": "Synthwave"},
    expiration=86400,
    provider="spotify",
    category=1,
    persistent=False,
)

# Retrieve the cached data

album = await cache.get(
    key="album:12345",
    provider="spotify",
    category=1,
    default={},
)

# Force a refresh for fresh data

async with cache.handle_refresh(bypass=True):
    fresh_data = await fetch_from_spotify()
    await cache.set(key="album:12345", data=fresh_data, expiration=86400)

```

Key observations from this implementation:

- `set()` automatically JSON-serializes the payload and handles expiration timestamps.
- `get()` returns deserialized data and respects the expiration logic.
- The `handle_refresh` context manager enables atomic bypass operations without global configuration changes.

## Summary

- **SQLite backend**: Uses `cache.db` with WAL mode for concurrent read/write performance.
- **Indexed lookups**: Multi-column indexes on `category`, `key`, and `provider` ensure O(log n) retrieval.
- **JSON serialization**: Lightweight storage format via `json_dumps` and `async_json_loads`.
- **Automatic maintenance**: Daily cleanup at 04:00 UTC removes expired entries; vacuuming reclaims space.
- **Size protection**: Monitors against `MAX_CACHE_DB_SIZE_MB` (50 MiB) to prevent disk saturation.
- **Flexible bypass**: `handle_refresh` context manager allows temporary cache invalidation for fresh data fetching.

## Frequently Asked Questions

### What storage backend does Music Assistant use for caching?

Music Assistant uses a local **SQLite database** (`cache.db`) located in the controller's data directory. The system leverages SQLite's built-in WAL (Write-Ahead Logging) mode for high-performance concurrent access, coupled with JSON serialization to ensure data portability across different Python versions and platforms.

### How does the cache controller prevent database bloat?

The controller implements a three-layer defense: automatic expiration cleanup via `auto_cleanup()`, oversized cache monitoring through `_check_oversized_cache()` against the 50 MiB `MAX_CACHE_DB_SIZE_MB` limit, and scheduled vacuuming to reclaim space from deleted rows. These mechanisms ensure the database file remains compact and responsive.

### Can I force Music Assistant to ignore cached data for specific operations?

Yes. Use the `handle_refresh` context manager with `bypass=True` to temporarily override the `BYPASS_CACHE` flag for a specific code block. This forces cache misses without disabling the cache globally, making it ideal for manual refresh operations or when fetching critical updated metadata from providers.

### What happens to expired cache entries?

By default, the `auto_cleanup()` task running daily at 04:00 UTC permanently deletes rows where the `expires` timestamp has passed. However, if the `allow_expired_cache` flag was set to `True` when the entry was created, the expired data persists as a fallback until explicitly overwritten or deleted, supporting stale-while-revalidate patterns for uninterrupted user experiences.