99 lines
2.3 KiB
Go
99 lines
2.3 KiB
Go
package storage
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// HoldCache caches hold DIDs for (DID, repository) pairs
|
|
// This avoids expensive ATProto lookups on every blob request during pulls
|
|
//
|
|
// NOTE: This is a simple in-memory cache for MVP. For production deployments:
|
|
// - Use Redis or similar for distributed caching
|
|
// - Consider implementing cache size limits
|
|
// - Monitor memory usage under high load
|
|
type HoldCache struct {
|
|
mu sync.RWMutex
|
|
cache map[string]*holdCacheEntry
|
|
}
|
|
|
|
type holdCacheEntry struct {
|
|
holdDID string
|
|
expiresAt time.Time
|
|
}
|
|
|
|
var globalHoldCache = &HoldCache{
|
|
cache: make(map[string]*holdCacheEntry),
|
|
}
|
|
|
|
func init() {
|
|
// Start background cleanup goroutine
|
|
go func() {
|
|
ticker := time.NewTicker(5 * time.Minute)
|
|
defer ticker.Stop()
|
|
for range ticker.C {
|
|
globalHoldCache.Cleanup()
|
|
}
|
|
}()
|
|
}
|
|
|
|
// GetGlobalHoldCache returns the global hold cache instance
|
|
func GetGlobalHoldCache() *HoldCache {
|
|
return globalHoldCache
|
|
}
|
|
|
|
// Set stores a hold DID for a (DID, repository) pair with a TTL
|
|
func (c *HoldCache) Set(did, repository, holdDID string, ttl time.Duration) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
key := did + ":" + repository
|
|
c.cache[key] = &holdCacheEntry{
|
|
holdDID: holdDID,
|
|
expiresAt: time.Now().Add(ttl),
|
|
}
|
|
}
|
|
|
|
// Get retrieves a hold DID for a (DID, repository) pair
|
|
// Returns empty string and false if not found or expired
|
|
func (c *HoldCache) Get(did, repository string) (string, bool) {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
|
|
key := did + ":" + repository
|
|
entry, ok := c.cache[key]
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
|
|
// Check if expired
|
|
if time.Now().After(entry.expiresAt) {
|
|
// Don't delete here (would need write lock), let cleanup handle it
|
|
return "", false
|
|
}
|
|
|
|
return entry.holdDID, true
|
|
}
|
|
|
|
// Cleanup removes expired entries (called automatically every 5 minutes)
|
|
func (c *HoldCache) Cleanup() {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
|
|
now := time.Now()
|
|
removed := 0
|
|
for key, entry := range c.cache {
|
|
if now.After(entry.expiresAt) {
|
|
delete(c.cache, key)
|
|
removed++
|
|
}
|
|
}
|
|
|
|
// Log cleanup stats for monitoring
|
|
if removed > 0 || len(c.cache) > 100 {
|
|
// Log if we removed entries OR if cache is growing large
|
|
// This helps identify if cache size is becoming a concern
|
|
println("Hold cache cleanup: removed", removed, "entries, remaining", len(c.cache))
|
|
}
|
|
}
|