github.com/dashpay/godash@v0.0.0-20160726055534-e038a21e0e3d/blockchain/indexers/manager.go (about)

     1  // Copyright (c) 2016 The btcsuite developers
     2  // Copyright (c) 2016 The Dash developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package indexers
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  
    12  	"github.com/dashpay/godash/blockchain"
    13  	"github.com/dashpay/godash/database"
    14  	"github.com/dashpay/godash/wire"
    15  	"github.com/dashpay/godashutil"
    16  )
    17  
    18  var (
    19  	// indexTipsBucketName is the name of the db bucket used to house the
    20  	// current tip of each index.
    21  	indexTipsBucketName = []byte("idxtips")
    22  )
    23  
    24  // -----------------------------------------------------------------------------
    25  // The index manager tracks the current tip of each index by using a parent
    26  // bucket that contains an entry for index.
    27  //
    28  // The serialized format for an index tip is:
    29  //
    30  //   [<block hash><block height>],...
    31  //
    32  //   Field           Type           Size
    33  //   block hash      wire.ShaHash   wire.HashSize
    34  //   block height    uint32         4 bytes
    35  // -----------------------------------------------------------------------------
    36  
    37  // dbPutIndexerTip uses an existing database transaction to update or add the
    38  // current tip for the given index to the provided values.
    39  func dbPutIndexerTip(dbTx database.Tx, idxKey []byte, hash *wire.ShaHash, height int32) error {
    40  	serialized := make([]byte, wire.HashSize+4)
    41  	copy(serialized, hash[:])
    42  	byteOrder.PutUint32(serialized[wire.HashSize:], uint32(height))
    43  
    44  	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
    45  	return indexesBucket.Put(idxKey, serialized)
    46  }
    47  
    48  // dbFetchIndexerTip uses an existing database transaction to retrieve the
    49  // hash and height of the current tip for the provided index.
    50  func dbFetchIndexerTip(dbTx database.Tx, idxKey []byte) (*wire.ShaHash, int32, error) {
    51  	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
    52  	serialized := indexesBucket.Get(idxKey)
    53  	if len(serialized) < wire.HashSize+4 {
    54  		return nil, 0, database.Error{
    55  			ErrorCode: database.ErrCorruption,
    56  			Description: fmt.Sprintf("unexpected end of data for "+
    57  				"index %q tip", string(idxKey)),
    58  		}
    59  	}
    60  
    61  	var hash wire.ShaHash
    62  	copy(hash[:], serialized[:wire.HashSize])
    63  	height := int32(byteOrder.Uint32(serialized[wire.HashSize:]))
    64  	return &hash, height, nil
    65  }
    66  
    67  // dbIndexConnectBlock adds all of the index entries associated with the
    68  // given block using the provided indexer and updates the tip of the indexer
    69  // accordingly.  An error will be returned if the current tip for the indexer is
    70  // not the previous block for the passed block.
    71  func dbIndexConnectBlock(dbTx database.Tx, indexer Indexer, block *godashutil.Block, view *blockchain.UtxoViewpoint) error {
    72  	// Assert that the block being connected properly connects to the
    73  	// current tip of the index.
    74  	idxKey := indexer.Key()
    75  	curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey)
    76  	if err != nil {
    77  		return err
    78  	}
    79  	if !curTipHash.IsEqual(&block.MsgBlock().Header.PrevBlock) {
    80  		return AssertError(fmt.Sprintf("dbIndexConnectBlock must be "+
    81  			"called with a block that extends the current index "+
    82  			"tip (%s, tip %s, block %s)", indexer.Name(),
    83  			curTipHash, block.Sha()))
    84  	}
    85  
    86  	// Notify the indexer with the connected block so it can index it.
    87  	if err := indexer.ConnectBlock(dbTx, block, view); err != nil {
    88  		return err
    89  	}
    90  
    91  	// Update the current index tip.
    92  	return dbPutIndexerTip(dbTx, idxKey, block.Sha(), block.Height())
    93  }
    94  
    95  // dbIndexDisconnectBlock removes all of the index entries associated with the
    96  // given block using the provided indexer and updates the tip of the indexer
    97  // accordingly.  An error will be returned if the current tip for the indexer is
    98  // not the passed block.
    99  func dbIndexDisconnectBlock(dbTx database.Tx, indexer Indexer, block *godashutil.Block, view *blockchain.UtxoViewpoint) error {
   100  	// Assert that the block being disconnected is the current tip of the
   101  	// index.
   102  	idxKey := indexer.Key()
   103  	curTipHash, _, err := dbFetchIndexerTip(dbTx, idxKey)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	if !curTipHash.IsEqual(block.Sha()) {
   108  		return AssertError(fmt.Sprintf("dbIndexDisconnectBlock must "+
   109  			"be called with the block at the current index tip "+
   110  			"(%s, tip %s, block %s)", indexer.Name(),
   111  			curTipHash, block.Sha()))
   112  	}
   113  
   114  	// Notify the indexer with the disconnected block so it can remove all
   115  	// of the appropriate entries.
   116  	if err := indexer.DisconnectBlock(dbTx, block, view); err != nil {
   117  		return err
   118  	}
   119  
   120  	// Update the current index tip.
   121  	prevHash := &block.MsgBlock().Header.PrevBlock
   122  	return dbPutIndexerTip(dbTx, idxKey, prevHash, block.Height()-1)
   123  }
   124  
   125  // Manager defines an index manager that manages multiple optional indexes and
   126  // implements the blockchain.IndexManager interface so it can be seamlessly
   127  // plugged into normal chain processing.
   128  type Manager struct {
   129  	db             database.DB
   130  	enabledIndexes []Indexer
   131  }
   132  
   133  // Ensure the Manager type implements the blockchain.IndexManager interface.
   134  var _ blockchain.IndexManager = (*Manager)(nil)
   135  
   136  // indexDropKey returns the key for an index which indicates it is in the
   137  // process of being dropped.
   138  func indexDropKey(idxKey []byte) []byte {
   139  	dropKey := make([]byte, len(idxKey)+1)
   140  	dropKey[0] = 'd'
   141  	copy(dropKey[1:], idxKey)
   142  	return dropKey
   143  }
   144  
   145  // maybeFinishDrops determines if each of the enabled indexes are in the middle
   146  // of being dropped and finishes dropping them when the are.  This is necessary
   147  // because dropping and index has to be done in several atomic steps rather than
   148  // one big atomic step due to the massive number of entries.
   149  func (m *Manager) maybeFinishDrops() error {
   150  	indexNeedsDrop := make([]bool, len(m.enabledIndexes))
   151  	err := m.db.View(func(dbTx database.Tx) error {
   152  		// None of the indexes needs to be dropped if the index tips
   153  		// bucket hasn't been created yet.
   154  		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   155  		if indexesBucket == nil {
   156  			return nil
   157  		}
   158  
   159  		// Make the indexer as requiring a drop if one is already in
   160  		// progress.
   161  		for i, indexer := range m.enabledIndexes {
   162  			dropKey := indexDropKey(indexer.Key())
   163  			if indexesBucket.Get(dropKey) != nil {
   164  				indexNeedsDrop[i] = true
   165  			}
   166  		}
   167  
   168  		return nil
   169  	})
   170  	if err != nil {
   171  		return err
   172  	}
   173  
   174  	// Finish dropping any of the enabled indexes that are already in the
   175  	// middle of being dropped.
   176  	for i, indexer := range m.enabledIndexes {
   177  		if !indexNeedsDrop[i] {
   178  			continue
   179  		}
   180  
   181  		log.Infof("Resuming %s drop", indexer.Name())
   182  		err := dropIndex(m.db, indexer.Key(), indexer.Name())
   183  		if err != nil {
   184  			return err
   185  		}
   186  	}
   187  
   188  	return nil
   189  }
   190  
   191  // maybeCreateIndexes determines if each of the enabled indexes have already
   192  // been created and creates them if not.
   193  func (m *Manager) maybeCreateIndexes(dbTx database.Tx) error {
   194  	indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   195  	for _, indexer := range m.enabledIndexes {
   196  		// Nothing to do if the index tip already exists.
   197  		idxKey := indexer.Key()
   198  		if indexesBucket.Get(idxKey) != nil {
   199  			continue
   200  		}
   201  
   202  		// The tip for the index does not exist, so create it and
   203  		// invoke the create callback for the index so it can perform
   204  		// any one-time initialization it requires.
   205  		if err := indexer.Create(dbTx); err != nil {
   206  			return err
   207  		}
   208  
   209  		// Set the tip for the index to values which represent an
   210  		// uninitialized index.
   211  		err := dbPutIndexerTip(dbTx, idxKey, &wire.ShaHash{}, -1)
   212  		if err != nil {
   213  			return err
   214  		}
   215  	}
   216  
   217  	return nil
   218  }
   219  
   220  // Init initializes the enabled indexes.  This is called during chain
   221  // initialization and primarily consists of catching up all indexes to the
   222  // current best chain tip.  This is necessary since each index can be disabled
   223  // and re-enabled at any time and attempting to catch-up indexes at the same
   224  // time new blocks are being downloaded would lead to an overall longer time to
   225  // catch up due to the I/O contention.
   226  //
   227  // This is part of the blockchain.IndexManager interface.
   228  func (m *Manager) Init(chain *blockchain.BlockChain) error {
   229  	// Nothing to do when no indexes are enabled.
   230  	if len(m.enabledIndexes) == 0 {
   231  		return nil
   232  	}
   233  
   234  	// Finish and drops that were previously interrupted.
   235  	if err := m.maybeFinishDrops(); err != nil {
   236  		return err
   237  	}
   238  
   239  	// Create the initial state for the indexes as needed.
   240  	err := m.db.Update(func(dbTx database.Tx) error {
   241  		// Create the bucket for the current tips as needed.
   242  		meta := dbTx.Metadata()
   243  		_, err := meta.CreateBucketIfNotExists(indexTipsBucketName)
   244  		if err != nil {
   245  			return err
   246  		}
   247  
   248  		return m.maybeCreateIndexes(dbTx)
   249  	})
   250  	if err != nil {
   251  		return err
   252  	}
   253  
   254  	// Initialize each of the enabled indexes.
   255  	for _, indexer := range m.enabledIndexes {
   256  		if err := indexer.Init(); err != nil {
   257  			return err
   258  		}
   259  	}
   260  
   261  	// Rollback indexes to the main chain if their tip is an orphaned fork.
   262  	// This is fairly unlikely, but it can happen if the chain is
   263  	// reorganized while the index is disabled.  This has to be done in
   264  	// reverse order because later indexes can depend on earlier ones.
   265  	for i := len(m.enabledIndexes); i > 0; i-- {
   266  		indexer := m.enabledIndexes[i-1]
   267  
   268  		// Fetch the current tip for the index.
   269  		var height int32
   270  		var hash *wire.ShaHash
   271  		err := m.db.View(func(dbTx database.Tx) error {
   272  			idxKey := indexer.Key()
   273  			hash, height, err = dbFetchIndexerTip(dbTx, idxKey)
   274  			if err != nil {
   275  				return err
   276  			}
   277  			return nil
   278  		})
   279  		if err != nil {
   280  			return err
   281  		}
   282  
   283  		// Nothing to do if the index does not have any entries yet.
   284  		if height == -1 {
   285  			continue
   286  		}
   287  
   288  		// Loop until the tip is a block that exists in the main chain.
   289  		initialHeight := height
   290  		for {
   291  			exists, err := chain.MainChainHasBlock(hash)
   292  			if err != nil {
   293  				return err
   294  			}
   295  			if exists {
   296  				break
   297  			}
   298  
   299  			// At this point the index tip is orphaned, so load the
   300  			// orphaned block from the database directly and
   301  			// disconnect it from the index.  The block has to be
   302  			// loaded directly since it is no longer in the main
   303  			// chain and thus the chain.BlockByHash function would
   304  			// error.
   305  			err = m.db.Update(func(dbTx database.Tx) error {
   306  				blockBytes, err := dbTx.FetchBlock(hash)
   307  				if err != nil {
   308  					return err
   309  				}
   310  				block, err := godashutil.NewBlockFromBytes(blockBytes)
   311  				if err != nil {
   312  					return err
   313  				}
   314  				block.SetHeight(height)
   315  
   316  				// When the index requires all of the referenced
   317  				// txouts they need to be retrieved from the
   318  				// transaction index.
   319  				var view *blockchain.UtxoViewpoint
   320  				if indexNeedsInputs(indexer) {
   321  					var err error
   322  					view, err = makeUtxoView(dbTx, block)
   323  					if err != nil {
   324  						return err
   325  					}
   326  				}
   327  
   328  				// Remove all of the index entries associated
   329  				// with the block and update the indexer tip.
   330  				err = dbIndexDisconnectBlock(dbTx, indexer,
   331  					block, view)
   332  				if err != nil {
   333  					return err
   334  				}
   335  
   336  				// Update the tip to the previous block.
   337  				hash = &block.MsgBlock().Header.PrevBlock
   338  				height--
   339  
   340  				return nil
   341  			})
   342  			if err != nil {
   343  				return err
   344  			}
   345  		}
   346  
   347  		if initialHeight != height {
   348  			log.Infof("Removed %d orphaned blocks from %s "+
   349  				"(heights %d to %d)", initialHeight-height,
   350  				indexer.Name(), height+1, initialHeight)
   351  		}
   352  	}
   353  
   354  	// Fetch the current tip heights for each index along with tracking the
   355  	// lowest one so the catchup code only needs to start at the earliest
   356  	// block and is able to skip connecting the block for the indexes that
   357  	// don't need it.
   358  	bestHeight := chain.BestSnapshot().Height
   359  	lowestHeight := bestHeight
   360  	indexerHeights := make([]int32, len(m.enabledIndexes))
   361  	err = m.db.View(func(dbTx database.Tx) error {
   362  		for i, indexer := range m.enabledIndexes {
   363  			idxKey := indexer.Key()
   364  			hash, height, err := dbFetchIndexerTip(dbTx, idxKey)
   365  			if err != nil {
   366  				return err
   367  			}
   368  
   369  			log.Debugf("Current %s tip (height %d, hash %v)",
   370  				indexer.Name(), height, hash)
   371  			indexerHeights[i] = height
   372  			if height < lowestHeight {
   373  				lowestHeight = height
   374  			}
   375  		}
   376  		return nil
   377  	})
   378  	if err != nil {
   379  		return err
   380  	}
   381  
   382  	// Nothing to index if all of the indexes are caught up.
   383  	if lowestHeight == bestHeight {
   384  		return nil
   385  	}
   386  
   387  	// Create a progress logger for the indexing process below.
   388  	progressLogger := newBlockProgressLogger("Indexed", log)
   389  
   390  	// At this point, one or more indexes are behind the current best chain
   391  	// tip and need to be caught up, so log the details and loop through
   392  	// each block that needs to be indexed.
   393  	log.Infof("Catching up indexes from height %d to %d", lowestHeight,
   394  		bestHeight)
   395  	for height := lowestHeight + 1; height <= bestHeight; height++ {
   396  		// Load the block for the height since it is required to index
   397  		// it.
   398  		block, err := chain.BlockByHeight(height)
   399  		if err != nil {
   400  			return err
   401  		}
   402  
   403  		// Connect the block for all indexes that need it.
   404  		var view *blockchain.UtxoViewpoint
   405  		for i, indexer := range m.enabledIndexes {
   406  			// Skip indexes that don't need to be updated with this
   407  			// block.
   408  			if indexerHeights[i] >= height {
   409  				continue
   410  			}
   411  
   412  			err := m.db.Update(func(dbTx database.Tx) error {
   413  				// When the index requires all of the referenced
   414  				// txouts and they haven't been loaded yet, they
   415  				// need to be retrieved from the transaction
   416  				// index.
   417  				if view == nil && indexNeedsInputs(indexer) {
   418  					var err error
   419  					view, err = makeUtxoView(dbTx, block)
   420  					if err != nil {
   421  						return err
   422  					}
   423  				}
   424  				return dbIndexConnectBlock(dbTx, indexer, block,
   425  					view)
   426  			})
   427  			if err != nil {
   428  				return err
   429  			}
   430  			indexerHeights[i] = height
   431  		}
   432  
   433  		// Log indexing progress.
   434  		progressLogger.LogBlockHeight(block)
   435  	}
   436  
   437  	log.Infof("Indexes caught up to height %d", bestHeight)
   438  	return nil
   439  }
   440  
   441  // indexNeedsInputs returns whether or not the index needs access to the txouts
   442  // referenced by the transaction inputs being indexed.
   443  func indexNeedsInputs(index Indexer) bool {
   444  	if idx, ok := index.(NeedsInputser); ok {
   445  		return idx.NeedsInputs()
   446  	}
   447  
   448  	return false
   449  }
   450  
   451  // dbFetchTx looks up the passed transaction hash in the transaction index and
   452  // loads it from the database.
   453  func dbFetchTx(dbTx database.Tx, hash *wire.ShaHash) (*wire.MsgTx, error) {
   454  	// Look up the location of the transaction.
   455  	blockRegion, err := dbFetchTxIndexEntry(dbTx, hash)
   456  	if err != nil {
   457  		return nil, err
   458  	}
   459  	if blockRegion == nil {
   460  		return nil, fmt.Errorf("transaction %v not found", hash)
   461  	}
   462  
   463  	// Load the raw transaction bytes from the database.
   464  	txBytes, err := dbTx.FetchBlockRegion(blockRegion)
   465  	if err != nil {
   466  		return nil, err
   467  	}
   468  
   469  	// Deserialize the transaction.
   470  	var msgTx wire.MsgTx
   471  	err = msgTx.Deserialize(bytes.NewReader(txBytes))
   472  	if err != nil {
   473  		return nil, err
   474  	}
   475  
   476  	return &msgTx, nil
   477  }
   478  
   479  // makeUtxoView creates a mock unspent transaction output view by using the
   480  // transaction index in order to look up all inputs referenced by the
   481  // transactions in the block.  This is sometimes needed when catching indexes up
   482  // because many of the txouts could actually already be spent however the
   483  // associated scripts are still required to index them.
   484  func makeUtxoView(dbTx database.Tx, block *godashutil.Block) (*blockchain.UtxoViewpoint, error) {
   485  	view := blockchain.NewUtxoViewpoint()
   486  	for txIdx, tx := range block.Transactions() {
   487  		// Coinbases do not reference any inputs.  Since the block is
   488  		// required to have already gone through full validation, it has
   489  		// already been proven on the first transaction in the block is
   490  		// a coinbase.
   491  		if txIdx == 0 {
   492  			continue
   493  		}
   494  
   495  		// Use the transaction index to load all of the referenced
   496  		// inputs and add their outputs to the view.
   497  		for _, txIn := range tx.MsgTx().TxIn {
   498  			originOut := &txIn.PreviousOutPoint
   499  			originTx, err := dbFetchTx(dbTx, &originOut.Hash)
   500  			if err != nil {
   501  				return nil, err
   502  			}
   503  
   504  			view.AddTxOuts(godashutil.NewTx(originTx), 0)
   505  		}
   506  	}
   507  
   508  	return view, nil
   509  }
   510  
   511  // ConnectBlock must be invoked when a block is extending the main chain.  It
   512  // keeps track of the state of each index it is managing, performs some sanity
   513  // checks, and invokes each indexer.
   514  //
   515  // This is part of the blockchain.IndexManager interface.
   516  func (m *Manager) ConnectBlock(dbTx database.Tx, block *godashutil.Block, view *blockchain.UtxoViewpoint) error {
   517  	// Call each of the currently active optional indexes with the block
   518  	// being connected so they can update accordingly.
   519  	for _, index := range m.enabledIndexes {
   520  		err := dbIndexConnectBlock(dbTx, index, block, view)
   521  		if err != nil {
   522  			return err
   523  		}
   524  	}
   525  	return nil
   526  }
   527  
   528  // DisconnectBlock must be invoked when a block is being disconnected from the
   529  // end of the main chain.  It keeps track of the state of each index it is
   530  // managing, performs some sanity checks, and invokes each indexer to remove
   531  // the index entries associated with the block.
   532  //
   533  // This is part of the blockchain.IndexManager interface.
   534  func (m *Manager) DisconnectBlock(dbTx database.Tx, block *godashutil.Block, view *blockchain.UtxoViewpoint) error {
   535  	// Call each of the currently active optional indexes with the block
   536  	// being disconnected so they can update accordingly.
   537  	for _, index := range m.enabledIndexes {
   538  		err := dbIndexDisconnectBlock(dbTx, index, block, view)
   539  		if err != nil {
   540  			return err
   541  		}
   542  	}
   543  	return nil
   544  }
   545  
   546  // NewManager returns a new index manager with the provided indexes enabled.
   547  //
   548  // The manager returned satisfies the blockchain.IndexManager interface and thus
   549  // cleanly plugs into the normal blockchain processing path.
   550  func NewManager(db database.DB, enabledIndexes []Indexer) *Manager {
   551  	return &Manager{
   552  		db:             db,
   553  		enabledIndexes: enabledIndexes,
   554  	}
   555  }
   556  
   557  // dropIndex drops the passed index from the database.  Since indexes can be
   558  // massive, it deletes the index in multiple database transactions in order to
   559  // keep memory usage to reasonable levels.  It also marks the drop in progress
   560  // so the drop can be resumed if it is stopped before it is done before the
   561  // index can be used again.
   562  func dropIndex(db database.DB, idxKey []byte, idxName string) error {
   563  	// Nothing to do if the index doesn't already exist.
   564  	var needsDelete bool
   565  	err := db.View(func(dbTx database.Tx) error {
   566  		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   567  		if indexesBucket != nil && indexesBucket.Get(idxKey) != nil {
   568  			needsDelete = true
   569  		}
   570  		return nil
   571  	})
   572  	if err != nil {
   573  		return err
   574  	}
   575  	if !needsDelete {
   576  		log.Infof("Not dropping %s because it does not exist", idxName)
   577  		return nil
   578  	}
   579  
   580  	// Mark that the index is in the process of being dropped so that it
   581  	// can be resumed on the next start if interrupted before the process is
   582  	// complete.
   583  	log.Infof("Dropping all %s entries.  This might take a while...",
   584  		idxName)
   585  	err = db.Update(func(dbTx database.Tx) error {
   586  		indexesBucket := dbTx.Metadata().Bucket(indexTipsBucketName)
   587  		return indexesBucket.Put(indexDropKey(idxKey), idxKey)
   588  	})
   589  	if err != nil {
   590  		return err
   591  	}
   592  
   593  	// Since the indexes can be so large, attempting to simply delete
   594  	// the bucket in a single database transaction would result in massive
   595  	// memory usage and likely crash many systems due to ulimits.  In order
   596  	// to avoid this, use a cursor to delete a maximum number of entries out
   597  	// of the bucket at a time.
   598  	const maxDeletions = 2000000
   599  	var totalDeleted uint64
   600  	for numDeleted := maxDeletions; numDeleted == maxDeletions; {
   601  		numDeleted = 0
   602  		err := db.Update(func(dbTx database.Tx) error {
   603  			bucket := dbTx.Metadata().Bucket(idxKey)
   604  			cursor := bucket.Cursor()
   605  			for ok := cursor.First(); ok; ok = cursor.Next() &&
   606  				numDeleted < maxDeletions {
   607  
   608  				if err := cursor.Delete(); err != nil {
   609  					return err
   610  				}
   611  				numDeleted++
   612  			}
   613  			return nil
   614  		})
   615  		if err != nil {
   616  			return err
   617  		}
   618  
   619  		if numDeleted > 0 {
   620  			totalDeleted += uint64(numDeleted)
   621  			log.Infof("Deleted %d keys (%d total) from %s",
   622  				numDeleted, totalDeleted, idxName)
   623  		}
   624  	}
   625  
   626  	// Call extra index specific deinitialization for the transaction index.
   627  	if idxName == txIndexName {
   628  		if err := dropBlockIDIndex(db); err != nil {
   629  			return err
   630  		}
   631  	}
   632  
   633  	// Remove the index tip, index bucket, and in-progress drop flag now
   634  	// that all index entries have been removed.
   635  	err = db.Update(func(dbTx database.Tx) error {
   636  		meta := dbTx.Metadata()
   637  		indexesBucket := meta.Bucket(indexTipsBucketName)
   638  		if err := indexesBucket.Delete(idxKey); err != nil {
   639  			return err
   640  		}
   641  
   642  		if err := meta.DeleteBucket(idxKey); err != nil {
   643  			return err
   644  		}
   645  
   646  		return indexesBucket.Delete(indexDropKey(idxKey))
   647  	})
   648  	if err != nil {
   649  		return err
   650  	}
   651  
   652  	log.Infof("Dropped %s", idxName)
   653  	return nil
   654  }