Caching Strategy
Relevant source files
The following files were used as context for generating this wiki page:The dokku-mcp server implements an in-memory caching strategy to optimize interactions with the Dokku host. By caching the results of frequently executed, read-only Dokku commands, the system significantly reduces the number of SSH connections, lowers latency, and decreases the load on the Dokku server.
The cache is TTL-based (Time-To-Live) and features command-specific policies, automatic background cleanup of expired entries, and a mechanism for manual invalidation. This system is primarily managed within the dokku-api package and is transparent to the higher-level server plugins that consume the Dokku client.
Architecture
The caching system is built around a few core components that work together to manage the lifecycle of cached command results. The client struct, which implements the DokkuClient interface, holds an instance of the CommandCacheManager.
Sources: internal/dokku-api/client.go:88-90, internal/dokku-api/client.types.go:50-53
Core Components
The diagram below illustrates the relationships between the main caching components.
- CommandCacheManager: The central component that orchestrates caching logic. It is responsible for getting, setting, and invalidating cache entries, as well as managing the background cleanup process.
- CacheConfig: A data structure holding the configuration for the cache, including whether it's enabled, the default TTL, and a map of command-specific TTL policies.
- commandCache: An internal, thread-safe (using
sync.RWMutex) map that stores the actual cache entries. - cacheEntry: Represents a single cached item, containing the command's result (
[]byte), any error that occurred, and its expiration timestamp.
Command Execution Flow
When a command is executed through client.ExecuteCommand, it first consults the cache. This "cache-aside" pattern ensures that only non-cached or expired commands result in an actual SSH call to the Dokku host.
The following sequence diagram shows the logic flow for a command execution.
Sources: internal/dokku-api/client.go:102-114
Cache Key Generation
To ensure that each unique command and argument combination is cached separately, a unique key is generated. The key is a 16-character SHA256 hash derived from the command name and all its arguments. This prevents collisions, for example, between apps:list and apps:report my-app.
// internal/dokku-api/cache.go:119-127
func (cm *CommandCacheManager) generateCacheKey(command string, args []string) string {
hasher := sha256.New()
hasher.Write([]byte(command))
for _, arg := range args {
hasher.Write([]byte(arg))
}
return hex.EncodeToString(hasher.Sum(nil))[:16] // First 16 chars
}
Sources: internal/dokku-api/cache.go:119-127
Expiration and Cleanup
The cache employs a two-pronged strategy for managing stale data: Time-To-Live (TTL) on a per-entry basis and a periodic background cleanup task.
Time-To-Live (TTL) Policies
Each entry is stored with an expiresAt timestamp. The duration until expiration (TTL) is determined by the CacheConfig. A DefaultTTL is used unless a specific policy for the command exists in the Policies map.
The default configuration defines several policies for commands with different data volatility.
| Command | Default TTL | Description |
|---|---|---|
logs | 30 seconds | Logs change very frequently. |
ps:scale | 1 minute | Process scaling is semi-frequent. |
apps:exists | 2 minutes | App existence is semi-stable. |
config:show | 5 minutes | Configuration is moderately stable. |
domains:report | 5 minutes | Domain reports are moderately stable. |
plugin:list | 15 minutes | The list of installed plugins is stable. |
version | 30 minutes | The Dokku version is very stable. |
ssh-keys:list | 10 minutes | SSH keys change infrequently. |
| (default) | 5 minutes | Default for all other commands. |
Sources: internal/dokku-api/cache.types.go:13-28
Background Cleanup
A background goroutine runs periodically to purge expired entries from the cache, preventing unbounded memory growth. This process is initiated by startCleanup when the CommandCacheManager is created.
The cleanup task is triggered by a time.Ticker set to run at an interval of half the DefaultTTL.
This diagram shows the flow of the background cleanup process.
Manual Invalidation
In addition to automatic expiration, the entire cache can be cleared on demand by calling the Invalidate() method on the CommandCacheManager. This is useful when an external change is known to have occurred that would render the cache stale.
Sources: internal/dokku-api/cache.go:94-103
Configuration
The caching behavior is configured via the cache section in the main config.yaml file or corresponding environment variables. The ClientConfig struct holds a pointer to the CacheConfig.
Sources: internal/dokku-api/client.types.go:42
| Field | Type | Description |
|---|---|---|
enabled | bool | Globally enables or disables the caching feature. |
default_ttl | time.Duration | The default time-to-live for cache entries without a specific policy. |
policies | map[string]time.Duration | A map of Dokku commands to their specific TTL durations. |
Sources: internal/dokku-api/cache.types.go:8-12
An example from the local development setup script shows caching being explicitly disabled:
# scripts/setup-dokku-local.sh
# ...
# Caching configuration
cache_enabled: false
# ...
Sources: scripts/setup-dokku-local.sh:91-92
To enable it, the configuration would look like this:
cache:
enabled: true
default_ttl: 5m
policies:
plugin:list: 15m
version: 30m
Summary
The caching strategy in dokku-mcp is an essential performance feature that minimizes direct communication with the Dokku host. It uses an in-memory, TTL-based approach with command-specific policies to ensure a good balance between data freshness and performance. The system is self-maintaining, with an automatic background cleanup process, but also allows for manual invalidation when needed. Its behavior is fully configurable, allowing operators to tune caching to their specific needs.