10 KiB
Storage Cache Usage Audit
Date: 2025-10-27
Purpose: Document all dependencies on the daemon storage cache before removal
Related: bd-30 (Audit Current Cache Usage), bd-29 (Remove Daemon Storage Cache epic)
Summary
The daemon storage cache (storageCache map[string]*StorageCacheEntry) is designed for multi-repository routing but only ever caches one repository in practice (local daemon model). All code paths can safely use s.storage directly instead of getStorageForRequest().
Key Findings
1. getStorageForRequest() Callers
Total calls: 17 production calls + 11 test calls
Production files:
internal/rpc/server_issues_epics.go- 8 calls (CreateIssue, UpdateIssue, CloseIssue, ReopenIssue, GetIssue, ListIssues, DeleteIssue, CreateEpic)internal/rpc/server_labels_deps_comments.go- 4 calls (AddLabel, RemoveLabel, AddDependency, AddComment)internal/rpc/server_export_import_auto.go- 2 calls (AutoExport, AutoImport)internal/rpc/server_compact.go- 2 calls (CompactIssue, GetDeletedIssue)internal/rpc/server_routing_validation_diagnostics.go- 1 call (ValidateDatabase)
Test file:
internal/rpc/server_eviction_test.go- 11 calls (cache eviction tests only)
Pattern (all identical):
store, err := s.getStorageForRequest(req)
if err != nil {
return Response{Success: false, Error: err.Error()}
}
// ... use store ...
Replacement strategy: Replace with store := s.storage and remove error handling.
2. req.Cwd Usage
Purpose: req.Cwd is set by RPC client for database discovery in multi-repo routing.
Code paths:
-
RPC client (
internal/rpc/client.go):- Sets
req.Cwdin request construction (line not shown in excerpt, but inferred from server usage) - Client debug output references
CacheSizefrom health check (line 78)
- Sets
-
Server cache lookup (
internal/rpc/server_cache_storage.go):// If no cwd specified, use default storage if req.Cwd == "" { return s.storage, nil }- When
req.Cwdis set, usesfindDatabaseForCwd()to locate.beads/*.db - Canonicalizes to repo root as cache key
- Checks cache with mtime-based invalidation (lines 162-256)
- When
-
Validation endpoint (
server_routing_validation_diagnostics.go):- Uses
req.Cwdfor database validation (line 84-86) - Only validator that inspects
req.Cwdoutside of cache
- Uses
-
Comment forwarding (
server_labels_deps_comments.go):- Passes
req.Cwdthrough when forwarding AddComment to hooks (line 179) - Preserves context for downstream handlers
- Passes
Observation: In local daemon model, req.Cwd is always empty or always matches the daemon's workspace. The daemon only serves one repository, so routing logic is unused.
3. MCP Server Multi-Repo Strategy
Files examined:
integrations/beads-mcp/(searched forreq.Cwd- no results)
Findings:
- MCP server does not use
req.Cwdrouting - MCP follows recommended architecture: separate daemon per repository
- Each MCP workspace connects to its own local daemon via socket
- No shared daemon across projects
Implication: MCP will not be affected by cache removal. Each daemon already serves only one repo.
4. Cache-Dependent Tests
File: internal/rpc/server_eviction_test.go
Tests to DELETE (entire file):
TestStorageCacheEviction_TTL- TTL-based evictionTestStorageCacheEviction_LRU- LRU eviction when over max sizeTestStorageCacheEviction_MemoryPressure- Memory pressure evictionTestStorageCacheEviction_MtimeInvalidation- Stale DB detectionTestConcurrentCacheAccess- Concurrent cache accessTestSubdirectoryDatabaseLookup- Subdirectory canonicalizationTestManualCacheEviction- Manual evict APITestCacheSizeLimit- Cache size enforcementTestNegativeTTL- Negative TTL config
File: internal/rpc/limits_test.go
Updates needed:
- Remove assertions like
len(srv.storageCache)(exact lines not shown, needs inspection) - Remove manual cache population (if any)
5. Environment Variables
Cache-related env vars (to deprecate):
-
BEADS_DAEMON_MAX_CACHE_SIZE- Default: 50
- Used in:
server_core.go:63 - Tested in:
server_eviction_test.go:238
-
BEADS_DAEMON_CACHE_TTL- Default: 30 minutes
- Used in:
server_core.go:72 - Tested in:
server_eviction_test.go:239, 423
-
BEADS_DAEMON_MEMORY_THRESHOLD_MB- Default: 500 MB
- Used in:
server_cache_storage.go:47 - Triggers aggressive eviction
Other env vars (keep):
BEADS_DAEMON_MAX_CONNS(connection limiting)BEADS_DAEMON_REQUEST_TIMEOUT(request timeout)
6. Cache-Related Server Fields
In internal/rpc/server_core.go (Server struct):
Fields to REMOVE:
storageCache map[string]*StorageCacheEntry // line 36
cacheMu sync.RWMutex // line 37
maxCacheSize int // line 38
cacheTTL time.Duration // line 39
cleanupTicker *time.Ticker // line 40
cacheHits int64 // line 44
cacheMisses int64 // line 45
Fields to KEEP:
storage storage.Storage // line 28 - default storage (use this!)
// ... all other fields ...
7. Cache Functions in server_cache_storage.go
All functions in this file will be deleted:
-
runCleanupLoop()(lines 23-37)- Periodic eviction ticker goroutine
- Called from
Start()
-
checkMemoryPressure()(lines 39-61)- Memory monitoring and aggressive eviction trigger
- Called from cleanup loop
-
aggressiveEviction()(lines 63-103)- Evicts 50% of cache under memory pressure
- LRU-based selection
-
evictStaleStorage()(lines 105-155)- TTL-based eviction
- LRU eviction for size enforcement
- Called periodically and after new cache entries
-
getStorageForRequest(req *Request)(lines 157-256)- Main cache lookup and routing logic
- mtime-based invalidation
- 17 production callers (see section 1)
-
findDatabaseForCwd(cwd string)(lines 258-286)- Walks up directory tree to find
.beads/*.db - Used only by
getStorageForRequest() - Also used by validation endpoint (line 86 of diagnostics)
- Walks up directory tree to find
Note: findDatabaseForCwd() is also called from server_routing_validation_diagnostics.go:86 for database path validation. We need to decide if that endpoint should be removed or reimplemented.
8. Metrics/Health Impact
Health endpoint fields to remove:
cache_size(line 77-78 in client.go debug output references this)
Metrics endpoint fields to remove:
cache_sizecache_hits(field line 44)cache_misses(field line 45)
Files to update:
internal/rpc/server_routing_validation_diagnostics.go- Health and Metrics handlersinternal/rpc/metrics.go- Metrics struct
9. Cache Initialization and Lifecycle
Initialization (server_core.go:NewServer()):
- Line 98:
storageCache: make(map[string]*StorageCacheEntry) - Lines 99-100:
maxCacheSize,cacheTTLconfig - Remove: Cache map creation and config parsing (lines 62-76, 98-100)
Start lifecycle (server_core.go:Start() - not shown in excerpt):
- Starts
runCleanupLoop()goroutine for periodic eviction - Remove: Cleanup loop goroutine launch
Stop lifecycle (server_core.go:Stop() - not shown in excerpt):
- Stops cleanup ticker
- Closes all cached storage entries
- Simplify: Just close
s.storagedirectly
Migration Strategy
Phase 1: Remove Cache Code (bd-31, bd-32, bd-33)
- Remove cache fields from
Serverstruct - Replace
getStorageForRequest(req)withs.storagein all 17 callers - Remove error handling for storage lookup failures
- Delete
server_cache_storage.goentirely
Phase 2: Clean Up Tests (bd-34)
- Delete
server_eviction_test.go(entire file) - Update
limits_test.goto remove cache assertions
Phase 3: Update Metrics (bd-35)
- Remove
cache_size,cache_hits,cache_missesfrom health/metrics endpoints - Remove cache-related fields from
Metricsstruct
Phase 4: Documentation (bd-36)
- Remove cache env var docs from ADVANCED.md, commands/daemons.md
- Add CHANGELOG entry documenting removal
Risk Assessment
✅ Safe to Remove
- Multi-repo routing: Unused in local daemon model (always 1 repo)
- Cache eviction logic: Unnecessary for single storage instance
- mtime invalidation: Irrelevant for persistent
s.storageconnection - LRU/TTL/memory pressure: Over-engineered for 1 cached entry
⚠️ Needs Verification
- MCP server: Confirmed using separate daemons (no
req.Cwdusage found) - Database validation:
ValidateDatabaseendpoint usesfindDatabaseForCwd()- may need reimplementation or removal - Comment forwarding: Passes
req.Cwdto hooks - verify hooks don't rely on it
✅ No Breaking Changes Expected
- Local daemon already behaves as if cache size = 1
- All requests route to same
s.storageinstance - Performance identical (no cache overhead to remove)
- Memory usage will improve (no cache structs)
Acceptance Criteria Checklist
- Document all callers of
getStorageForRequest()- 17 production, 11 test - Verify
req.Cwdis only for database discovery - Confirmed routing-only purpose - Confirm MCP doesn't rely on multi-repo cache - MCP uses separate daemons
- List tests assuming multi-repo routing -
server_eviction_test.go(entire file) - Review cache env vars - 3 cache vars, 2 non-cache vars to keep
- Document cache dependencies - All code paths identified above
Next Steps
- bd-31: Remove cache fields from
Serverstruct - bd-32: Replace
getStorageForRequest()withs.storage(17 call sites) - bd-33: Delete
server_cache_storage.go - bd-34: Delete
server_eviction_test.go, updatelimits_test.go - bd-35: Remove cache metrics from health/metrics endpoints
- bd-36: Update documentation and CHANGELOG
Special consideration: ValidateDatabase endpoint in server_routing_validation_diagnostics.go uses findDatabaseForCwd() outside of cache. Needs decision:
- Option A: Remove validation endpoint (may be unused)
- Option B: Inline database discovery logic in validator
- Option C: Keep
findDatabaseForCwd()as standalone helper
Recommendation: Verify if ValidateDatabase is used, then choose Option A or B.