Skip to content

Commit 945138e

Browse files
committed
Fix perf issue by key-endorsement to regular chaincode
Key-level endorsement needs to access the metadata for each of the keys in the write-set. Even the chaincodes that do not use this feature will end up paying this additional penality. Especially, in an environment that uses couchdb, the performance regression will be visible for the existing chaincodes. The major reason is that the metadata is accessed one at a time during validation and commit path whereas, couchdb is better used if the data can be loaded in bulk. Ideally, the data required by key-level endorsement for the entire block should be loaded in a single bulk load call. Which would benefit even the chaincodes that leverage this feature. However, this would need some refactoring that can be taken up in a future release. This CR introduces a fix which would save the regular chaincodes (that do not use key-level endorsement feature) from paying the avobe mentioned penality. In this fix, we track the chaincodes that do not use metadata and for such chaincodes, the GetStateMetadata call simply returns nil without going to the underlying db. Even when we start using the bulk-load for key-level endorsement, this fix will still be relevant in the sense that the bulkload call will be avoided for regular chaincodes FAB-11700 #done Change-Id: Icfc654b7d27f727f1811a5a300400e92b6e8be9d Signed-off-by: manish <manish.sethi@gmail.com>
1 parent 861254d commit 945138e

File tree

17 files changed

+1238
-73
lines changed

17 files changed

+1238
-73
lines changed

