NewBadgerCache creates a new Badger-based cache.
(dir string)
| 21 | |
| 22 | // NewBadgerCache creates a new Badger-based cache. |
| 23 | func NewBadgerCache(dir string) (*BadgerCache, error) { |
| 24 | // Ensure the directory exists |
| 25 | if err := os.MkdirAll(dir, 0o750); err != nil { |
| 26 | return nil, fmt.Errorf("failed to create badger directory: %w", err) |
| 27 | } |
| 28 | |
| 29 | // If there's a lock file but no process using it, remove it |
| 30 | lockFilePath := dir + "/LOCK" |
| 31 | if _, err := os.Stat(lockFilePath); err == nil { |
| 32 | // Lock file exists, check if it's stale |
| 33 | if isStale, err := isLockFileStale(lockFilePath); err != nil { |
| 34 | getCacheLogger().Debug("Failed to check if lock file is stale: %v", err) |
| 35 | } else if isStale { |
| 36 | getCacheLogger().Debug("Removing stale lock file") |
| 37 | |
| 38 | if err := os.Remove(lockFilePath); err != nil { |
| 39 | return nil, fmt.Errorf("failed to remove stale lock file: %w", err) |
| 40 | } |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | // Open the Badger database with default options |
| 45 | opts := badger.DefaultOptions(dir) |
| 46 | // Reduce logging noise |
| 47 | opts.Logger = nil |
| 48 | // Use a smaller value size to reduce memory usage |
| 49 | opts.ValueLogFileSize = 1 << 20 // 1MB |
| 50 | // Use memory mapping for values to improve performance |
| 51 | // Note: In badger v4, these options may have been renamed or removed |
| 52 | // opts.ValueLogLoadingMode = badger.MemoryMap |
| 53 | |
| 54 | db, err := badger.Open(opts) |
| 55 | if err != nil { |
| 56 | // Check if the error is due to resource temporarily unavailable |
| 57 | if os.IsExist(err) || isErrorTemporarilyUnavailable(err) { |
| 58 | return nil, fmt.Errorf("failed to open badger database (likely another process is using it): %w", err) |
| 59 | } |
| 60 | |
| 61 | return nil, fmt.Errorf("failed to open badger database: %w", err) |
| 62 | } |
| 63 | |
| 64 | cache := &BadgerCache{ |
| 65 | db: db, |
| 66 | stopGC: make(chan struct{}), |
| 67 | } |
| 68 | |
| 69 | // Run garbage collection in the background with proper cleanup |
| 70 | go func() { |
| 71 | ticker := time.NewTicker(5 * time.Minute) |
| 72 | defer ticker.Stop() |
| 73 | |
| 74 | for { |
| 75 | select { |
| 76 | case <-ticker.C: |
| 77 | err := db.RunValueLogGC(0.5) // Run GC if 50% or more space can be reclaimed |
| 78 | if err != nil && err != badger.ErrNoRewrite { |
| 79 | getCacheLogger().Debug("Badger value log GC failed: %v", err) |
| 80 | } |