github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/xact/xs/prefetch.go (about) 1 // Package xs is a collection of eXtended actions (xactions), including multi-object 2 // operations, list-objects, (cluster) rebalance and (target) resilver, ETL, and more. 3 /* 4 * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved. 5 */ 6 package xs 7 8 import ( 9 "context" 10 "fmt" 11 "sync" 12 "time" 13 14 "github.com/NVIDIA/aistore/api/apc" 15 "github.com/NVIDIA/aistore/cmn" 16 "github.com/NVIDIA/aistore/cmn/cos" 17 "github.com/NVIDIA/aistore/cmn/debug" 18 "github.com/NVIDIA/aistore/cmn/nlog" 19 "github.com/NVIDIA/aistore/core" 20 "github.com/NVIDIA/aistore/core/meta" 21 "github.com/NVIDIA/aistore/xact" 22 "github.com/NVIDIA/aistore/xact/xreg" 23 ) 24 25 // TODO: 26 // - user-assigned (configurable) num-workers 27 // - jogger(s) per mountpath type concurrency 28 29 type ( 30 prfFactory struct { 31 xreg.RenewBase 32 xctn *prefetch 33 msg *apc.PrefetchMsg 34 } 35 prefetch struct { 36 config *cmn.Config 37 msg *apc.PrefetchMsg 38 lriterator 39 xact.Base 40 latestVer bool 41 } 42 ) 43 44 func (*prfFactory) New(args xreg.Args, bck *meta.Bck) xreg.Renewable { 45 msg := args.Custom.(*apc.PrefetchMsg) 46 debug.Assert(!msg.IsList() || !msg.HasTemplate()) 47 np := &prfFactory{RenewBase: xreg.RenewBase{Args: args, Bck: bck}, msg: msg} 48 return np 49 } 50 51 func (p *prfFactory) Start() (err error) { 52 if p.msg.BlobThreshold > 0 { 53 if a := int64(minChunkSize << 2); p.msg.BlobThreshold < a { 54 return fmt.Errorf("blob-threshold (size) is too small: must be at least %s", cos.ToSizeIEC(a, 0)) 55 } 56 } 57 58 b := p.Bck 59 if err = b.Init(core.T.Bowner()); err != nil { 60 return err 61 } 62 if b.IsAIS() { 63 return fmt.Errorf("bucket %s is not _remote_ (can only prefetch remote buckets)", b) 64 } 65 p.xctn, err = newPrefetch(&p.Args, p.Kind(), b, p.msg) 66 return err 67 } 68 69 func (*prfFactory) Kind() string { return apc.ActPrefetchObjects } 70 func (p *prfFactory) Get() core.Xact { return p.xctn } 71 72 func (*prfFactory) WhenPrevIsRunning(xreg.Renewable) (xreg.WPR, error) { 73 return xreg.WprKeepAndStartNew, nil 74 } 75 76 func newPrefetch(xargs *xreg.Args, kind string, bck *meta.Bck, msg *apc.PrefetchMsg) (r *prefetch, err error) { 77 r = &prefetch{config: cmn.GCO.Get(), msg: msg} 78 79 err = r.lriterator.init(r, &msg.ListRange, bck) 80 if err != nil { 81 return nil, err 82 } 83 r.InitBase(xargs.UUID, kind, bck) 84 r.latestVer = bck.VersionConf().ValidateWarmGet || msg.LatestVer 85 return r, nil 86 } 87 88 func (r *prefetch) Run(wg *sync.WaitGroup) { 89 wg.Done() 90 err := r.lriterator.run(r, core.T.Sowner().Get()) 91 if err != nil { 92 r.AddErr(err, 5, cos.SmoduleXs) // duplicated? 93 } 94 r.lriterator.wait() 95 r.Finish() 96 } 97 98 func (r *prefetch) do(lom *core.LOM, lrit *lriterator) { 99 var ( 100 err error 101 size int64 102 ecode int 103 ) 104 105 lom.Lock(false) 106 oa, deleted, err := lom.LoadLatest(r.latestVer || r.msg.BlobThreshold > 0) // NOTE: shortcut to find size 107 lom.Unlock(false) 108 109 // handle assorted returns 110 switch { 111 case deleted: // remotely 112 debug.Assert(r.latestVer && err != nil) 113 if lrit.lrp != lrpList { 114 return // deleted or not found remotely, prefix or range 115 } 116 goto eret 117 case oa != nil: 118 // not latest 119 size = oa.Size 120 case err == nil: 121 return // nothing to do 122 case !cmn.IsErrObjNought(err): 123 goto eret 124 } 125 126 // Minimal locking, optimistic concurrency ==================================================== 127 // Not setting atime (a.k.a. access time) as prefetching != actual access. 128 // 129 // On the other hand, zero atime makes the object's lifespan in the cache too short - the first 130 // housekeeping traversal will remove it. Using negative `-now` value for subsequent correction 131 // (see core/lcache.go). ========================== 132 lom.SetAtimeUnix(-time.Now().UnixNano()) 133 134 if r.msg.BlobThreshold > 0 && size >= r.msg.BlobThreshold { 135 err = _prefetchBlob(lom, oa) 136 } else { 137 ecode, err = core.T.GetCold(context.Background(), lom, cmn.OwtGetPrefetchLock) 138 if err == nil { // done 139 r.ObjsAdd(1, lom.SizeBytes()) 140 } 141 } 142 143 if err == nil { // done 144 return 145 } 146 if cos.IsNotExist(err, ecode) && lrit.lrp != lrpList { 147 return // not found, prefix or range 148 } 149 eret: 150 r.AddErr(err, 5, cos.SmoduleXs) 151 } 152 153 // TODO: revisit 154 func _prefetchBlob(lom *core.LOM, oa *cmn.ObjAttrs) error { 155 const sleep = 5 * time.Second // TODO: add a tunable 156 params := &core.BlobParams{ 157 Lom: core.AllocLOM(lom.ObjName), 158 Msg: &apc.BlobMsg{}, // TODO: ditto 159 } 160 if err := params.Lom.InitBck(lom.Bucket()); err != nil { 161 return err 162 } 163 xctn, err := core.T.GetColdBlob(params, oa) 164 if err != nil { 165 return err 166 } 167 168 // TODO: add async option 169 var total time.Duration 170 for !xctn.Finished() { 171 time.Sleep(sleep) 172 total += sleep 173 if total >= time.Minute { 174 break 175 } 176 } 177 if xctn.Finished() { 178 return xctn.AbortErr() 179 } 180 181 nlog.Warningln(xctn.Name(), "- is taking more than 1 minute to complete") 182 return nil // (leaving x-blob dangling) 183 } 184 185 func (r *prefetch) Snap() (snap *core.Snap) { 186 snap = &core.Snap{} 187 r.ToSnap(snap) 188 189 snap.IdleX = r.IsIdle() 190 return 191 }