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 }