You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							195 lines
						
					
					
						
							4.7 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							195 lines
						
					
					
						
							4.7 KiB
						
					
					
				
								package log_buffer
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"container/list"
							 | 
						|
									"sync"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// DiskBufferCache is a small LRU cache for recently-read historical data buffers
							 | 
						|
								// This reduces Filer load when multiple consumers are catching up on historical messages
							 | 
						|
								type DiskBufferCache struct {
							 | 
						|
									maxSize   int
							 | 
						|
									ttl       time.Duration
							 | 
						|
									cache     map[string]*cacheEntry
							 | 
						|
									lruList   *list.List
							 | 
						|
									mu        sync.RWMutex
							 | 
						|
									hits      int64
							 | 
						|
									misses    int64
							 | 
						|
									evictions int64
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								type cacheEntry struct {
							 | 
						|
									key        string
							 | 
						|
									data       []byte
							 | 
						|
									offset     int64
							 | 
						|
									timestamp  time.Time
							 | 
						|
									lruElement *list.Element
							 | 
						|
									isNegative bool // true if this is a negative cache entry (data not found)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// NewDiskBufferCache creates a new cache with the specified size and TTL
							 | 
						|
								// Recommended size: 3-5 buffers (each ~8MB)
							 | 
						|
								// Recommended TTL: 30-60 seconds
							 | 
						|
								func NewDiskBufferCache(maxSize int, ttl time.Duration) *DiskBufferCache {
							 | 
						|
									cache := &DiskBufferCache{
							 | 
						|
										maxSize: maxSize,
							 | 
						|
										ttl:     ttl,
							 | 
						|
										cache:   make(map[string]*cacheEntry),
							 | 
						|
										lruList: list.New(),
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Start background cleanup goroutine
							 | 
						|
									go cache.cleanupLoop()
							 | 
						|
								
							 | 
						|
									return cache
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Get retrieves a buffer from the cache
							 | 
						|
								// Returns (data, offset, found)
							 | 
						|
								// If found=true and data=nil, this is a negative cache entry (data doesn't exist)
							 | 
						|
								func (c *DiskBufferCache) Get(key string) ([]byte, int64, bool) {
							 | 
						|
									c.mu.Lock()
							 | 
						|
									defer c.mu.Unlock()
							 | 
						|
								
							 | 
						|
									entry, exists := c.cache[key]
							 | 
						|
									if !exists {
							 | 
						|
										c.misses++
							 | 
						|
										return nil, 0, false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check if entry has expired
							 | 
						|
									if time.Since(entry.timestamp) > c.ttl {
							 | 
						|
										c.evict(entry)
							 | 
						|
										c.misses++
							 | 
						|
										return nil, 0, false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Move to front of LRU list (most recently used)
							 | 
						|
									c.lruList.MoveToFront(entry.lruElement)
							 | 
						|
									c.hits++
							 | 
						|
								
							 | 
						|
									if entry.isNegative {
							 | 
						|
										glog.V(4).Infof("📦 CACHE HIT (NEGATIVE): key=%s - data not found (hits=%d misses=%d)",
							 | 
						|
											key, c.hits, c.misses)
							 | 
						|
									} else {
							 | 
						|
										glog.V(4).Infof("📦 CACHE HIT: key=%s offset=%d size=%d (hits=%d misses=%d)",
							 | 
						|
											key, entry.offset, len(entry.data), c.hits, c.misses)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return entry.data, entry.offset, true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Put adds a buffer to the cache
							 | 
						|
								// If data is nil, this creates a negative cache entry (data doesn't exist)
							 | 
						|
								func (c *DiskBufferCache) Put(key string, data []byte, offset int64) {
							 | 
						|
									c.mu.Lock()
							 | 
						|
									defer c.mu.Unlock()
							 | 
						|
								
							 | 
						|
									isNegative := data == nil
							 | 
						|
								
							 | 
						|
									// Check if entry already exists
							 | 
						|
									if entry, exists := c.cache[key]; exists {
							 | 
						|
										// Update existing entry
							 | 
						|
										entry.data = data
							 | 
						|
										entry.offset = offset
							 | 
						|
										entry.timestamp = time.Now()
							 | 
						|
										entry.isNegative = isNegative
							 | 
						|
										c.lruList.MoveToFront(entry.lruElement)
							 | 
						|
										if isNegative {
							 | 
						|
											glog.V(4).Infof("📦 CACHE UPDATE (NEGATIVE): key=%s - data not found", key)
							 | 
						|
										} else {
							 | 
						|
											glog.V(4).Infof("📦 CACHE UPDATE: key=%s offset=%d size=%d", key, offset, len(data))
							 | 
						|
										}
							 | 
						|
										return
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Evict oldest entry if cache is full
							 | 
						|
									if c.lruList.Len() >= c.maxSize {
							 | 
						|
										oldest := c.lruList.Back()
							 | 
						|
										if oldest != nil {
							 | 
						|
											c.evict(oldest.Value.(*cacheEntry))
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Add new entry
							 | 
						|
									entry := &cacheEntry{
							 | 
						|
										key:        key,
							 | 
						|
										data:       data,
							 | 
						|
										offset:     offset,
							 | 
						|
										timestamp:  time.Now(),
							 | 
						|
										isNegative: isNegative,
							 | 
						|
									}
							 | 
						|
									entry.lruElement = c.lruList.PushFront(entry)
							 | 
						|
									c.cache[key] = entry
							 | 
						|
								
							 | 
						|
									if isNegative {
							 | 
						|
										glog.V(4).Infof("📦 CACHE PUT (NEGATIVE): key=%s - data not found (cache_size=%d/%d)",
							 | 
						|
											key, c.lruList.Len(), c.maxSize)
							 | 
						|
									} else {
							 | 
						|
										glog.V(4).Infof("📦 CACHE PUT: key=%s offset=%d size=%d (cache_size=%d/%d)",
							 | 
						|
											key, offset, len(data), c.lruList.Len(), c.maxSize)
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// evict removes an entry from the cache (must be called with lock held)
							 | 
						|
								func (c *DiskBufferCache) evict(entry *cacheEntry) {
							 | 
						|
									delete(c.cache, entry.key)
							 | 
						|
									c.lruList.Remove(entry.lruElement)
							 | 
						|
									c.evictions++
							 | 
						|
									glog.V(4).Infof("📦 CACHE EVICT: key=%s (evictions=%d)", entry.key, c.evictions)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// cleanupLoop periodically removes expired entries
							 | 
						|
								func (c *DiskBufferCache) cleanupLoop() {
							 | 
						|
									ticker := time.NewTicker(c.ttl / 2)
							 | 
						|
									defer ticker.Stop()
							 | 
						|
								
							 | 
						|
									for range ticker.C {
							 | 
						|
										c.cleanup()
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// cleanup removes expired entries
							 | 
						|
								func (c *DiskBufferCache) cleanup() {
							 | 
						|
									c.mu.Lock()
							 | 
						|
									defer c.mu.Unlock()
							 | 
						|
								
							 | 
						|
									now := time.Now()
							 | 
						|
									var toEvict []*cacheEntry
							 | 
						|
								
							 | 
						|
									// Find expired entries
							 | 
						|
									for _, entry := range c.cache {
							 | 
						|
										if now.Sub(entry.timestamp) > c.ttl {
							 | 
						|
											toEvict = append(toEvict, entry)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Evict expired entries
							 | 
						|
									for _, entry := range toEvict {
							 | 
						|
										c.evict(entry)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if len(toEvict) > 0 {
							 | 
						|
										glog.V(3).Infof("📦 CACHE CLEANUP: evicted %d expired entries", len(toEvict))
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Stats returns cache statistics
							 | 
						|
								func (c *DiskBufferCache) Stats() (hits, misses, evictions int64, size int) {
							 | 
						|
									c.mu.RLock()
							 | 
						|
									defer c.mu.RUnlock()
							 | 
						|
									return c.hits, c.misses, c.evictions, c.lruList.Len()
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// Clear removes all entries from the cache
							 | 
						|
								func (c *DiskBufferCache) Clear() {
							 | 
						|
									c.mu.Lock()
							 | 
						|
									defer c.mu.Unlock()
							 | 
						|
								
							 | 
						|
									c.cache = make(map[string]*cacheEntry)
							 | 
						|
									c.lruList = list.New()
							 | 
						|
									glog.V(2).Infof("📦 CACHE CLEARED")
							 | 
						|
								}
							 |