github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/ext/dload/db.go (about)

     1  // Package dload implements functionality to download resources into AIS cluster from external source.
     2  /*
     3   * Copyright (c) 2018-2023, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package dload
     6  
     7  import (
     8  	"errors"
     9  	"path"
    10  	"sync"
    11  
    12  	"github.com/NVIDIA/aistore/cmn/cos"
    13  	"github.com/NVIDIA/aistore/cmn/kvdb"
    14  	"github.com/NVIDIA/aistore/cmn/nlog"
    15  )
    16  
    17  const (
    18  	downloaderErrors     = "errors"
    19  	downloaderTasks      = "tasks"
    20  	downloaderCollection = "downloads"
    21  
    22  	// Number of errors stored in memory. When the number of errors exceeds
    23  	// this number, then all errors will be flushed to disk
    24  	errCacheSize = 100
    25  
    26  	// Number of tasks stored in memory. When the number of tasks exceeds
    27  	// this number, then all errors will be flushed to disk
    28  	taskInfoCacheSize = 1000
    29  )
    30  
    31  var errJobNotFound = errors.New("job not found")
    32  
    33  type downloaderDB struct {
    34  	mtx    sync.RWMutex
    35  	driver kvdb.Driver
    36  
    37  	errCache      map[string][]TaskErrInfo // memory cache for errors, see: errCacheSize
    38  	taskInfoCache map[string][]TaskDlInfo  // memory cache for tasks, see: taskInfoCacheSize
    39  }
    40  
    41  func newDownloadDB(driver kvdb.Driver) *downloaderDB {
    42  	return &downloaderDB{
    43  		driver:        driver,
    44  		errCache:      make(map[string][]TaskErrInfo, 10),
    45  		taskInfoCache: make(map[string][]TaskDlInfo, 10),
    46  	}
    47  }
    48  
    49  func (db *downloaderDB) errors(id string) (errors []TaskErrInfo, err error) {
    50  	key := path.Join(downloaderErrors, id)
    51  	if err := db.driver.Get(downloaderCollection, key, &errors); err != nil {
    52  		if !cos.IsErrNotFound(err) {
    53  			nlog.Errorln(err)
    54  			return nil, err
    55  		}
    56  		// nothing in DB - return an empty list
    57  		return db.errCache[id], nil
    58  	}
    59  
    60  	errors = append(errors, db.errCache[id]...)
    61  	return
    62  }
    63  
    64  func (db *downloaderDB) getErrors(id string) (errors []TaskErrInfo, err error) {
    65  	db.mtx.RLock()
    66  	defer db.mtx.RUnlock()
    67  	return db.errors(id)
    68  }
    69  
    70  func (db *downloaderDB) persistError(id, objName, errMsg string) {
    71  	db.mtx.Lock()
    72  	defer db.mtx.Unlock()
    73  
    74  	errInfo := TaskErrInfo{Name: objName, Err: errMsg}
    75  	if len(db.errCache[id]) < errCacheSize { // if possible store error in cache
    76  		db.errCache[id] = append(db.errCache[id], errInfo)
    77  		return
    78  	}
    79  
    80  	errMsgs, err := db.errors(id) // it will also append errors from cache
    81  	if err != nil {
    82  		nlog.Errorln(err)
    83  		return
    84  	}
    85  	errMsgs = append(errMsgs, errInfo)
    86  
    87  	key := path.Join(downloaderErrors, id)
    88  	if err := db.driver.Set(downloaderCollection, key, errMsgs); err != nil {
    89  		nlog.Errorln(err)
    90  		return
    91  	}
    92  
    93  	db.errCache[id] = db.errCache[id][:0] // clear cache
    94  }
    95  
    96  func (db *downloaderDB) tasks(id string) (tasks []TaskDlInfo, err error) {
    97  	key := path.Join(downloaderTasks, id)
    98  	if err := db.driver.Get(downloaderCollection, key, &tasks); err != nil {
    99  		if !cos.IsErrNotFound(err) {
   100  			nlog.Errorln(err)
   101  			return nil, err
   102  		}
   103  		// nothing in DB - return an empty list
   104  		return db.taskInfoCache[id], nil
   105  	}
   106  	tasks = append(tasks, db.taskInfoCache[id]...)
   107  	return
   108  }
   109  
   110  func (db *downloaderDB) persistTaskInfo(singleTask *singleTask) error {
   111  	id := singleTask.jobID()
   112  
   113  	db.mtx.Lock()
   114  	defer db.mtx.Unlock()
   115  
   116  	if len(db.taskInfoCache[id]) < taskInfoCacheSize { // if possible store task in cache
   117  		db.taskInfoCache[id] = append(db.taskInfoCache[id], singleTask.ToTaskDlInfo())
   118  		return nil
   119  	}
   120  
   121  	persistedTasks, err := db.tasks(id) // it will also append tasks from cache
   122  	if err != nil {
   123  		return err
   124  	}
   125  	persistedTasks = append(persistedTasks, singleTask.ToTaskDlInfo())
   126  
   127  	key := path.Join(downloaderTasks, id)
   128  	if err := db.driver.Set(downloaderCollection, key, persistedTasks); err != nil {
   129  		return err
   130  	}
   131  	// clear cache
   132  	clear(db.taskInfoCache[id])
   133  	db.taskInfoCache[id] = db.taskInfoCache[id][:0]
   134  	return nil
   135  }
   136  
   137  func (db *downloaderDB) getTasks(id string) (tasks []TaskDlInfo, err error) {
   138  	db.mtx.RLock()
   139  	defer db.mtx.RUnlock()
   140  	return db.tasks(id)
   141  }
   142  
   143  // flushes caches into the disk
   144  func (db *downloaderDB) flush(id string) error {
   145  	db.mtx.Lock()
   146  	defer db.mtx.Unlock()
   147  
   148  	if len(db.errCache[id]) > 0 {
   149  		errMsgs, err := db.errors(id) // it will also append errors from cache
   150  		if err != nil {
   151  			return err
   152  		}
   153  
   154  		key := path.Join(downloaderErrors, id)
   155  		if err := db.driver.Set(downloaderCollection, key, errMsgs); err != nil {
   156  			nlog.Errorln(err)
   157  			return err
   158  		}
   159  
   160  		db.errCache[id] = db.errCache[id][:0] // clear cache
   161  	}
   162  
   163  	if len(db.taskInfoCache[id]) > 0 {
   164  		persistedTasks, err := db.tasks(id) // it will also append tasks from cache
   165  		if err != nil {
   166  			return err
   167  		}
   168  
   169  		key := path.Join(downloaderTasks, id)
   170  		if err := db.driver.Set(downloaderCollection, key, persistedTasks); err != nil {
   171  			nlog.Errorln(err)
   172  			return err
   173  		}
   174  
   175  		db.taskInfoCache[id] = db.taskInfoCache[id][:0] // clear cache
   176  	}
   177  	return nil
   178  }
   179  
   180  func (db *downloaderDB) delete(id string) {
   181  	db.mtx.Lock()
   182  	key := path.Join(downloaderErrors, id)
   183  	db.driver.Delete(downloaderCollection, key)
   184  	key = path.Join(downloaderTasks, id)
   185  	db.driver.Delete(downloaderCollection, key)
   186  	db.mtx.Unlock()
   187  }