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  }