watchdog/internal/aggregate/registry.go
NotAShelf bb56df6423
internal/aggregate: optimize path registry for read-heavy workloads
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Ibc477830b471fe09838b7477fe73ffa56a6a6964
2026-03-02 22:38:02 +03:00

81 lines
1.9 KiB
Go

package aggregate
import (
"sync"
)
// Maintain a bounded set of unique request paths. This prevents metric cardinality explosion by rejecting new paths
// once the configured limit is reached.
type PathRegistry struct {
mu sync.RWMutex
paths map[string]struct{}
maxPaths int
overflowCount int
}
// Creates a new PathRegistry with the specified maximum number of unique paths.
// Once this limit is reached, subsequent Add() calls for new paths will be rejected.
func NewPathRegistry(maxPaths int) *PathRegistry {
return &PathRegistry{
paths: make(map[string]struct{}, maxPaths),
maxPaths: maxPaths,
}
}
// Add attempts to add a path to the registry.
// Returns true if the path was accepted: either already existed or was added,
// false if rejected due to reaching the limit.
func (r *PathRegistry) Add(path string) bool {
// Fast path: check with read lock first
r.mu.RLock()
if _, exists := r.paths[path]; exists {
r.mu.RUnlock()
return true
}
r.mu.RUnlock()
// Slow path: acquire write lock to add
r.mu.Lock()
defer r.mu.Unlock()
// Double-check after acquiring write lock
if _, exists := r.paths[path]; exists {
return true
}
// If we haven't reached the limit, add the path
if len(r.paths) < r.maxPaths {
r.paths[path] = struct{}{}
return true
}
// Limit reached, reject and increment overflow
r.overflowCount++
return false
}
// Contains checks if a path exists in the registry.
func (r *PathRegistry) Contains(path string) bool {
r.mu.RLock()
defer r.mu.RUnlock()
_, exists := r.paths[path]
return exists
}
// Count returns the number of unique paths in the registry.
func (r *PathRegistry) Count() int {
r.mu.RLock()
defer r.mu.RUnlock()
return len(r.paths)
}
// Returns the number of paths that were rejected due to the registry being at
// capacity.
func (r *PathRegistry) OverflowCount() int {
r.mu.RLock()
defer r.mu.RUnlock()
return r.overflowCount
}