# Cache Controller Functionality and Supported Backends in Music Assistant

> Explore Music Assistant's cache controller functionality. Discover how it uses SQLite for expiration-aware storage, namespaced keys, and stale-while-revalidate patterns to optimize application performance.

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

---

**The Cache Controller provides an expiration-aware key/value store using SQLite as its sole supported backend, offering namespaced storage, stale-while-revalidate patterns, and automatic database cleanup for the Music Assistant application.**

The `music-assistant/server` repository implements a centralized **Cache Controller** at [`music_assistant/controllers/cache/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/cache/controller.py) to manage temporary data across all music providers. This component serves as the primary caching layer, handling API responses, metadata storage, and provider-specific data with configurable expiration policies and checksum validation.

## How the Cache Controller Works

### Persistent Storage with SQLite

The controller persists all cache entries in an SQLite database file located at `$HOME/.musicassistant/cache.db`. It leverages the `DatabaseConnection` helper from [`music_assistant/helpers/database.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/database.py) to execute async operations against this single database file, providing durability across application restarts.

### Namespacing by Provider and Category

Every cache entry is scoped by **provider** and **category** parameters. This isolation prevents collisions between different music providers (e.g., Spotify, YouTube Music) while allowing each service to maintain its own cached data namespaces within the shared database.

### Expiration and Stale-While-Revalidate

Each entry stores an `expires` timestamp upon creation. The `get` method supports an `allow_expired_cache=True` parameter that returns expired entries when fresh data is unavailable. This stale-while-revalidate pattern, utilized by the `@use_cache` decorator, allows the application to serve cached data while triggering background refresh operations.

### Checksum Validation

The controller supports optional **checksum validation** through the `checksum` parameter in the `set` method. When retrieving data via `get`, the controller verifies that the stored checksum matches the requested value, ensuring data integrity for critical cached responses.

### Persistence Flag

When setting cache entries with `persistent=True`, the data survives normal `clear` operations. This flag protects essential cached data during user-initiated cache clears while allowing routine cleanup of transient entries.

### Bypass Support

A `BYPASS_CACHE` context variable enables temporary cache disablement for specific requests. This mechanism supports debugging scenarios and forced refresh operations without modifying the underlying cached data.

### Automatic Cleanup

The controller registers a daily background task identified by `CACHE_DATABASE_CLEANUP_TASK_ID` that removes expired rows not marked with `allow_expired_cache`. It also monitors the total database size against `MAX_CACHE_DB_SIZE_MB` and emits warnings when the cache database exceeds configured thresholds.

## Supported Caching Backends

### SQLite as the Sole Backend

The Cache Controller currently supports **only one backend**: an **SQLite database**. While the architecture abstracts database operations through `DatabaseConnection`, no alternative storage engines such as Redis, in-memory dictionaries, or file-based JSON are implemented in the repository.

All cache operations—including `insert_or_replace`, `get_row`, `delete`, and `execute`—target the single SQLite file. Features like "persistent" entries and "allow_expired_cache" operate within this same physical database, using schema flags rather than separate storage backends.

## Implementation Files and Architecture

### Core Controller Implementation

The file [`music_assistant/controllers/cache/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/cache/controller.py) contains the full implementation including initialization, CRUD operations, cleanup logic, and background task registration. This class inherits from the base controller defined in [`music_assistant/models/core_controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/models/core_controller.py), which provides manifest handling and lifecycle hooks.

### Database Abstraction Layer

Located at [`music_assistant/helpers/database.py`](https://github.com/music-assistant/server/blob/main/music_assistant/helpers/database.py), this module provides a minimal wrapper around `aiosqlite` that exposes async database operations used exclusively by the cache controller for SQLite interactions.

### Configuration Constants

The file [`music_assistant/controllers/cache/constants.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/cache/constants.py) defines schema versions, default expiration intervals, and the `MAX_CACHE_DB_SIZE_MB` threshold utilized by the cleanup routines.

## Practical Usage Examples

The following patterns demonstrate typical Cache Controller interactions from provider implementations:

```python

# Store API response with 2-hour expiration

await mass.cache.set(
    key="album_details:12345",
    data={"title": "Great Album", "artist": "Cool Band"},
    expiration=2 * 60 * 60,
    provider="spotify",
    category=0,
    persistent=False,
)

```

```python

# Retrieve cached data with fallback default

album = await mass.cache.get(
    key="album_details:12345",
    provider="spotify",
    category=0,
    default=None,
)

```

```python

# Clear non-persistent entries for specific provider

await mass.cache.clear(provider_filter="spotify", include_persistent=False)

```

```python

# Bypass cache for forced refresh

async with mass.cache.handle_refresh(bypass=True):
    fresh_data = await some_api_call()

```

## Summary

- The Cache Controller in [`music_assistant/controllers/cache/controller.py`](https://github.com/music-assistant/server/blob/main/music_assistant/controllers/cache/controller.py) provides the core caching infrastructure for the Music Assistant application using SQLite as its only supported backend.
- It implements **namespacing** via provider and category parameters to isolate data between different music services.
- **Expiration-aware storage** supports stale-while-revalidate patterns through `allow_expired_cache` and includes checksum validation for data integrity.
- The **persistence flag** protects critical data from routine cache clears, while the **bypass mechanism** enables debugging and forced refreshes.
- Automatic maintenance includes daily cleanup of expired entries and database size monitoring against `MAX_CACHE_DB_SIZE_MB`.

## Frequently Asked Questions

### What caching backends does the Cache Controller support?

The Cache Controller supports **only SQLite** as its caching backend, storing data in `$HOME/.musicassistant/cache.db`. While the code abstracts database operations through `DatabaseConnection`, no Redis, Memcached, or in-memory alternatives are implemented in the current codebase.

### How does the stale-while-revalidate pattern work?

When `allow_expired_cache=True` is passed to the `get` method, the controller returns expired entries if present, allowing the application to serve stale data while triggering background refresh operations. This pattern is primarily utilized through the `@use_cache` decorator.

### Where is the cache physically stored on disk?

All cache data resides in an SQLite database file located at `$HOME/.musicassistant/cache.db`. This includes both persistent and non-persistent entries, with differentiation handled via database flags rather than separate files.

### How can developers bypass the cache for debugging?

Developers can use the `BYPASS_CACHE` context variable through the `handle_refresh` context manager with `bypass=True`. This temporarily disables cache reads for the scope of the request without deleting existing cached data, enabling forced API refreshes during debugging.