github.com/TeaOSLab/EdgeNode@v1.3.8/internal/utils/kvstore/store.go (about)

     1  // Copyright 2024 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
     2  
     3  package kvstore
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"github.com/TeaOSLab/EdgeNode/internal/events"
     9  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
    10  	fsutils "github.com/TeaOSLab/EdgeNode/internal/utils/fs"
    11  	memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem"
    12  	"github.com/cockroachdb/pebble"
    13  	"github.com/iwind/TeaGo/Tea"
    14  	"io"
    15  	"os"
    16  	"path/filepath"
    17  	"strings"
    18  	"sync"
    19  )
    20  
    21  const StoreSuffix = ".store"
    22  
    23  type Store struct {
    24  	name string
    25  
    26  	path   string
    27  	rawDB  *pebble.DB
    28  	locker *fsutils.Locker
    29  
    30  	isClosed bool
    31  
    32  	dbs []*DB
    33  
    34  	mu sync.Mutex
    35  }
    36  
    37  // NewStore create store with name
    38  func NewStore(storeName string) (*Store, error) {
    39  	if !IsValidName(storeName) {
    40  		return nil, errors.New("invalid store name '" + storeName + "'")
    41  	}
    42  
    43  	var path = Tea.Root + "/data/stores/" + storeName + StoreSuffix
    44  	_, err := os.Stat(path)
    45  	if err != nil && os.IsNotExist(err) {
    46  		_ = os.MkdirAll(path, 0777)
    47  	}
    48  
    49  	return &Store{
    50  		name:   storeName,
    51  		path:   path,
    52  		locker: fsutils.NewLocker(path + "/.fs"),
    53  	}, nil
    54  }
    55  
    56  // NewStoreWithPath create store with path
    57  func NewStoreWithPath(path string) (*Store, error) {
    58  	if !strings.HasSuffix(path, ".store") {
    59  		return nil, errors.New("store path must contains a '.store' suffix")
    60  	}
    61  
    62  	_, err := os.Stat(path)
    63  	if err != nil && os.IsNotExist(err) {
    64  		_ = os.MkdirAll(path, 0777)
    65  	}
    66  
    67  	var storeName = filepath.Base(path)
    68  	storeName = strings.TrimSuffix(storeName, ".store")
    69  
    70  	if !IsValidName(storeName) {
    71  		return nil, errors.New("invalid store name '" + storeName + "'")
    72  	}
    73  
    74  	return &Store{
    75  		name:   storeName,
    76  		path:   path,
    77  		locker: fsutils.NewLocker(path + "/.fs"),
    78  	}, nil
    79  }
    80  
    81  func OpenStore(storeName string) (*Store, error) {
    82  	store, err := NewStore(storeName)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	err = store.Open()
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	return store, nil
    92  }
    93  
    94  func OpenStoreDir(dir string, storeName string) (*Store, error) {
    95  	if !IsValidName(storeName) {
    96  		return nil, errors.New("invalid store name '" + storeName + "'")
    97  	}
    98  
    99  	var path = strings.TrimSuffix(dir, "/") + "/" + storeName + StoreSuffix
   100  	_, err := os.Stat(path)
   101  	if err != nil && os.IsNotExist(err) {
   102  		_ = os.MkdirAll(path, 0777)
   103  	}
   104  
   105  	var store = &Store{
   106  		name:   storeName,
   107  		path:   path,
   108  		locker: fsutils.NewLocker(path + "/.fs"),
   109  	}
   110  
   111  	err = store.Open()
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	return store, nil
   116  }
   117  
   118  var storeOnce = &sync.Once{}
   119  var defaultSore *Store
   120  
   121  func DefaultStore() (*Store, error) {
   122  	if defaultSore != nil {
   123  		return defaultSore, nil
   124  	}
   125  
   126  	var resultErr error
   127  	storeOnce.Do(func() {
   128  		store, err := NewStore("default")
   129  		if err != nil {
   130  			resultErr = fmt.Errorf("create default store failed: %w", err)
   131  			remotelogs.Error("KV", resultErr.Error())
   132  			return
   133  		}
   134  		err = store.Open()
   135  		if err != nil {
   136  			resultErr = fmt.Errorf("open default store failed: %w", err)
   137  			remotelogs.Error("KV", resultErr.Error())
   138  			return
   139  		}
   140  		defaultSore = store
   141  	})
   142  
   143  	return defaultSore, resultErr
   144  }
   145  
   146  func (this *Store) Path() string {
   147  	return this.path
   148  }
   149  
   150  func (this *Store) Open() error {
   151  	err := this.locker.Lock()
   152  	if err != nil {
   153  		return err
   154  	}
   155  
   156  	var opt = &pebble.Options{
   157  		Logger: NewLogger(),
   158  	}
   159  
   160  	if fsutils.DiskIsFast() {
   161  		opt.BytesPerSync = 1 << 20
   162  	}
   163  
   164  	var memoryMB = memutils.SystemMemoryGB() * 2
   165  	if memoryMB > 256 {
   166  		memoryMB = 256
   167  	}
   168  	if memoryMB > 4 {
   169  		opt.MemTableSize = uint64(memoryMB) << 20
   170  	}
   171  
   172  	rawDB, err := pebble.Open(this.path, opt)
   173  	if err != nil {
   174  		return err
   175  	}
   176  	this.rawDB = rawDB
   177  
   178  	// events
   179  	events.OnClose(func() {
   180  		_ = this.Close()
   181  	})
   182  
   183  	return nil
   184  }
   185  
   186  func (this *Store) Set(keyBytes []byte, valueBytes []byte) error {
   187  	return this.rawDB.Set(keyBytes, valueBytes, DefaultWriteOptions)
   188  }
   189  
   190  func (this *Store) Get(keyBytes []byte) (valueBytes []byte, closer io.Closer, err error) {
   191  	return this.rawDB.Get(keyBytes)
   192  }
   193  
   194  func (this *Store) Delete(keyBytes []byte) error {
   195  	return this.rawDB.Delete(keyBytes, DefaultWriteOptions)
   196  }
   197  
   198  func (this *Store) NewDB(dbName string) (*DB, error) {
   199  	this.mu.Lock()
   200  	defer this.mu.Unlock()
   201  
   202  	// check existence
   203  	for _, db := range this.dbs {
   204  		if db.name == dbName {
   205  			return db, nil
   206  		}
   207  	}
   208  
   209  	// create new
   210  	db, err := NewDB(this, dbName)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  
   215  	this.dbs = append(this.dbs, db)
   216  	return db, nil
   217  }
   218  
   219  func (this *Store) RawDB() *pebble.DB {
   220  	return this.rawDB
   221  }
   222  
   223  func (this *Store) Flush() error {
   224  	return this.rawDB.Flush()
   225  }
   226  
   227  func (this *Store) Close() error {
   228  	if this.isClosed {
   229  		return nil
   230  	}
   231  
   232  	_ = this.locker.Release()
   233  
   234  	this.mu.Lock()
   235  	var lastErr error
   236  	for _, db := range this.dbs {
   237  		err := db.Close()
   238  		if err != nil {
   239  			lastErr = err
   240  		}
   241  	}
   242  
   243  	this.mu.Unlock()
   244  
   245  	if this.rawDB != nil {
   246  		this.isClosed = true
   247  		err := this.rawDB.Close()
   248  		if err != nil {
   249  			return err
   250  		}
   251  	}
   252  
   253  	return lastErr
   254  }
   255  
   256  func (this *Store) IsClosed() bool {
   257  	return this.isClosed
   258  }