github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/search/indexed_block_db.go (about)

     1  // Copyright 2019 Keybase Inc. All rights reserved.
     2  // Use of this source code is governed by a BSD
     3  // license that can be found in the LICENSE file.
     4  
     5  package search
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"io"
    11  	"path/filepath"
    12  	"strconv"
    13  	"sync"
    14  
    15  	"github.com/keybase/client/go/kbfs/data"
    16  	"github.com/keybase/client/go/kbfs/kbfsblock"
    17  	"github.com/keybase/client/go/kbfs/ldbutils"
    18  	"github.com/keybase/client/go/kbfs/libkbfs"
    19  	"github.com/keybase/client/go/kbfs/tlf"
    20  	"github.com/keybase/client/go/logger"
    21  	"github.com/pkg/errors"
    22  	ldberrors "github.com/syndtr/goleveldb/leveldb/errors"
    23  	"github.com/syndtr/goleveldb/leveldb/storage"
    24  )
    25  
    26  const (
    27  	blockDbFilename               string = "block.leveldb"
    28  	tlfDbFilename                 string = "tlf.leveldb"
    29  	initialIndexedBlocksDbVersion uint64 = 1
    30  	currentIndexedBlocksDbVersion uint64 = initialIndexedBlocksDbVersion
    31  	indexedBlocksFolderName       string = "indexed_blocks"
    32  	indexedBlocksMDKey            string = "--md--"
    33  )
    34  
    35  // IndexedBlockDb is a database that holds metadata about indexed blocks.
    36  type IndexedBlockDb struct {
    37  	config libkbfs.Config
    38  	log    logger.Logger
    39  
    40  	// Track the cache hit rate and eviction rate
    41  	hitMeter    *ldbutils.CountMeter
    42  	missMeter   *ldbutils.CountMeter
    43  	putMeter    *ldbutils.CountMeter
    44  	deleteMeter *ldbutils.CountMeter
    45  
    46  	// Protect the DB from being shutdown while they're being
    47  	// accessed, and mutable data.
    48  	lock            sync.RWMutex
    49  	blockDb         *ldbutils.LevelDb // blockID -> index-related metadata
    50  	bufferedBlockDb *ldbutils.LevelDb // in-memory buffer for block db
    51  	tlfDb           *ldbutils.LevelDb // tlfID+blockID -> nil (for cleanup when TLF is un-indexed)
    52  
    53  	docIDLock sync.Mutex
    54  	maxDocID  int64
    55  
    56  	shutdownCh chan struct{}
    57  
    58  	closer func()
    59  }
    60  
    61  // newIndexedBlockDbFromStorage creates a new *IndexedBlockDb
    62  // with the passed-in storage.Storage interfaces as storage layers for each
    63  // db.
    64  func newIndexedBlockDbFromStorage(
    65  	config libkbfs.Config, blockStorage, tlfStorage storage.Storage) (
    66  	db *IndexedBlockDb, err error) {
    67  	log := config.MakeLogger("IBD")
    68  	closers := make([]io.Closer, 0, 2)
    69  	closer := func() {
    70  		for _, c := range closers {
    71  			closeErr := c.Close()
    72  			if closeErr != nil {
    73  				log.Warning("Error closing leveldb or storage: %+v", closeErr)
    74  			}
    75  		}
    76  		if db != nil && db.bufferedBlockDb != nil {
    77  			closeErr := db.bufferedBlockDb.Close()
    78  			if closeErr != nil {
    79  				log.Warning("Error closing leveldb or storage: %+v", closeErr)
    80  			}
    81  		}
    82  	}
    83  	defer func() {
    84  		if err != nil {
    85  			err = errors.WithStack(err)
    86  			closer()
    87  		}
    88  	}()
    89  	blockDbOptions := ldbutils.LeveldbOptions(config.Mode())
    90  	blockDb, err := ldbutils.OpenLevelDbWithOptions(
    91  		blockStorage, blockDbOptions)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  	closers = append(closers, blockDb)
    96  
    97  	tlfDbOptions := ldbutils.LeveldbOptions(config.Mode())
    98  	tlfDb, err := ldbutils.OpenLevelDbWithOptions(tlfStorage, tlfDbOptions)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	closers = append(closers, tlfDb)
   103  
   104  	bufferedBlockDb, err := ldbutils.OpenLevelDbWithOptions(
   105  		storage.NewMemStorage(), nil)
   106  	if err != nil {
   107  		return nil, err
   108  	}
   109  
   110  	db = &IndexedBlockDb{
   111  		config:          config,
   112  		hitMeter:        ldbutils.NewCountMeter(),
   113  		missMeter:       ldbutils.NewCountMeter(),
   114  		putMeter:        ldbutils.NewCountMeter(),
   115  		deleteMeter:     ldbutils.NewCountMeter(),
   116  		log:             log,
   117  		blockDb:         blockDb,
   118  		bufferedBlockDb: bufferedBlockDb,
   119  		tlfDb:           tlfDb,
   120  		shutdownCh:      make(chan struct{}),
   121  		closer:          closer,
   122  	}
   123  
   124  	err = db.loadMaxDocID()
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	return db, nil
   130  }
   131  
   132  // newIndexedBlockDb creates a new *IndexedBlockDb with a
   133  // specified directory on the filesystem as storage.
   134  func newIndexedBlockDb(config libkbfs.Config, dirPath string) (
   135  	db *IndexedBlockDb, err error) {
   136  	log := config.MakeLogger("IBD")
   137  	defer func() {
   138  		if err != nil {
   139  			log.Error("Error initializing indexed block db: %+v", err)
   140  		}
   141  	}()
   142  	dbPath := filepath.Join(dirPath, indexedBlocksFolderName)
   143  	versionPath, err := ldbutils.GetVersionedPathForDb(
   144  		log, dbPath, "indexed blocks", currentIndexedBlocksDbVersion)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	blockDbPath := filepath.Join(versionPath, blockDbFilename)
   149  	blockStorage, err := storage.OpenFile(blockDbPath, false)
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	defer func() {
   154  		if err != nil {
   155  			blockStorage.Close()
   156  		}
   157  	}()
   158  	tlfDbPath := filepath.Join(versionPath, tlfDbFilename)
   159  	tlfStorage, err := storage.OpenFile(tlfDbPath, false)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	defer func() {
   164  		if err != nil {
   165  			tlfStorage.Close()
   166  		}
   167  	}()
   168  	return newIndexedBlockDbFromStorage(config, blockStorage, tlfStorage)
   169  }
   170  
   171  // indexedBlocksMD is metadata related to the entire indexed blocks DB.
   172  type indexedBlocksMD struct {
   173  	// Exported only for serialization.
   174  	MaxDocID int64 // stored as int here for easy incrementing
   175  }
   176  
   177  func (db *IndexedBlockDb) loadMaxDocID() error {
   178  	db.docIDLock.Lock()
   179  	defer db.docIDLock.Unlock()
   180  
   181  	mdBytes, err := db.blockDb.Get([]byte(indexedBlocksMDKey), nil)
   182  	switch errors.Cause(err) {
   183  	case nil:
   184  	case ldberrors.ErrNotFound:
   185  		// Leave the current ID as 0.
   186  		return nil
   187  	default:
   188  		return err
   189  	}
   190  
   191  	var md indexedBlocksMD
   192  	err = db.config.Codec().Decode(mdBytes, &md)
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	db.maxDocID = md.MaxDocID
   198  	return nil
   199  }
   200  
   201  // GetNextDocIDs generates and reserves the next N doc IDs.
   202  func (db *IndexedBlockDb) GetNextDocIDs(n int) ([]string, error) {
   203  	db.docIDLock.Lock()
   204  	defer db.docIDLock.Unlock()
   205  
   206  	res := make([]string, n)
   207  	for i := 0; i < n; i++ {
   208  		res[i] = strconv.FormatInt(db.maxDocID+int64(i+1), 16)
   209  	}
   210  
   211  	md := indexedBlocksMD{MaxDocID: db.maxDocID + int64(n)}
   212  	encodedMD, err := db.config.Codec().Encode(&md)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	err = db.blockDb.Put([]byte(indexedBlocksMDKey), encodedMD, nil)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	db.maxDocID = md.MaxDocID
   223  	return res, nil
   224  }
   225  
   226  // blockMD is per-block metadata for an individual indexed block.
   227  type blockMD struct {
   228  	// Exported only for serialization.
   229  	IndexVersion uint64 `codec:"i"`
   230  	DocID        string `codec:"d"`
   231  	DirDone      bool   `codec:"dd,omitempty"`
   232  }
   233  
   234  func blockDbKeyString(ptr data.BlockPointer) string {
   235  	nonce := ptr.RefNonce
   236  	if nonce == kbfsblock.ZeroRefNonce {
   237  		return ptr.ID.String()
   238  	}
   239  	return ptr.ID.String() + nonce.String()
   240  }
   241  
   242  func blockDbKey(ptr data.BlockPointer) []byte {
   243  	return []byte(blockDbKeyString(ptr))
   244  }
   245  
   246  func tlfKey(tlfID tlf.ID, ptr data.BlockPointer) []byte {
   247  	return []byte(tlfID.String() + blockDbKeyString(ptr))
   248  }
   249  
   250  // getMetadataLocked retrieves the metadata for a block in the db, or
   251  // returns leveldb.ErrNotFound and a zero-valued metadata otherwise.
   252  func (db *IndexedBlockDb) getMetadataLocked(
   253  	ptr data.BlockPointer, metered bool) (metadata blockMD, err error) {
   254  	var hitMeter, missMeter *ldbutils.CountMeter
   255  	if metered {
   256  		hitMeter = db.hitMeter
   257  		missMeter = db.missMeter
   258  	}
   259  
   260  	metadataBytes, err := db.bufferedBlockDb.GetWithMeter(
   261  		blockDbKey(ptr), hitMeter, missMeter)
   262  	switch errors.Cause(err) {
   263  	case nil:
   264  	case ldberrors.ErrNotFound:
   265  		metadataBytes = nil
   266  	default:
   267  		return blockMD{}, err
   268  	}
   269  
   270  	if metadataBytes == nil {
   271  		metadataBytes, err = db.blockDb.GetWithMeter(
   272  			blockDbKey(ptr), hitMeter, missMeter)
   273  		if err != nil {
   274  			return blockMD{}, err
   275  		}
   276  	}
   277  
   278  	err = db.config.Codec().Decode(metadataBytes, &metadata)
   279  	if err != nil {
   280  		return blockMD{}, err
   281  	}
   282  	return metadata, nil
   283  }
   284  
   285  // DbClosedError indicates that the DB has been closed, and thus isn't
   286  // accepting any more operations.
   287  type DbClosedError struct {
   288  	op string
   289  }
   290  
   291  // Error implements the error interface for DbClosedError.
   292  func (e DbClosedError) Error() string {
   293  	return fmt.Sprintf("Error performing %s operation: the db is "+
   294  		"closed", e.op)
   295  }
   296  
   297  // checkAndLockDb checks whether the db is started.
   298  func (db *IndexedBlockDb) checkDbLocked(
   299  	ctx context.Context, method string) error {
   300  	// First see if the context has expired since we began.
   301  	select {
   302  	case <-ctx.Done():
   303  		return ctx.Err()
   304  	default:
   305  	}
   306  
   307  	// shutdownCh has to be checked under lock, otherwise we can race.
   308  	select {
   309  	case <-db.shutdownCh:
   310  		return errors.WithStack(DbClosedError{method})
   311  	default:
   312  	}
   313  	if db.blockDb == nil || db.tlfDb == nil {
   314  		return errors.WithStack(DbClosedError{method})
   315  	}
   316  	return nil
   317  }
   318  
   319  // Get returns the version and doc ID for the given block.
   320  func (db *IndexedBlockDb) Get(
   321  	ctx context.Context, ptr data.BlockPointer) (
   322  	indexVersion uint64, docID string, dirDone bool, err error) {
   323  	db.lock.RLock()
   324  	defer db.lock.RUnlock()
   325  	err = db.checkDbLocked(ctx, "IBD(Get)")
   326  	if err != nil {
   327  		return 0, "", false, err
   328  	}
   329  
   330  	md, err := db.getMetadataLocked(ptr, ldbutils.Metered)
   331  	if err != nil {
   332  		return 0, "", false, err
   333  	}
   334  	return md.IndexVersion, md.DocID, md.DirDone, nil
   335  }
   336  
   337  func (db *IndexedBlockDb) putLocked(
   338  	ctx context.Context, blockDb *ldbutils.LevelDb, tlfID tlf.ID,
   339  	ptr data.BlockPointer, indexVersion uint64, docID string,
   340  	dirDone bool) error {
   341  	md := blockMD{
   342  		IndexVersion: indexVersion,
   343  		DocID:        docID,
   344  		DirDone:      dirDone,
   345  	}
   346  	encodedMetadata, err := db.config.Codec().Encode(&md)
   347  	if err != nil {
   348  		return err
   349  	}
   350  
   351  	return blockDb.PutWithMeter(
   352  		blockDbKey(ptr), encodedMetadata, db.putMeter)
   353  }
   354  
   355  // Put saves the version and doc ID for the given block.
   356  func (db *IndexedBlockDb) Put(
   357  	ctx context.Context, tlfID tlf.ID, ptr data.BlockPointer,
   358  	indexVersion uint64, docID string, dirDone bool) error {
   359  	db.lock.Lock()
   360  	defer db.lock.Unlock()
   361  	err := db.checkDbLocked(ctx, "IBD(Put)")
   362  	if err != nil {
   363  		return err
   364  	}
   365  
   366  	err = db.putLocked(
   367  		ctx, db.blockDb, tlfID, ptr, indexVersion, docID, dirDone)
   368  	if err != nil {
   369  		return err
   370  	}
   371  
   372  	// Record the tlf+blockID, so we can iterate the blocks if we ever
   373  	// need to delete all the blocks associated with a TLF.
   374  	return db.tlfDb.Put(tlfKey(tlfID, ptr), []byte{}, nil)
   375  }
   376  
   377  // PutMemory saves the data to memory, and returns a function that
   378  // will flush it to disk when the caller is ready.
   379  func (db *IndexedBlockDb) PutMemory(
   380  	ctx context.Context, tlfID tlf.ID, ptr data.BlockPointer,
   381  	indexVersion uint64, docID string, dirDone bool) (
   382  	flushFn func() error, err error) {
   383  	db.lock.Lock()
   384  	defer db.lock.Unlock()
   385  
   386  	err = db.checkDbLocked(ctx, "IBD(PutMemory)")
   387  	if err != nil {
   388  		return nil, err
   389  	}
   390  
   391  	err = db.putLocked(
   392  		ctx, db.bufferedBlockDb, tlfID, ptr, indexVersion, docID, dirDone)
   393  	if err != nil {
   394  		return nil, err
   395  	}
   396  
   397  	return func() error {
   398  		return db.Put(ctx, tlfID, ptr, indexVersion, docID, dirDone)
   399  	}, nil
   400  }
   401  
   402  // ClearMemory clears out the buffered puts from memory.
   403  func (db *IndexedBlockDb) ClearMemory() error {
   404  	db.lock.Lock()
   405  	defer db.lock.Unlock()
   406  
   407  	bufferedBlockDb, err := ldbutils.OpenLevelDbWithOptions(
   408  		storage.NewMemStorage(), nil)
   409  	if err != nil {
   410  		return err
   411  	}
   412  
   413  	if bufferedBlockDb != nil {
   414  		err := db.bufferedBlockDb.Close()
   415  		if err != nil {
   416  			return err
   417  		}
   418  	}
   419  
   420  	db.bufferedBlockDb = bufferedBlockDb
   421  	return nil
   422  }
   423  
   424  // Delete removes the metadata for the block pointer from the DB.
   425  func (db *IndexedBlockDb) Delete(
   426  	ctx context.Context, tlfID tlf.ID, ptr data.BlockPointer) error {
   427  	db.lock.Lock()
   428  	defer db.lock.Unlock()
   429  	err := db.checkDbLocked(ctx, "IBD(Delete)")
   430  	if err != nil {
   431  		return err
   432  	}
   433  
   434  	defer func() {
   435  		if err == nil {
   436  			db.deleteMeter.Mark(1)
   437  		}
   438  	}()
   439  
   440  	err = db.blockDb.Delete(blockDbKey(ptr), nil)
   441  	if err != nil {
   442  		return err
   443  	}
   444  
   445  	return db.tlfDb.Delete(tlfKey(tlfID, ptr), nil)
   446  }
   447  
   448  // Shutdown closes this db.
   449  func (db *IndexedBlockDb) Shutdown(ctx context.Context) {
   450  	db.lock.Lock()
   451  	defer db.lock.Unlock()
   452  	// shutdownCh has to be checked under lock, otherwise we can race.
   453  	select {
   454  	case <-db.shutdownCh:
   455  		db.log.CWarningf(ctx, "Shutdown called more than once")
   456  		return
   457  	default:
   458  	}
   459  	close(db.shutdownCh)
   460  	if db.blockDb == nil || db.tlfDb == nil {
   461  		return
   462  	}
   463  	db.closer()
   464  	db.blockDb = nil
   465  	db.tlfDb = nil
   466  	db.hitMeter.Shutdown()
   467  	db.missMeter.Shutdown()
   468  	db.putMeter.Shutdown()
   469  	db.deleteMeter.Shutdown()
   470  }