github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/core/lcache.go (about) 1 // Package core provides core metadata and in-cluster API 2 /* 3 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 4 */ 5 package core 6 7 import ( 8 "sync" 9 "time" 10 11 "github.com/NVIDIA/aistore/cmn" 12 "github.com/NVIDIA/aistore/cmn/atomic" 13 "github.com/NVIDIA/aistore/cmn/cos" 14 "github.com/NVIDIA/aistore/cmn/nlog" 15 "github.com/NVIDIA/aistore/core/meta" 16 "github.com/NVIDIA/aistore/fs" 17 "github.com/NVIDIA/aistore/hk" 18 "github.com/NVIDIA/aistore/memsys" 19 "github.com/NVIDIA/aistore/sys" 20 ) 21 22 const ( 23 oomEvictAtime = time.Minute * 5 // OOM 24 mpeEvictAtime = time.Minute * 10 // extreme 25 mphEvictAtime = time.Minute * 20 // high 26 mpnEvictAtime = time.Hour // normal 27 28 iniEvictAtime = mpnEvictAtime / 2 // initial 29 maxEvictAtime = mpnEvictAtime * 2 // maximum 30 ) 31 32 type lchk struct { 33 cache *sync.Map 34 // runtime 35 now time.Time 36 d time.Duration 37 totalCnt int64 38 // stats 39 evictedCnt int64 40 flushColdCnt int64 41 // single entry 42 running atomic.Bool 43 } 44 45 func regLomCacheWithHK() { 46 g.lchk.running.Store(false) 47 hk.Reg("lcache"+hk.NameSuffix, g.lchk.housekeep, iniEvictAtime) 48 } 49 50 // 51 // evictions 52 // 53 54 func UncacheBck(b *meta.Bck) { 55 var ( 56 caches = lomCaches() 57 n = max(sys.NumCPU()/4, 4) 58 wg = cos.NewLimitedWaitGroup(n, len(caches)) 59 ) 60 for _, lcache := range caches { 61 wg.Add(1) 62 go func(cache *sync.Map) { 63 cache.Range(func(hkey, value any) bool { 64 lmd := value.(*lmeta) 65 bck, _ := cmn.ParseUname(lmd.uname) 66 if bck.Equal((*cmn.Bck)(b)) { 67 cache.Delete(hkey) 68 } 69 return true 70 }) 71 wg.Done() 72 }(lcache) 73 } 74 wg.Wait() 75 } 76 77 // NOTE: watch https://github.com/golang/go/pull/61702 for `sync.Map.Clear`, likely Go 22 78 func UncacheMountpath(mi *fs.Mountpath) { 79 for idx := range cos.MultiSyncMapCount { 80 cache := mi.LomCache(idx) 81 cache.Range(func(hkey any, _ any) bool { 82 cache.Delete(hkey) 83 return true 84 }) 85 } 86 } 87 88 ////////// 89 // lchk // 90 ////////// 91 92 func (lchk *lchk) housekeep() (d time.Duration) { 93 var tag string 94 d, tag = lchk.mp() 95 if !lchk.running.CAS(false, true) { 96 if tag != "" { 97 nlog.Infof("running now: memory pressure %q, next sched %v", tag, d) 98 } 99 return 100 } 101 go lchk.evictAll(d /*evict older than*/) 102 return 103 } 104 105 const termDuration = time.Duration(-1) 106 107 func (lchk *lchk) terminating() bool { return lchk.d == termDuration } 108 109 func (*lchk) mp() (d time.Duration, tag string) { 110 p := g.pmm.Pressure() 111 switch p { 112 case memsys.OOM: 113 d = oomEvictAtime 114 tag = "OOM" 115 case memsys.PressureExtreme: 116 d = mpeEvictAtime 117 tag = "extreme" 118 case memsys.PressureHigh: 119 d = mphEvictAtime 120 tag = "high" 121 default: 122 d = mpnEvictAtime 123 } 124 return 125 } 126 127 func (lchk *lchk) evictAll(d time.Duration) { 128 lchk.now = time.Now() 129 lchk.d = d 130 131 lchk.evictedCnt, lchk.flushColdCnt, lchk.totalCnt = 0, 0, 0 132 133 // single-threaded: one cache at a time 134 caches := lomCaches() 135 if lchk.terminating() { 136 for _, cache := range caches { 137 lchk.cache = cache 138 cache.Range(lchk.fterm) 139 } 140 return 141 } 142 143 for _, cache := range caches { 144 lchk.cache = cache 145 cache.Range(lchk.frun) 146 } 147 148 if _, tag := lchk.mp(); tag != "" { 149 nlog.Infof("memory pressure %q, total %d, evicted %d", tag, lchk.totalCnt, lchk.evictedCnt) 150 } 151 152 // stats 153 g.tstats.Add(LcacheEvictedCount, lchk.evictedCnt) 154 g.tstats.Add(LcacheFlushColdCount, lchk.flushColdCnt) 155 156 lchk.running.Store(false) 157 } 158 159 func (lchk *lchk) fterm(_, value any) bool { 160 md := value.(*lmeta) 161 if md.Atime < 0 { 162 // prefetched, not yet accessed 163 lchk.flush(md, time.Unix(0, -md.Atime)) 164 return true 165 } 166 if md.isDirty() || md.atimefs != uint64(md.Atime) { 167 lchk.flush(md, time.Unix(0, md.Atime)) 168 } 169 return true 170 } 171 172 func (lchk *lchk) frun(hkey, value any) bool { 173 var ( 174 md = value.(*lmeta) 175 mdTime = md.Atime 176 ) 177 lchk.totalCnt++ 178 179 if mdTime < 0 { 180 // prefetched, not yet accessed 181 mdTime = -mdTime 182 } 183 atime := time.Unix(0, mdTime) 184 elapsed := lchk.now.Sub(atime) 185 if elapsed < lchk.d { 186 return true 187 } 188 if md.isDirty() { 189 if lchk.d > mphEvictAtime && elapsed < maxEvictAtime { 190 return true 191 } 192 lchk.flush(md, atime) 193 } else if md.atimefs != uint64(md.Atime) { 194 lchk.flush(md, atime) 195 } 196 lchk.cache.Delete(hkey) 197 lchk.evictedCnt++ 198 return true 199 } 200 201 func (lchk *lchk) flush(md *lmeta, atime time.Time) { 202 lif := LIF{Uname: md.uname, BID: md.bckID} 203 lom, err := lif.LOM() 204 if err == nil { 205 lom.Lock(true) 206 lom.flushCold(md, atime) 207 lom.Unlock(true) 208 FreeLOM(lom) 209 lchk.flushColdCnt++ 210 } 211 }