Skip to content

Commit a932b54

Browse files
author
Chris Elder
committed
FAB-3046 Add CouchDB batch select operations
This is change 2 of 4 for FAB-2725 CouchDB optimizations Motivation for this change: Interactions with CouchDB are currently done individually. Need to switch to using bulk operations to get optimal performance from CouchDB. Need to performance test and stress test. - Add bulk select methods to couchdb Change-Id: I463530e0875176de0e0ab3cdf7971b1e3d7b4b36 Signed-off-by: Chris Elder <[email protected]>
1 parent 0640d43 commit a932b54

File tree

2 files changed

+175
-1
lines changed

2 files changed

+175
-1
lines changed

core/ledger/util/couchdb/couchdb.go

+64
Original file line numberDiff line numberDiff line change
@@ -960,6 +960,70 @@ func (dbclient *CouchDatabase) QueryDocuments(query string) (*[]QueryResult, err
960960

961961
}
962962

963+
//BatchRetrieveIDRevision - batch method to retrieve IDs and revisions
964+
func (dbclient *CouchDatabase) BatchRetrieveIDRevision(keys []string) ([]*DocMetadata, error) {
965+
966+
batchURL, err := url.Parse(dbclient.CouchInstance.conf.URL)
967+
if err != nil {
968+
logger.Errorf("URL parse error: %s", err.Error())
969+
return nil, err
970+
}
971+
batchURL.Path = dbclient.DBName + "/_all_docs"
972+
973+
queryParms := batchURL.Query()
974+
queryParms.Add("include_docs", "true")
975+
batchURL.RawQuery = queryParms.Encode()
976+
977+
keymap := make(map[string]interface{})
978+
979+
keymap["keys"] = keys
980+
981+
jsonKeys, err := json.Marshal(keymap)
982+
if err != nil {
983+
return nil, err
984+
}
985+
986+
//Set up a buffer for the data response from CouchDB
987+
data := new(bytes.Buffer)
988+
989+
data.ReadFrom(bytes.NewReader(jsonKeys))
990+
991+
resp, _, err := dbclient.CouchInstance.handleRequest(http.MethodPost, batchURL.String(), data, "", "")
992+
if err != nil {
993+
return nil, err
994+
}
995+
defer resp.Body.Close()
996+
997+
if logger.IsEnabledFor(logging.DEBUG) {
998+
dump, _ := httputil.DumpResponse(resp, false)
999+
// compact debug log by replacing carriage return / line feed with dashes to separate http headers
1000+
logger.Debugf("HTTP Response: %s", bytes.Replace(dump, []byte{0x0d, 0x0a}, []byte{0x20, 0x7c, 0x20}, -1))
1001+
}
1002+
1003+
//handle as JSON document
1004+
jsonResponseRaw, err := ioutil.ReadAll(resp.Body)
1005+
if err != nil {
1006+
return nil, err
1007+
}
1008+
1009+
var jsonResponse = &BatchRetrieveDocMedatadataResponse{}
1010+
1011+
err2 := json.Unmarshal(jsonResponseRaw, &jsonResponse)
1012+
if err2 != nil {
1013+
return nil, err2
1014+
}
1015+
1016+
revisionDocs := []*DocMetadata{}
1017+
1018+
for _, row := range jsonResponse.Rows {
1019+
revisionDoc := &DocMetadata{ID: row.ID, Rev: row.Doc.Rev, Version: row.Doc.Version}
1020+
revisionDocs = append(revisionDocs, revisionDoc)
1021+
}
1022+
1023+
return revisionDocs, nil
1024+
1025+
}
1026+
9631027
//BatchUpdateDocuments - batch method to batch update documents
9641028
func (dbclient *CouchDatabase) BatchUpdateDocuments(documents []*CouchDoc) ([]*BatchUpdateResponse, error) {
9651029

core/ledger/util/couchdb/couchdb_test.go

+111-1
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,7 @@ func TestRichQuery(t *testing.T) {
777777
}
778778
}
779779

