github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/core/ledger/kvledger/txmgmt/statedb/statecouchdb/commit_handling.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package statecouchdb
     8  
     9  import (
    10  	"fmt"
    11  	"math"
    12  	"sync"
    13  
    14  	"github.com/hechain20/hechain/core/ledger/kvledger/txmgmt/statedb"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  type committer struct {
    19  	db             *couchDatabase
    20  	batchUpdateMap map[string]*batchableDocument
    21  	namespace      string
    22  	cacheKVs       cacheKVs
    23  	cacheEnabled   bool
    24  }
    25  
    26  func (c *committer) addToCacheUpdate(kv *keyValue) {
    27  	if !c.cacheEnabled {
    28  		return
    29  	}
    30  
    31  	if kv.Value == nil {
    32  		// nil value denotes a delete operation
    33  		c.cacheKVs[kv.key] = nil
    34  		return
    35  	}
    36  
    37  	c.cacheKVs[kv.key] = &CacheValue{
    38  		Version:        kv.Version.ToBytes(),
    39  		Value:          kv.Value,
    40  		Metadata:       kv.Metadata,
    41  		AdditionalInfo: []byte(kv.revision),
    42  	}
    43  }
    44  
    45  func (c *committer) updateRevisionInCacheUpdate(key, rev string) {
    46  	if !c.cacheEnabled {
    47  		return
    48  	}
    49  	cv := c.cacheKVs[key]
    50  	if cv == nil {
    51  		// nil value denotes a delete
    52  		return
    53  	}
    54  	cv.AdditionalInfo = []byte(rev)
    55  }
    56  
    57  // buildCommitters builds committers per namespace. Each committer transforms the
    58  // given batch in the form of underlying db and keep it in memory.
    59  func (vdb *VersionedDB) buildCommitters(updates *statedb.UpdateBatch) ([]*committer, error) {
    60  	namespaces := updates.GetUpdatedNamespaces()
    61  
    62  	// for each namespace, we build multiple committers (based on maxBatchSize per namespace)
    63  	var wg sync.WaitGroup
    64  	nsCommittersChan := make(chan []*committer, len(namespaces))
    65  	defer close(nsCommittersChan)
    66  	errsChan := make(chan error, len(namespaces))
    67  	defer close(errsChan)
    68  
    69  	// for each namespace, we build committers in parallel. This is because,
    70  	// the committer building process requires fetching of missing revisions
    71  	// that in turn, we want to do in parallel
    72  	for _, ns := range namespaces {
    73  		nsUpdates := updates.GetUpdates(ns)
    74  		wg.Add(1)
    75  		go func(ns string) {
    76  			defer wg.Done()
    77  			committers, err := vdb.buildCommittersForNs(ns, nsUpdates)
    78  			if err != nil {
    79  				errsChan <- err
    80  				return
    81  			}
    82  			nsCommittersChan <- committers
    83  		}(ns)
    84  	}
    85  	wg.Wait()
    86  
    87  	// collect all committers
    88  	var allCommitters []*committer
    89  	select {
    90  	case err := <-errsChan:
    91  		return nil, errors.WithStack(err)
    92  	default:
    93  		for i := 0; i < len(namespaces); i++ {
    94  			allCommitters = append(allCommitters, <-nsCommittersChan...)
    95  		}
    96  	}
    97  
    98  	return allCommitters, nil
    99  }
   100  
   101  func (vdb *VersionedDB) buildCommittersForNs(ns string, nsUpdates map[string]*statedb.VersionedValue) ([]*committer, error) {
   102  	db, err := vdb.getNamespaceDBHandle(ns)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	// for each namespace, build mutiple committers based on the maxBatchSize
   107  	maxBatchSize := db.couchInstance.maxBatchUpdateSize()
   108  	numCommitters := 1
   109  	if maxBatchSize > 0 {
   110  		numCommitters = int(math.Ceil(float64(len(nsUpdates)) / float64(maxBatchSize)))
   111  	}
   112  	committers := make([]*committer, numCommitters)
   113  
   114  	cacheEnabled := vdb.cache.enabled(ns)
   115  
   116  	for i := 0; i < numCommitters; i++ {
   117  		committers[i] = &committer{
   118  			db:             db,
   119  			batchUpdateMap: make(map[string]*batchableDocument),
   120  			namespace:      ns,
   121  			cacheKVs:       make(cacheKVs),
   122  			cacheEnabled:   cacheEnabled,
   123  		}
   124  	}
   125  
   126  	// for each committer, create a couchDoc per key-value pair present in the update batch
   127  	// which are associated with the committer's namespace.
   128  	revisions, err := vdb.getRevisions(ns, nsUpdates)
   129  	if err != nil {
   130  		return nil, err
   131  	}
   132  
   133  	i := 0
   134  	for key, vv := range nsUpdates {
   135  		kv := &keyValue{key: key, revision: revisions[key], VersionedValue: vv}
   136  		couchDoc, err := keyValToCouchDoc(kv)
   137  		if err != nil {
   138  			return nil, err
   139  		}
   140  		committers[i].batchUpdateMap[key] = &batchableDocument{CouchDoc: *couchDoc, Deleted: vv.Value == nil}
   141  		committers[i].addToCacheUpdate(kv)
   142  		if maxBatchSize > 0 && len(committers[i].batchUpdateMap) == maxBatchSize {
   143  			i++
   144  		}
   145  	}
   146  	return committers, nil
   147  }
   148  
   149  func (vdb *VersionedDB) executeCommitter(committers []*committer) error {
   150  	errsChan := make(chan error, len(committers))
   151  	defer close(errsChan)
   152  	var wg sync.WaitGroup
   153  	wg.Add(len(committers))
   154  
   155  	for _, c := range committers {
   156  		go func(c *committer) {
   157  			defer wg.Done()
   158  			if err := c.commitUpdates(); err != nil {
   159  				errsChan <- err
   160  			}
   161  		}(c)
   162  	}
   163  	wg.Wait()
   164  
   165  	select {
   166  	case err := <-errsChan:
   167  		return errors.WithStack(err)
   168  	default:
   169  		return nil
   170  	}
   171  }
   172  
   173  // commitUpdates commits the given updates to couchdb
   174  func (c *committer) commitUpdates() error {
   175  	docs := []*couchDoc{}
   176  	for _, update := range c.batchUpdateMap {
   177  		docs = append(docs, &update.CouchDoc)
   178  	}
   179  
   180  	// Do the bulk update into couchdb. Note that this will do retries if the entire bulk update fails or times out
   181  	responses, err := c.db.batchUpdateDocuments(docs)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	// IF INDIVIDUAL DOCUMENTS IN THE BULK UPDATE DID NOT SUCCEED, TRY THEM INDIVIDUALLY
   187  	// iterate through the response from CouchDB by document
   188  	for _, resp := range responses {
   189  		// If the document returned an error, retry the individual document
   190  		if resp.Ok {
   191  			c.updateRevisionInCacheUpdate(resp.ID, resp.Rev)
   192  			continue
   193  		}
   194  		doc := c.batchUpdateMap[resp.ID]
   195  
   196  		var err error
   197  		// Remove the "_rev" from the JSON before saving
   198  		// this will allow the CouchDB retry logic to retry revisions without encountering
   199  		// a mismatch between the "If-Match" and the "_rev" tag in the JSON
   200  		if doc.CouchDoc.jsonValue != nil {
   201  			err = removeJSONRevision(&doc.CouchDoc.jsonValue)
   202  			if err != nil {
   203  				return err
   204  			}
   205  		}
   206  		// Check to see if the document was added to the batch as a delete type document
   207  		if doc.Deleted {
   208  			logger.Warningf("CouchDB batch document delete encountered an problem. Retrying delete for document ID:%s", resp.ID)
   209  			// If this is a deleted document, then retry the delete
   210  			// If the delete fails due to a document not being found (404 error),
   211  			// the document has already been deleted and the DeleteDoc will not return an error
   212  			err = c.db.deleteDoc(resp.ID, "")
   213  		} else {
   214  			logger.Warningf("CouchDB batch document update encountered an problem. Reason:%s, Retrying update for document ID:%s", resp.Reason, resp.ID)
   215  			// Save the individual document to couchdb
   216  			// Note that this will do retries as needed
   217  			var revision string
   218  			revision, err = c.db.saveDoc(resp.ID, "", &doc.CouchDoc)
   219  			c.updateRevisionInCacheUpdate(resp.ID, revision)
   220  		}
   221  
   222  		// If the single document update or delete returns an error, then throw the error
   223  		if err != nil {
   224  			errorString := fmt.Sprintf("error saving document ID: %v. Error: %s,  Reason: %s",
   225  				resp.ID, resp.Error, resp.Reason)
   226  
   227  			logger.Errorf(errorString)
   228  			return errors.WithMessage(err, errorString)
   229  		}
   230  	}
   231  	return nil
   232  }
   233  
   234  func (vdb *VersionedDB) getRevisions(ns string, nsUpdates map[string]*statedb.VersionedValue) (map[string]string, error) {
   235  	revisions := make(map[string]string)
   236  	nsRevs := vdb.committedDataCache.revs[ns]
   237  
   238  	var missingKeys []string
   239  	var ok bool
   240  	for key := range nsUpdates {
   241  		if revisions[key], ok = nsRevs[key]; !ok {
   242  			missingKeys = append(missingKeys, key)
   243  		}
   244  	}
   245  
   246  	if len(missingKeys) == 0 {
   247  		// all revisions were present in the committedDataCache
   248  		return revisions, nil
   249  	}
   250  
   251  	missingKeys, err := vdb.addMissingRevisionsFromCache(ns, missingKeys, revisions)
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	if len(missingKeys) == 0 {
   257  		// remaining revisions were present in the state cache
   258  		return revisions, nil
   259  	}
   260  
   261  	// don't update the cache for missing entries as
   262  	// revisions are going to get changed after the commit
   263  	if err := vdb.addMissingRevisionsFromDB(ns, missingKeys, revisions); err != nil {
   264  		return nil, err
   265  	}
   266  	return revisions, nil
   267  }
   268  
   269  func (vdb *VersionedDB) addMissingRevisionsFromCache(ns string, keys []string, revs map[string]string) ([]string, error) {
   270  	if !vdb.cache.enabled(ns) {
   271  		return keys, nil
   272  	}
   273  
   274  	var missingKeys []string
   275  	for _, k := range keys {
   276  		cv, err := vdb.cache.getState(vdb.chainName, ns, k)
   277  		if err != nil {
   278  			return nil, err
   279  		}
   280  		if cv == nil {
   281  			missingKeys = append(missingKeys, k)
   282  			continue
   283  		}
   284  		revs[k] = string(cv.AdditionalInfo)
   285  	}
   286  	return missingKeys, nil
   287  }
   288  
   289  func (vdb *VersionedDB) addMissingRevisionsFromDB(ns string, missingKeys []string, revisions map[string]string) error {
   290  	db, err := vdb.getNamespaceDBHandle(ns)
   291  	if err != nil {
   292  		return err
   293  	}
   294  
   295  	logger.Debugf("Pulling revisions for the [%d] keys for namsespace [%s] that were not part of the readset", len(missingKeys), db.dbName)
   296  	retrievedMetadata, err := retrieveNsMetadata(db, missingKeys)
   297  	if err != nil {
   298  		return err
   299  	}
   300  	for _, metadata := range retrievedMetadata {
   301  		revisions[metadata.ID] = metadata.Rev
   302  	}
   303  
   304  	return nil
   305  }
   306  
   307  // batchableDocument defines a document for a batch
   308  type batchableDocument struct {
   309  	CouchDoc couchDoc
   310  	Deleted  bool
   311  }