github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/metacache-bucket.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "errors" 23 "runtime/debug" 24 "sort" 25 "sync" 26 "time" 27 28 "github.com/minio/minio/internal/logger" 29 "github.com/minio/pkg/v2/console" 30 ) 31 32 // a bucketMetacache keeps track of all caches generated 33 // for a bucket. 34 type bucketMetacache struct { 35 // Name of bucket 36 bucket string 37 38 // caches indexed by id. 39 caches map[string]metacache 40 // cache ids indexed by root paths 41 cachesRoot map[string][]string `msg:"-"` 42 43 // Internal state 44 mu sync.RWMutex `msg:"-"` 45 updated bool `msg:"-"` 46 } 47 48 type deleteAllStorager interface { 49 deleteAll(ctx context.Context, bucket, prefix string) 50 } 51 52 // newBucketMetacache creates a new bucketMetacache. 53 // Optionally remove all existing caches. 54 func newBucketMetacache(bucket string, cleanup bool) *bucketMetacache { 55 if cleanup { 56 // Recursively delete all caches. 57 objAPI := newObjectLayerFn() 58 if objAPI != nil { 59 ez, ok := objAPI.(deleteAllStorager) 60 if ok { 61 ctx := context.Background() 62 ez.deleteAll(ctx, minioMetaBucket, metacachePrefixForID(bucket, slashSeparator)) 63 } 64 } 65 } 66 return &bucketMetacache{ 67 bucket: bucket, 68 caches: make(map[string]metacache, 10), 69 cachesRoot: make(map[string][]string, 10), 70 } 71 } 72 73 func (b *bucketMetacache) debugf(format string, data ...interface{}) { 74 if serverDebugLog { 75 console.Debugf(format+"\n", data...) 76 } 77 } 78 79 // findCache will attempt to find a matching cache for the provided options. 80 // If a cache with the same ID exists already it will be returned. 81 // If none can be found a new is created with the provided ID. 82 func (b *bucketMetacache) findCache(o listPathOptions) metacache { 83 if b == nil { 84 logger.Info("bucketMetacache.findCache: nil cache for bucket %s", o.Bucket) 85 return metacache{} 86 } 87 88 if o.Bucket != b.bucket { 89 logger.Info("bucketMetacache.findCache: bucket %s does not match this bucket %s", o.Bucket, b.bucket) 90 debug.PrintStack() 91 return metacache{} 92 } 93 94 // Grab a write lock, since we create one if we cannot find one. 95 b.mu.Lock() 96 defer b.mu.Unlock() 97 98 // Check if exists already. 99 if c, ok := b.caches[o.ID]; ok { 100 c.lastHandout = time.Now() 101 b.caches[o.ID] = c 102 b.debugf("returning existing %v", o.ID) 103 return c 104 } 105 106 if !o.Create { 107 return metacache{ 108 id: o.ID, 109 bucket: o.Bucket, 110 status: scanStateNone, 111 } 112 } 113 114 // Create new and add. 115 best := o.newMetacache() 116 b.caches[o.ID] = best 117 b.cachesRoot[best.root] = append(b.cachesRoot[best.root], best.id) 118 b.updated = true 119 b.debugf("returning new cache %s, bucket: %v", best.id, best.bucket) 120 return best 121 } 122 123 // cleanup removes redundant and outdated entries. 124 func (b *bucketMetacache) cleanup() { 125 // Entries to remove. 126 remove := make(map[string]struct{}) 127 128 // Test on a copy 129 // cleanup is the only one deleting caches. 130 caches, _ := b.cloneCaches() 131 132 for id, cache := range caches { 133 if !cache.worthKeeping() { 134 b.debugf("cache %s not worth keeping", id) 135 remove[id] = struct{}{} 136 continue 137 } 138 if cache.id != id { 139 logger.Info("cache ID mismatch %s != %s", id, cache.id) 140 remove[id] = struct{}{} 141 continue 142 } 143 if cache.bucket != b.bucket { 144 logger.Info("cache bucket mismatch %s != %s", b.bucket, cache.bucket) 145 remove[id] = struct{}{} 146 continue 147 } 148 } 149 150 // If above limit, remove the caches with the oldest handout time. 151 if len(caches)-len(remove) > metacacheMaxEntries { 152 remainCaches := make([]metacache, 0, len(caches)-len(remove)) 153 for id, cache := range caches { 154 if _, ok := remove[id]; ok { 155 continue 156 } 157 remainCaches = append(remainCaches, cache) 158 } 159 if len(remainCaches) > metacacheMaxEntries { 160 // Sort oldest last... 161 sort.Slice(remainCaches, func(i, j int) bool { 162 return remainCaches[i].lastHandout.Before(remainCaches[j].lastHandout) 163 }) 164 // Keep first metacacheMaxEntries... 165 for _, cache := range remainCaches[metacacheMaxEntries:] { 166 if time.Since(cache.lastHandout) > metacacheMaxClientWait { 167 remove[cache.id] = struct{}{} 168 } 169 } 170 } 171 } 172 173 for id := range remove { 174 b.deleteCache(id) 175 } 176 } 177 178 // updateCacheEntry will update a cache. 179 // Returns the updated status. 180 func (b *bucketMetacache) updateCacheEntry(update metacache) (metacache, error) { 181 b.mu.Lock() 182 defer b.mu.Unlock() 183 existing, ok := b.caches[update.id] 184 if !ok { 185 return update, errFileNotFound 186 } 187 existing.update(update) 188 b.caches[update.id] = existing 189 b.updated = true 190 return existing, nil 191 } 192 193 // cloneCaches will return a clone of all current caches. 194 func (b *bucketMetacache) cloneCaches() (map[string]metacache, map[string][]string) { 195 b.mu.RLock() 196 defer b.mu.RUnlock() 197 dst := make(map[string]metacache, len(b.caches)) 198 for k, v := range b.caches { 199 dst[k] = v 200 } 201 // Copy indexes 202 dst2 := make(map[string][]string, len(b.cachesRoot)) 203 for k, v := range b.cachesRoot { 204 tmp := make([]string, len(v)) 205 copy(tmp, v) 206 dst2[k] = tmp 207 } 208 209 return dst, dst2 210 } 211 212 // deleteAll will delete all on disk data for ALL caches. 213 // Deletes are performed concurrently. 214 func (b *bucketMetacache) deleteAll() { 215 ctx := context.Background() 216 217 objAPI := newObjectLayerFn() 218 if objAPI == nil { 219 return 220 } 221 222 ez, ok := objAPI.(deleteAllStorager) 223 if !ok { 224 logger.LogIf(ctx, errors.New("bucketMetacache: expected objAPI to be 'deleteAllStorager'")) 225 return 226 } 227 228 b.mu.Lock() 229 defer b.mu.Unlock() 230 231 b.updated = true 232 // Delete all. 233 ez.deleteAll(ctx, minioMetaBucket, metacachePrefixForID(b.bucket, slashSeparator)) 234 b.caches = make(map[string]metacache, 10) 235 b.cachesRoot = make(map[string][]string, 10) 236 } 237 238 // deleteCache will delete a specific cache and all files related to it across the cluster. 239 func (b *bucketMetacache) deleteCache(id string) { 240 b.mu.Lock() 241 c, ok := b.caches[id] 242 if ok { 243 // Delete from root map. 244 list := b.cachesRoot[c.root] 245 for i, lid := range list { 246 if id == lid { 247 list = append(list[:i], list[i+1:]...) 248 break 249 } 250 } 251 b.cachesRoot[c.root] = list 252 delete(b.caches, id) 253 b.updated = true 254 } 255 b.mu.Unlock() 256 if ok { 257 c.delete(context.Background()) 258 } 259 }