780-
func TestBatchCreateRetrieve(t *testing.T) {
780+
func TestBatchBatchOperations(t *testing.T) {
781781

782782
if ledgerconfig.IsCouchDBEnabled() == true {
783783

@@ -893,5 +893,115 @@ func TestBatchCreateRetrieve(t *testing.T) {
893893
testutil.AssertEquals(t, updateDoc.Error, updateDocumentConflictError)
894894
testutil.AssertEquals(t, updateDoc.Reason, updateDocumentConflictReason)
895895
}
896+
897+
//----------------------------------------------
898+
//Test Batch Retrieve Keys and Update
899+
900+
var keys []string
901+
902+
keys = append(keys, "marble01")
903+
keys = append(keys, "marble03")
904+
905+
batchRevs, err := db.BatchRetrieveIDRevision(keys)
906+
testutil.AssertNoError(t, err, fmt.Sprintf("Error when attempting retrieve revisions"))
907+
908+
batchUpdateDocs = []*CouchDoc{}
909+
910+
//iterate through the revision docs
911+
for _, revdoc := range batchRevs {
912+
if revdoc.ID == "marble01" {
913+
//update the json with the rev and add to the batch
914+
marble01Doc := addRevisionAndDeleteStatus(revdoc.Rev, byteJSON01, false)
915+
batchUpdateDocs = append(batchUpdateDocs, &CouchDoc{JSONValue: marble01Doc, Attachments: attachments1})
916+
}
917+
918+
if revdoc.ID == "marble03" {
919+
//update the json with the rev and add to the batch
920+
marble03Doc := addRevisionAndDeleteStatus(revdoc.Rev, byteJSON03, false)
921+
batchUpdateDocs = append(batchUpdateDocs, &CouchDoc{JSONValue: marble03Doc, Attachments: attachments3})
922+
}
923+
}
924+
925+
//Update couchdb with the batch
926+
batchUpdateResp, err = db.BatchUpdateDocuments(batchUpdateDocs)
927+
testutil.AssertNoError(t, err, fmt.Sprintf("Error when attempting to update a batch of documents"))
928+
//check to make sure each batch update response was successful
929+
for _, updateDoc := range batchUpdateResp {
930+
testutil.AssertEquals(t, updateDoc.Ok, true)
931+
}
932+
933+
//----------------------------------------------
934+
//Test Batch Delete
935+
936+
keys = []string{}
937+
938+
keys = append(keys, "marble02")
939+
keys = append(keys, "marble04")
940+
941+
batchRevs, err = db.BatchRetrieveIDRevision(keys)
942+
testutil.AssertNoError(t, err, fmt.Sprintf("Error when attempting retrieve revisions"))
943+
944+
batchUpdateDocs = []*CouchDoc{}
945+
946+
//iterate through the revision docs
947+
for _, revdoc := range batchRevs {
948+
if revdoc.ID == "marble02" {
949+
//update the json with the rev and add to the batch
950+
marble02Doc := addRevisionAndDeleteStatus(revdoc.Rev, byteJSON02, true)
951+
batchUpdateDocs = append(batchUpdateDocs, &CouchDoc{JSONValue: marble02Doc, Attachments: attachments1})
952+
}
953+
if revdoc.ID == "marble04" {
954+
//update the json with the rev and add to the batch
955+
marble04Doc := addRevisionAndDeleteStatus(revdoc.Rev, byteJSON04, true)
956+
batchUpdateDocs = append(batchUpdateDocs, &CouchDoc{JSONValue: marble04Doc, Attachments: attachments3})
957+
}
958+
}
959+
960+
//Update couchdb with the batch
961+
batchUpdateResp, err = db.BatchUpdateDocuments(batchUpdateDocs)
962+
testutil.AssertNoError(t, err, fmt.Sprintf("Error when attempting to update a batch of documents"))
963+
964+
//check to make sure each batch update response was successful
965+
for _, updateDoc := range batchUpdateResp {
966+
testutil.AssertEquals(t, updateDoc.Ok, true)
967+
}
968+
969+
//Retrieve the test document
970+
dbGetResp, _, geterr = db.ReadDoc("marble02")
971+
testutil.AssertNoError(t, geterr, fmt.Sprintf("Error when trying to retrieve a document"))
972+
973+
//assert the value was deleted
974+
testutil.AssertNil(t, dbGetResp)
975+
976+
//Retrieve the test document
977+
dbGetResp, _, geterr = db.ReadDoc("marble04")
978+
testutil.AssertNoError(t, geterr, fmt.Sprintf("Error when trying to retrieve a document"))
979+
980+
//assert the value was deleted
981+
testutil.AssertNil(t, dbGetResp)
982+
}
983+
}
984+
985+
//addRevisionAndDeleteStatus adds keys for version and chaincodeID to the JSON value
986+
func addRevisionAndDeleteStatus(revision string, value []byte, deleted bool) []byte {
987+
988+
//create a version mapping
989+
jsonMap := make(map[string]interface{})
990+
991+
json.Unmarshal(value, &jsonMap)
992+
993+
//add the revision
994+
if revision != "" {
995+
jsonMap["_rev"] = revision
996+
}
997+
998+
//If this record is to be deleted, set the "_deleted" property to true
999+
if deleted {
1000+
jsonMap["_deleted"] = true
8961001
}
1002+
//marshal the data to a byte array
1003+
returnJSON, _ := json.Marshal(jsonMap)
1004+
1005+
return returnJSON
1006+
8971007
}

0 commit comments

Comments
 (0)