common/ledger/util/leveldbhelper/leveldb_provider.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ func (h *DBHandle) Delete(key []byte, sync bool) error {
8181

8282
// WriteBatch writes a batch in an atomic way
8383
func (h *DBHandle) WriteBatch(batch *UpdateBatch, sync bool) error {
84+
if len(batch.KVs) == 0 {
85+
return nil
86+
}
8487
levelBatch := &leveldb.Batch{}
8588
for k, v := range batch.KVs {
8689
key := constructLevelKey(h.dbName, []byte(k))

core/ledger/kvledger/bookkeeping/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ type Category int
1919
const (
2020
// PvtdataExpiry repersents the bookkeeping related to expiry of pvtdata because of BTL policy
2121
PvtdataExpiry Category = iota
22+
// MetadataPresenceIndicator maintains the bookkeeping about whether metadata is ever set for a namespace
23+
MetadataPresenceIndicator
2224
)
2325

2426
// Provider provides handle to different bookkeepers for the given ledger

core/ledger/kvledger/bookkeeping/test_exports.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type TestEnv struct {
2828
}
2929

3030
// NewTestEnv construct a TestEnv for testing
31-
func NewTestEnv(t *testing.T) *TestEnv {
31+
func NewTestEnv(t testing.TB) *TestEnv {
3232
removePath(t)
3333
provider := NewProvider()
3434
return &TestEnv{t, provider}

core/ledger/kvledger/kv_ledger_provider.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,14 @@ func NewProvider() (ledger.PeerLedgerProvider, error) {
5757
// Initialize the ID store (inventory of chainIds/ledgerIds)
5858
idStore := openIDStore(ledgerconfig.GetLedgerProviderPath())
5959
ledgerStoreProvider := ledgerstorage.NewProvider()
60+
bookkeepingProvider := bookkeeping.NewProvider()
6061
// Initialize the versioned database (state database)
61-
vdbProvider, err := privacyenabledstate.NewCommonStorageDBProvider()
62+
vdbProvider, err := privacyenabledstate.NewCommonStorageDBProvider(bookkeepingProvider)
6263
if err != nil {
6364
return nil, err
6465
}
6566
// Initialize the history database (index for history of values by key)
6667
historydbProvider := historyleveldb.NewHistoryDBProvider()
67-
bookkeepingProvider := bookkeeping.NewProvider()
6868
logger.Info("ledger provider Initialized")
6969
provider := &Provider{idStore, ledgerStoreProvider,
7070
vdbProvider, historydbProvider, nil, nil, bookkeepingProvider, nil}

core/ledger/kvledger/txmgmt/privacyenabledstate/common_storage_db.go

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ import (
1515
"github.com/hyperledger/fabric/core/common/ccprovider"
1616
"github.com/hyperledger/fabric/core/common/privdata"
1717
"github.com/hyperledger/fabric/core/ledger/cceventmgmt"
18-
"github.com/hyperledger/fabric/protos/common"
19-
18+
"github.com/hyperledger/fabric/core/ledger/kvledger/bookkeeping"
2019
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb"
2120
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb/statecouchdb"
2221
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb/stateleveldb"
2322
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version"
2423
"github.com/hyperledger/fabric/core/ledger/ledgerconfig"
24+
"github.com/hyperledger/fabric/protos/common"
2525
"github.com/pkg/errors"
2626
)
2727

@@ -36,10 +36,11 @@ const (
3636
// CommonStorageDBProvider implements interface DBProvider
3737
type CommonStorageDBProvider struct {
3838
statedb.VersionedDBProvider
39+
bookkeepingProvider bookkeeping.Provider
3940
}
4041

4142
// NewCommonStorageDBProvider constructs an instance of DBProvider
42-
func NewCommonStorageDBProvider() (DBProvider, error) {
43+
func NewCommonStorageDBProvider(bookkeeperProvider bookkeeping.Provider) (DBProvider, error) {
4344
var vdbProvider statedb.VersionedDBProvider
4445
var err error
4546
if ledgerconfig.IsCouchDBEnabled() {
@@ -49,7 +50,7 @@ func NewCommonStorageDBProvider() (DBProvider, error) {
4950
} else {
5051
vdbProvider = stateleveldb.NewVersionedDBProvider()
5152
}
52-
return &CommonStorageDBProvider{vdbProvider}, nil
53+
return &CommonStorageDBProvider{vdbProvider, bookkeeperProvider}, nil
5354
}
5455

5556
// GetDBHandle implements function from interface DBProvider
@@ -58,7 +59,9 @@ func (p *CommonStorageDBProvider) GetDBHandle(id string) (DB, error) {
5859
if err != nil {
5960
return nil, err
6061
}
61-
return NewCommonStorageDB(vdb, id)
62+
bookkeeper := p.bookkeepingProvider.GetDBHandle(id, bookkeeping.MetadataPresenceIndicator)
63+
metadataHint := newMetadataHint(bookkeeper)
64+
return NewCommonStorageDB(vdb, id, metadataHint)
6265
}
6366

6467
// Close implements function from interface DBProvider
@@ -70,12 +73,13 @@ func (p *CommonStorageDBProvider) Close() {
7073
// both the public and private data
7174
type CommonStorageDB struct {
7275
statedb.VersionedDB
76+
metadataHint *metadataHint
7377
}
7478

7579
// NewCommonStorageDB wraps a VersionedDB instance. The public data is managed directly by the wrapped versionedDB.
7680
// For managing the hashed data and private data, this implementation creates separate namespaces in the wrapped db
77-
func NewCommonStorageDB(vdb statedb.VersionedDB, ledgerid string) (DB, error) {
78-
return &CommonStorageDB{VersionedDB: vdb}, nil
81+
func NewCommonStorageDB(vdb statedb.VersionedDB, ledgerid string, metadataHint *metadataHint) (DB, error) {
82+
return &CommonStorageDB{vdb, metadataHint}, nil
7983
}
8084

8185
// IsBulkOptimizable implements corresponding function in interface DB
@@ -197,9 +201,39 @@ func (s *CommonStorageDB) ApplyPrivacyAwareUpdates(updates *UpdateBatch, height
197201
combinedUpdates := updates.PubUpdates
198202
addPvtUpdates(combinedUpdates, updates.PvtUpdates)
199203
addHashedUpdates(combinedUpdates, updates.HashUpdates, !s.BytesKeySuppoted())
204+
s.metadataHint.setMetadataUsedFlag(updates)
200205
return s.VersionedDB.ApplyUpdates(combinedUpdates.UpdateBatch, height)
201206
}
202207

208+
// GetStateMetadata implements corresponding function in interface DB. This implementation provides
209+
// an optimization such that it keeps track if a namespaces has never stored metadata for any of
210+
// its items, the value 'nil' is returned without going to the db. This is intented to be invoked
211+
// in the validation and commit path. This saves the chaincodes from paying unnecessary performance
212+
// penality if they do not use features that leverage metadata (such as key-level endorsement),
213+
func (s *CommonStorageDB) GetStateMetadata(namespace, key string) ([]byte, error) {
214+
if !s.metadataHint.metadataEverUsedFor(namespace) {
215+
return nil, nil
216+
}
217+
vv, err := s.GetState(namespace, key)
218+
if err != nil || vv == nil {
219+
return nil, err
220+
}
221+
return vv.Metadata, nil
222+
}
223+
224+
// GetPrivateDataMetadataByHash implements corresponding function in interface DB. For additional details, see
225+
// decription of the similar function 'GetStateMetadata'
226+
func (s *CommonStorageDB) GetPrivateDataMetadataByHash(namespace, collection string, keyHash []byte) ([]byte, error) {
227+
if !s.metadataHint.metadataEverUsedFor(namespace) {
228+
return nil, nil
229+
}
230+
vv, err := s.GetValueHash(namespace, collection, keyHash)
231+
if err != nil || vv == nil {
232+
return nil, err
233+
}
234+
return vv.Metadata, nil
235+
}
236+
203237
func (s *CommonStorageDB) getCollectionConfigMap(chaincodeDefinition *cceventmgmt.ChaincodeDefinition) (map[string]bool, error) {
204238
var collectionConfigsBytes []byte
205239
collectionConfigsMap := make(map[string]bool)
@@ -254,17 +288,14 @@ func (s *CommonStorageDB) getCollectionConfigMap(chaincodeDefinition *cceventmgm
254288
// is acceptable since peer can continue in the committing role without the indexes. However, executing chaincode queries
255289
// may be affected, until a new chaincode with fixed indexes is installed and instantiated
256290
func (s *CommonStorageDB) HandleChaincodeDeploy(chaincodeDefinition *cceventmgmt.ChaincodeDefinition, dbArtifactsTar []byte) error {
257-
258291
//Check to see if the interface for IndexCapable is implemented
259292
indexCapable, ok := s.VersionedDB.(statedb.IndexCapable)
260293
if !ok {
261294
return nil
262295
}
263-
264296
if chaincodeDefinition == nil {
265297
return errors.New("chaincode definition not found while creating couchdb index")
266298
}
267-
268299
dbArtifacts, err := ccprovider.ExtractFileEntries(dbArtifactsTar, indexCapable.GetDBType())
269300
if err != nil {
270301
logger.Errorf("Index creation: error extracting db artifacts from tar for chaincode [%s]: %s", chaincodeDefinition.Name, err)
@@ -296,7 +327,6 @@ func (s *CommonStorageDB) HandleChaincodeDeploy(chaincodeDefinition *cceventmgmt
296327
if !ok {
297328
logger.Errorf("Error processing index for chaincode [%s]: cannot create an index for an undefined collection=[%s]", chaincodeDefinition.Name, collectionName)
298329
} else {
299-
300330
err := indexCapable.ProcessIndexesForChaincodeDeploy(derivePvtDataNs(chaincodeDefinition.Name, collectionName),
301331
archiveDirectoryEntries)
302332
if err != nil {

core/ledger/kvledger/txmgmt/privacyenabledstate/db.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ type DB interface {
3535
GetKeyHashVersion(namespace, collection string, keyHash []byte) (*version.Height, error)
3636
GetPrivateDataMultipleKeys(namespace, collection string, keys []string) ([]*statedb.VersionedValue, error)
3737
GetPrivateDataRangeScanIterator(namespace, collection, startKey, endKey string) (statedb.ResultsIterator, error)
38+
GetStateMetadata(namespace, key string) ([]byte, error)
39+
GetPrivateDataMetadataByHash(namespace, collection string, keyHash []byte) ([]byte, error)
3840
ExecuteQueryOnPrivateData(namespace, collection, query string) (statedb.ResultsIterator, error)
3941
ApplyPrivacyAwareUpdates(updates *UpdateBatch, height *version.Height) error
4042
}

core/ledger/kvledger/txmgmt/privacyenabledstate/db_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,11 +542,54 @@ func testHandleChainCodeDeploy(t *testing.T, env TestEnv) {
542542

543543
}
544544

545+
func TestMetadataRetrieval(t *testing.T) {
546+
for _, env := range testEnvs {
547+
t.Run(env.GetName(), func(t *testing.T) {
548+
testMetadataRetrieval(t, env)
549+
})
550+
}
551+
}
552+
553+
func testMetadataRetrieval(t *testing.T, env TestEnv) {
554+
env.Init(t)
555+
defer env.Cleanup()
556+
db := env.GetDBHandle("test-ledger-id")
557+
558+
updates := NewUpdateBatch()
559+
updates.PubUpdates.PutValAndMetadata("ns1", "key1", []byte("value1"), []byte("metadata1"), version.NewHeight(1, 1))
560+
updates.PubUpdates.PutValAndMetadata("ns1", "key2", []byte("value2"), nil, version.NewHeight(1, 2))
561+
updates.PubUpdates.PutValAndMetadata("ns2", "key3", []byte("value3"), nil, version.NewHeight(1, 3))
562+
563+
putPvtUpdatesWithMetadata(t, updates, "ns1", "coll1", "key1", []byte("pvt_value1"), []byte("metadata1"), version.NewHeight(1, 4))
564+
putPvtUpdatesWithMetadata(t, updates, "ns1", "coll1", "key2", []byte("pvt_value2"), nil, version.NewHeight(1, 5))
565+
putPvtUpdatesWithMetadata(t, updates, "ns2", "coll1", "key3", []byte("pvt_value3"), nil, version.NewHeight(1, 6))
566+
db.ApplyPrivacyAwareUpdates(updates, version.NewHeight(2, 6))
567+
568+
vm, _ := db.GetStateMetadata("ns1", "key1")
569+
assert.Equal(t, vm, []byte("metadata1"))
570+
vm, _ = db.GetStateMetadata("ns1", "key2")
571+
assert.Nil(t, vm)
572+
vm, _ = db.GetStateMetadata("ns2", "key3")
573+
assert.Nil(t, vm)
574+
575+
vm, _ = db.GetPrivateDataMetadataByHash("ns1", "coll1", util.ComputeStringHash("key1"))
576+
assert.Equal(t, vm, []byte("metadata1"))
577+
vm, _ = db.GetPrivateDataMetadataByHash("ns1", "coll1", util.ComputeStringHash("key2"))
578+
assert.Nil(t, vm)
579+
vm, _ = db.GetPrivateDataMetadataByHash("ns2", "coll1", util.ComputeStringHash("key3"))
580+
assert.Nil(t, vm)
581+
}
582+
545583
func putPvtUpdates(t *testing.T, updates *UpdateBatch, ns, coll, key string, value []byte, ver *version.Height) {
546584
updates.PvtUpdates.Put(ns, coll, key, value, ver)
547585
updates.HashUpdates.Put(ns, coll, util.ComputeStringHash(key), util.ComputeHash(value), ver)
548586
}
549587

588+
func putPvtUpdatesWithMetadata(t *testing.T, updates *UpdateBatch, ns, coll, key string, value []byte, metadata []byte, ver *version.Height) {
589+
updates.PvtUpdates.Put(ns, coll, key, value, ver)
590+
updates.HashUpdates.PutValHashAndMetadata(ns, coll, util.ComputeStringHash(key), util.ComputeHash(value), metadata, ver)
591+
}
592+
550593
func deletePvtUpdates(t *testing.T, updates *UpdateBatch, ns, coll, key string, ver *version.Height) {
551594
updates.PvtUpdates.Delete(ns, coll, key, ver)
552595
updates.HashUpdates.Delete(ns, coll, util.ComputeStringHash(key), ver)
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package privacyenabledstate
8+
9+
import (
10+
"github.com/hyperledger/fabric/common/ledger/util/leveldbhelper"
11+
)
12+
13+
type metadataHint struct {
14+
cache map[string]bool
15+
bookkeeper *leveldbhelper.DBHandle
16+
}
17+
18+
func newMetadataHint(bookkeeper *leveldbhelper.DBHandle) *metadataHint {
19+
cache := map[string]bool{}
20+
itr := bookkeeper.GetIterator(nil, nil)
21+
defer itr.Release()
22+
for itr.Next() {
23+
namespace := string(itr.Key())
24+
cache[namespace] = true
25+
}
26+
return &metadataHint{cache, bookkeeper}
27+
}
28+
29+
func (h *metadataHint) metadataEverUsedFor(namespace string) bool {
30+
return h.cache[namespace]
31+
}
32+
33+
func (h *metadataHint) setMetadataUsedFlag(updates *UpdateBatch) {
34+
batch := leveldbhelper.NewUpdateBatch()
35+
for ns := range filterNamespacesThatHasMetadata(updates) {
36+
if h.cache[ns] {
37+
continue
38+
}
39+
h.cache[ns] = true
40+
batch.Put([]byte(ns), []byte{})
41+
}
42+
h.bookkeeper.WriteBatch(batch, true)
43+
}
44+
45+
func filterNamespacesThatHasMetadata(updates *UpdateBatch) map[string]bool {
46+
namespaces := map[string]bool{}
47+
pubUpdates, hashUpdates := updates.PubUpdates, updates.HashUpdates
48+
// add ns for public data
49+
for _, ns := range pubUpdates.GetUpdatedNamespaces() {
50+
for _, vv := range updates.PubUpdates.GetUpdates(ns) {
51+
if vv.Metadata == nil {
52+
continue
53+
}
54+
namespaces[ns] = true
55+
}
56+
}
57+
// add ns for private hashes
58+
for ns, nsBatch := range hashUpdates.UpdateMap {
59+
for _, coll := range nsBatch.GetCollectionNames() {
60+
for _, vv := range nsBatch.GetUpdates(coll) {
61+
if vv.Metadata == nil {
62+
continue
63+
}
64+
namespaces[ns] = true
65+
}
66+
}
67+
}
68+
return namespaces
69+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package privacyenabledstate
8+
9+
import (
10+
"testing"
11+
12+
"github.com/hyperledger/fabric/core/ledger/kvledger/bookkeeping"
13+
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb/mock"
14+
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/version"
15+
"github.com/stretchr/testify/assert"
16+
)
17+
18+
func TestMetadataHintCorrectness(t *testing.T) {
19+
bookkeepingTestEnv := bookkeeping.NewTestEnv(t)
20+
defer bookkeepingTestEnv.Cleanup()
21+
bookkeeper := bookkeepingTestEnv.TestProvider.GetDBHandle("ledger1", bookkeeping.MetadataPresenceIndicator)
22+
23+
metadataHint := newMetadataHint(bookkeeper)
24+
assert.False(t, metadataHint.metadataEverUsedFor("ns1"))
25+
26+
updates := NewUpdateBatch()
27+
updates.PubUpdates.PutValAndMetadata("ns1", "key", []byte("value"), []byte("metadata"), version.NewHeight(1, 1))
28+
updates.PubUpdates.PutValAndMetadata("ns2", "key", []byte("value"), []byte("metadata"), version.NewHeight(1, 2))
29+
updates.PubUpdates.PutValAndMetadata("ns3", "key", []byte("value"), nil, version.NewHeight(1, 3))
30+
updates.HashUpdates.PutValAndMetadata("ns1_pvt", "key", "coll", []byte("value"), []byte("metadata"), version.NewHeight(1, 1))
31+
updates.HashUpdates.PutValAndMetadata("ns2_pvt", "key", "coll", []byte("value"), []byte("metadata"), version.NewHeight(1, 3))
32+
updates.HashUpdates.PutValAndMetadata("ns3_pvt", "key", "coll", []byte("value"), nil, version.NewHeight(1, 3))
33+
metadataHint.setMetadataUsedFlag(updates)
34+
35+
t.Run("MetadataAddedInCurrentSession", func(t *testing.T) {
36+
assert.True(t, metadataHint.metadataEverUsedFor("ns1"))
37+
assert.True(t, metadataHint.metadataEverUsedFor("ns2"))
38+
assert.True(t, metadataHint.metadataEverUsedFor("ns1_pvt"))
39+
assert.True(t, metadataHint.metadataEverUsedFor("ns2_pvt"))
40+
assert.False(t, metadataHint.metadataEverUsedFor("ns3"))
41+
assert.False(t, metadataHint.metadataEverUsedFor("ns4"))
42+
})
43+
44+
t.Run("MetadataFromPersistence", func(t *testing.T) {
45+
metadataHintFromPersistence := newMetadataHint(bookkeeper)
46+
assert.True(t, metadataHintFromPersistence.metadataEverUsedFor("ns1"))
47+
assert.True(t, metadataHintFromPersistence.metadataEverUsedFor("ns2"))
48+
assert.True(t, metadataHintFromPersistence.metadataEverUsedFor("ns1_pvt"))
49+
assert.True(t, metadataHintFromPersistence.metadataEverUsedFor("ns2_pvt"))
50+
assert.False(t, metadataHintFromPersistence.metadataEverUsedFor("ns3"))
51+
assert.False(t, metadataHintFromPersistence.metadataEverUsedFor("ns4"))
52+
})
53+
}
54+
55+
func TestMetadataHintOptimizationSkippingGoingToDB(t *testing.T) {
56+
bookkeepingTestEnv := bookkeeping.NewTestEnv(t)
57+
defer bookkeepingTestEnv.Cleanup()
58+
bookkeeper := bookkeepingTestEnv.TestProvider.GetDBHandle("ledger1", bookkeeping.MetadataPresenceIndicator)
59+
60+
mockVersionedDB := &mock.VersionedDB{}
61+
db, err := NewCommonStorageDB(mockVersionedDB, "testledger", newMetadataHint(bookkeeper))
62+
assert.NoError(t, err)
63+
updates := NewUpdateBatch()
64+
updates.PubUpdates.PutValAndMetadata("ns1", "key", []byte("value"), []byte("metadata"), version.NewHeight(1, 1))
65+
updates.PubUpdates.PutValAndMetadata("ns2", "key", []byte("value"), nil, version.NewHeight(1, 2))
66+
db.ApplyPrivacyAwareUpdates(updates, version.NewHeight(1, 3))
67+
68+
db.GetStateMetadata("ns1", "randomkey")
69+
assert.Equal(t, 1, mockVersionedDB.GetStateCallCount())
70+
db.GetPrivateDataMetadataByHash("ns1", "randomColl", []byte("randomKeyhash"))
71+
assert.Equal(t, 2, mockVersionedDB.GetStateCallCount())
72+
73+
db.GetStateMetadata("ns2", "randomkey")
74+
db.GetPrivateDataMetadataByHash("ns2", "randomColl", []byte("randomKeyhash"))
75+
assert.Equal(t, 2, mockVersionedDB.GetStateCallCount())
76+
77+
db.GetStateMetadata("randomeNs", "randomkey")
78+
db.GetPrivateDataMetadataByHash("randomeNs", "randomColl", []byte("randomKeyhash"))
79+
assert.Equal(t, 2, mockVersionedDB.GetStateCallCount())
80+
}

0 commit comments

Comments
 (0)