github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/metacache.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 "fmt" 24 "path" 25 "strings" 26 "time" 27 28 "github.com/minio/minio/internal/logger" 29 ) 30 31 type scanStatus uint8 32 33 const ( 34 scanStateNone scanStatus = iota 35 scanStateStarted 36 scanStateSuccess 37 scanStateError 38 39 // Time in which the initiator of a scan must have reported back. 40 metacacheMaxRunningAge = time.Minute 41 42 // Max time between client calls before dropping an async cache listing. 43 metacacheMaxClientWait = 3 * time.Minute 44 45 // metacacheBlockSize is the number of file/directory entries to have in each block. 46 metacacheBlockSize = 5000 47 48 // metacacheSharePrefix controls whether prefixes on dirty paths are always shared. 49 // This will make `test/a` and `test/b` share listings if they are concurrent. 50 // Enabling this will make cache sharing more likely and cause less IO, 51 // but may cause additional latency to some calls. 52 metacacheSharePrefix = false 53 ) 54 55 //go:generate msgp -file $GOFILE -unexported 56 57 // metacache contains a tracked cache entry. 58 type metacache struct { 59 // do not re-arrange the struct this struct has been ordered to use less 60 // space - if you do so please run https://github.com/orijtech/structslop 61 // and verify if your changes are optimal. 62 ended time.Time `msg:"end"` 63 started time.Time `msg:"st"` 64 lastHandout time.Time `msg:"lh"` 65 lastUpdate time.Time `msg:"u"` 66 bucket string `msg:"b"` 67 filter string `msg:"flt"` 68 id string `msg:"id"` 69 error string `msg:"err"` 70 root string `msg:"root"` 71 fileNotFound bool `msg:"fnf"` 72 status scanStatus `msg:"stat"` 73 recursive bool `msg:"rec"` 74 dataVersion uint8 `msg:"v"` 75 } 76 77 func (m *metacache) finished() bool { 78 return !m.ended.IsZero() 79 } 80 81 // worthKeeping indicates if the cache by itself is worth keeping. 82 func (m *metacache) worthKeeping() bool { 83 if m == nil { 84 return false 85 } 86 cache := m 87 switch { 88 case !cache.finished() && time.Since(cache.lastUpdate) > metacacheMaxRunningAge: 89 // Not finished and update for metacacheMaxRunningAge, discard it. 90 return false 91 case cache.finished() && time.Since(cache.lastHandout) > 5*metacacheMaxClientWait: 92 // Keep for 15 minutes after we last saw the client. 93 // Since the cache is finished keeping it a bit longer doesn't hurt us. 94 return false 95 case cache.status == scanStateError || cache.status == scanStateNone: 96 // Remove failed listings after 5 minutes. 97 return time.Since(cache.lastUpdate) > 5*time.Minute 98 } 99 return true 100 } 101 102 // baseDirFromPrefix will return the base directory given an object path. 103 // For example an object with name prefix/folder/object.ext will return `prefix/folder/`. 104 func baseDirFromPrefix(prefix string) string { 105 b := path.Dir(prefix) 106 if b == "." || b == "./" || b == "/" { 107 b = "" 108 } 109 if !strings.Contains(prefix, slashSeparator) { 110 b = "" 111 } 112 if len(b) > 0 && !strings.HasSuffix(b, slashSeparator) { 113 b += slashSeparator 114 } 115 return b 116 } 117 118 // update cache with new status. 119 // The updates are conditional so multiple callers can update with different states. 120 func (m *metacache) update(update metacache) { 121 m.lastUpdate = UTCNow() 122 123 if m.lastHandout.After(m.lastHandout) { 124 m.lastHandout = UTCNow() 125 } 126 if m.status == scanStateStarted && update.status == scanStateSuccess { 127 m.ended = UTCNow() 128 } 129 130 if m.status == scanStateStarted && update.status != scanStateStarted { 131 m.status = update.status 132 } 133 134 if m.status == scanStateStarted && time.Since(m.lastHandout) > metacacheMaxClientWait { 135 // Drop if client hasn't been seen for 3 minutes. 136 m.status = scanStateError 137 m.error = "client not seen" 138 } 139 140 if m.error == "" && update.error != "" { 141 m.error = update.error 142 m.status = scanStateError 143 m.ended = UTCNow() 144 } 145 m.fileNotFound = m.fileNotFound || update.fileNotFound 146 } 147 148 // delete all cache data on disks. 149 func (m *metacache) delete(ctx context.Context) { 150 if m.bucket == "" || m.id == "" { 151 logger.LogIf(ctx, fmt.Errorf("metacache.delete: bucket (%s) or id (%s) empty", m.bucket, m.id)) 152 } 153 objAPI := newObjectLayerFn() 154 if objAPI == nil { 155 logger.LogIf(ctx, errors.New("metacache.delete: no object layer")) 156 return 157 } 158 ez, ok := objAPI.(deleteAllStorager) 159 if !ok { 160 logger.LogIf(ctx, errors.New("metacache.delete: expected objAPI to be 'deleteAllStorager'")) 161 return 162 } 163 ez.deleteAll(ctx, minioMetaBucket, metacachePrefixForID(m.bucket, m.id)) 164 }