github.com/NVIDIA/aistore@v1.3.23-0.20240517131212-7df6609be51d/cmn/kvdb/bunt.go (about)

     1  // Package kvdb provides a local key/value database server for AIS.
     2  /*
     3   * Copyright (c) 2018-2024, NVIDIA CORPORATION. All rights reserved.
     4   */
     5  package kvdb
     6  
     7  import (
     8  	"strings"
     9  
    10  	"github.com/NVIDIA/aistore/cmn/cos"
    11  	jsoniter "github.com/json-iterator/go"
    12  	"github.com/tidwall/buntdb"
    13  )
    14  
    15  // BuntDB:
    16  // At this moment, BuntDB runs with the following settings:
    17  // - calls filesystem sync every second (if we want to do it less frequently
    18  //   may add `Flush` to `Driver` interface and call in Get/Set/Delete/List
    19  //   when a certain number of seconds pass. There is not way to do it in easier
    20  //   way with built-in stuff: either Never or every second)
    21  // - after database size exceeds `autoShrinkSize`(1MB), BuntDB starts compacting
    22  //   the database periodically(when it grows over a given limit)
    23  // - compacting is executed when the database size is greater than the size
    24  //   after previous compacting by `AutoShrinkPercentage`(50%)
    25  
    26  const autoShrinkSize = cos.MiB
    27  
    28  type BuntDriver struct {
    29  	driver *buntdb.DB
    30  }
    31  
    32  // interface guard
    33  var _ Driver = (*BuntDriver)(nil)
    34  
    35  func NewBuntDB(path string) (*BuntDriver, error) {
    36  	driver, err := buntdb.Open(path)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  	driver.SetConfig(buntdb.Config{
    41  		SyncPolicy:           buntdb.EverySecond, // periodical FS sync
    42  		AutoShrinkMinSize:    autoShrinkSize,     // start autoShrink only of file exceeds the size
    43  		AutoShrinkPercentage: 50,                 // run compacting when DB grows by half
    44  	})
    45  	return &BuntDriver{driver: driver}, nil
    46  }
    47  
    48  func buntToCommonErr(err error, collection, key string) error {
    49  	if err == buntdb.ErrNotFound {
    50  		what := collection
    51  		if key != "" {
    52  			what += " \"" + key + "\""
    53  		}
    54  		return cos.NewErrNotFound(nil, what)
    55  	}
    56  	return err
    57  }
    58  
    59  // Create "unique" key from collection and key, so there was no trouble when
    60  // there is an overlap. E.g, if key and collection uses the same separator
    61  // for subkeys, two pairs ("abc", "def/ghi") and ("abc/def", "ghi") generate
    62  // the same full path. The function should make them different.
    63  func makePath(collection, key string) string {
    64  	if strings.HasSuffix(collection, "##") {
    65  		return collection + key
    66  	}
    67  	return collection + CollectionSepa + key
    68  }
    69  
    70  func (bd *BuntDriver) Close() error {
    71  	return bd.driver.Close()
    72  }
    73  
    74  func (bd *BuntDriver) Set(collection, key string, object any) error {
    75  	b := cos.MustMarshal(object)
    76  	err := bd.SetString(collection, key, string(b))
    77  	return buntToCommonErr(err, collection, key)
    78  }
    79  
    80  func (bd *BuntDriver) Get(collection, key string, object any) error {
    81  	s, err := bd.GetString(collection, key)
    82  	if err != nil {
    83  		return buntToCommonErr(err, collection, key)
    84  	}
    85  	return jsoniter.Unmarshal([]byte(s), object)
    86  }
    87  
    88  func (bd *BuntDriver) SetString(collection, key, data string) error {
    89  	name := makePath(collection, key)
    90  	err := bd.driver.Update(func(tx *buntdb.Tx) error {
    91  		_, _, err := tx.Set(name, data, nil)
    92  		return err
    93  	})
    94  	return buntToCommonErr(err, collection, key)
    95  }
    96  
    97  func (bd *BuntDriver) GetString(collection, key string) (string, error) {
    98  	var value string
    99  	name := makePath(collection, key)
   100  	err := bd.driver.View(func(tx *buntdb.Tx) error {
   101  		var err error
   102  		value, err = tx.Get(name)
   103  		return err
   104  	})
   105  	return value, buntToCommonErr(err, collection, key)
   106  }
   107  
   108  func (bd *BuntDriver) Delete(collection, key string) error {
   109  	name := makePath(collection, key)
   110  	err := bd.driver.Update(func(tx *buntdb.Tx) error {
   111  		_, err := tx.Delete(name)
   112  		return err
   113  	})
   114  	return buntToCommonErr(err, collection, key)
   115  }
   116  
   117  func (bd *BuntDriver) List(collection, pattern string) ([]string, error) {
   118  	var (
   119  		keys   = make([]string, 0)
   120  		filter string
   121  	)
   122  	if !strings.Contains(pattern, "*") && !strings.Contains(pattern, "?") {
   123  		pattern += "*"
   124  	}
   125  	filter = makePath(collection, pattern)
   126  	err := bd.driver.View(func(tx *buntdb.Tx) error {
   127  		tx.AscendKeys(filter, func(path, _ string) bool {
   128  			_, key := ParsePath(path)
   129  			if key != "" {
   130  				keys = append(keys, key)
   131  			}
   132  			return true
   133  		})
   134  		return nil
   135  	})
   136  	return keys, buntToCommonErr(err, collection, "")
   137  }
   138  
   139  func (bd *BuntDriver) DeleteCollection(collection string) error {
   140  	keys, err := bd.List(collection, "")
   141  	if err != nil || len(keys) == 0 {
   142  		return err
   143  	}
   144  	return bd.driver.Update(func(tx *buntdb.Tx) error {
   145  		for _, k := range keys {
   146  			_, err := tx.Delete(k)
   147  			if err != nil && err != buntdb.ErrNotFound {
   148  				return err
   149  			}
   150  		}
   151  		return nil
   152  	})
   153  }
   154  
   155  func (bd *BuntDriver) GetAll(collection, pattern string) (map[string]string, error) {
   156  	var (
   157  		values = make(map[string]string)
   158  		filter string
   159  	)
   160  	if !strings.Contains(pattern, "*") && !strings.Contains(pattern, "?") {
   161  		pattern += "*"
   162  	}
   163  	filter = makePath(collection, pattern)
   164  	err := bd.driver.View(func(tx *buntdb.Tx) error {
   165  		tx.AscendKeys(filter, func(path, val string) bool {
   166  			_, key := ParsePath(path)
   167  			if key != "" {
   168  				values[key] = val
   169  			}
   170  			return true
   171  		})
   172  		return nil
   173  	})
   174  	return values, buntToCommonErr(err, collection, "")
   175  }