-
Notifications
You must be signed in to change notification settings - Fork 8.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fsync and do not generate empty files in snapshots #1345
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ package confighistory | |
|
||
import ( | ||
"fmt" | ||
"path" | ||
"path/filepath" | ||
|
||
"github.com/golang/protobuf/proto" | ||
"github.com/hyperledger/fabric-protos-go/common" | ||
|
@@ -181,23 +181,27 @@ func (r *Retriever) CollectionConfigAt(blockNum uint64, chaincodeName string) (* | |
// extra bytes. Further, the collection config namespace is not expected to have | ||
// millions of entries. | ||
func (r *Retriever) ExportConfigHistory(dir string, newHashFunc snapshot.NewHashFunc) (map[string][]byte, error) { | ||
dataFileWriter, err := snapshot.CreateFile(path.Join(dir, snapshotDataFileName), snapshotFileFormat, newHashFunc) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer dataFileWriter.Close() | ||
|
||
nsItr := r.dbHandle.getNamespaceIterator(collectionConfigNamespace) | ||
if err := nsItr.Error(); err != nil { | ||
return nil, errors.Wrap(err, "internal leveldb error while obtaining db iterator") | ||
|
||
} | ||
defer nsItr.Release() | ||
|
||
var numCollectionConfigs uint64 = 0 | ||
var dataFileWriter *snapshot.FileWriter | ||
var err error | ||
for nsItr.Next() { | ||
if err := nsItr.Error(); err != nil { | ||
return nil, errors.Wrap(err, "internal leveldb error while iterating for collection config history") | ||
} | ||
if numCollectionConfigs == 0 { // first iteration, create the data file | ||
dataFileWriter, err = snapshot.CreateFile(filepath.Join(dir, snapshotDataFileName), snapshotFileFormat, newHashFunc) | ||
Comment on lines
+198
to
+199
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarification Question
Comment
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, that how it would work.
It would be human readable JSON, so an admin can match the hashes manually. IMO, it much clearer to have a set of files and match the names and hashes in the metadata. If a channel does not use private data and we keep exporting hashes files and collection config files, that may cause more confusion.
When it comes to exporting the stuff, we should not apply the same parameter as we do for fully in-memory data. The in-memory data is not seen by anyone. In the case of exported stuff, I prefer the neatness of the exported data dictating the requirement than the other way around. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More on human readable JSON, this works until we add compression. We have to think more for allowing for compression. In any case, there will be a final hash that would represent the snapshot and that hash along with the basic info about the snapshot would be present in a JSON file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What I am saying is that when we have the same set of files all the time, it is easy for the consumer, i.e., the new peer processing these snapshot files. When the set of files can be different per snapshot, it adds unnecessary complication in the code at the consumer side. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As I said before, to me neatness of exported data should dictate the code than the other way. Also, I am not sure what code complication are you referring to. During import, if data files are not present for a particular component, it simply returns. That's rather far more simpler to reason about. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see. I was assuming that the kvledger would ask each component for the required file and only if it is present, kvledger would call the |
||
if err != nil { | ||
return nil, err | ||
} | ||
defer dataFileWriter.Close() | ||
} | ||
if err := dataFileWriter.EncodeBytes(nsItr.Key()); err != nil { | ||
return nil, err | ||
} | ||
|
@@ -206,12 +210,16 @@ func (r *Retriever) ExportConfigHistory(dir string, newHashFunc snapshot.NewHash | |
} | ||
numCollectionConfigs++ | ||
} | ||
|
||
if dataFileWriter == nil { | ||
return nil, nil | ||
} | ||
|
||
dataHash, err := dataFileWriter.Done() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
metadataFileWriter, err := snapshot.CreateFile(path.Join(dir, snapshotMetadataFileName), snapshotFileFormat, newHashFunc) | ||
metadataFileWriter, err := snapshot.CreateFile(filepath.Join(dir, snapshotMetadataFileName), snapshotFileFormat, newHashFunc) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,7 +8,7 @@ package privacyenabledstate | |
|
||
import ( | ||
"hash" | ||
"path" | ||
"path/filepath" | ||
|
||
"github.com/hyperledger/fabric/common/ledger/snapshot" | ||
"github.com/hyperledger/fabric/core/ledger/kvledger/txmgmt/statedb" | ||
|
@@ -33,28 +33,8 @@ func (s *DB) ExportPubStateAndPvtStateHashes(dir string, newHashFunc snapshot.Ne | |
} | ||
defer itr.Close() | ||
|
||
pubStateWriter, err := newSnapshotWriter( | ||
path.Join(dir, pubStateDataFileName), | ||
path.Join(dir, pubStateMetadataFileName), | ||
dbValueFormat, | ||
newHashFunc, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer pubStateWriter.close() | ||
|
||
pvtStateHashesWriter, err := newSnapshotWriter( | ||
path.Join(dir, pvtStateHashesFileName), | ||
path.Join(dir, pvtStateHashesMetadataFileName), | ||
dbValueFormat, | ||
newHashFunc, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer pvtStateHashesWriter.close() | ||
|
||
var pubStateWriter *snapshotWriter | ||
var pvtStateHashesWriter *snapshotWriter | ||
for { | ||
compositeKey, dbValue, err := itr.Next() | ||
if err != nil { | ||
|
@@ -65,30 +45,61 @@ func (s *DB) ExportPubStateAndPvtStateHashes(dir string, newHashFunc snapshot.Ne | |
} | ||
switch { | ||
case isHashedDataNs(compositeKey.Namespace): | ||
if pvtStateHashesWriter == nil { // encountered first time the pvt state hash element | ||
pvtStateHashesWriter, err = newSnapshotWriter( | ||
filepath.Join(dir, pvtStateHashesFileName), | ||
filepath.Join(dir, pvtStateHashesMetadataFileName), | ||
dbValueFormat, | ||
newHashFunc, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer pvtStateHashesWriter.close() | ||
} | ||
if err := pvtStateHashesWriter.addData(compositeKey, dbValue); err != nil { | ||
return nil, err | ||
} | ||
default: | ||
if pubStateWriter == nil { // encountered first time the pub state element | ||
pubStateWriter, err = newSnapshotWriter( | ||
Comment on lines
+64
to
+65
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here also, the public state would be non-empty even at block height 1 as we store the genesis block. Earlier code structure -- file creation, dumping data to file, writing metadata, and closing it looked neater. If no data is dumped, it was reflected in the metadata. If we still decide not to have empty files, having There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same response as in the comment. Regarding |
||
filepath.Join(dir, pubStateDataFileName), | ||
filepath.Join(dir, pubStateMetadataFileName), | ||
dbValueFormat, | ||
newHashFunc, | ||
) | ||
if err != nil { | ||
return nil, err | ||
} | ||
defer pubStateWriter.close() | ||
} | ||
if err := pubStateWriter.addData(compositeKey, dbValue); err != nil { | ||
return nil, err | ||
} | ||
} | ||
} | ||
pubStateDataHash, pubStateMetadataHash, err := pubStateWriter.done() | ||
if err != nil { | ||
return nil, err | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit. Could return early here too if both writers are nil (just for the consistency with other export APIs). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll leave this as is. You typically return early to shorten an |
||
snapshotFilesInfo := map[string][]byte{} | ||
|
||
if pubStateWriter != nil { | ||
pubStateDataHash, pubStateMetadataHash, err := pubStateWriter.done() | ||
if err != nil { | ||
return nil, err | ||
} | ||
snapshotFilesInfo[pubStateDataFileName] = pubStateDataHash | ||
snapshotFilesInfo[pubStateMetadataFileName] = pubStateMetadataHash | ||
} | ||
pvtStateHahshesDataHash, pvtStateHashesMetadataHash, err := pvtStateHashesWriter.done() | ||
if err != nil { | ||
return nil, err | ||
|
||
if pvtStateHashesWriter != nil { | ||
pvtStateHahshesDataHash, pvtStateHashesMetadataHash, err := pvtStateHashesWriter.done() | ||
if err != nil { | ||
return nil, err | ||
} | ||
snapshotFilesInfo[pvtStateHashesFileName] = pvtStateHahshesDataHash | ||
snapshotFilesInfo[pvtStateHashesMetadataFileName] = pvtStateHashesMetadataHash | ||
} | ||
return map[string][]byte{ | ||
pubStateDataFileName: pubStateDataHash, | ||
pubStateMetadataFileName: pubStateMetadataHash, | ||
pvtStateHashesFileName: pvtStateHahshesDataHash, | ||
pvtStateHashesMetadataFileName: pvtStateHashesMetadataHash, | ||
}, | ||
nil | ||
|
||
return snapshotFilesInfo, nil | ||
} | ||
|
||
// snapshotWriter generates two files, a data file and a metadata file. The datafile contains a series of tuples <key, dbValue> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to have no TxId in the store? Even the genesis block has a txID right? 4eff624
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as possible, it's not good to drag the assumptions in the lower level code that how the consumer uses it. At this level of the code, you can open a store and invoke the
Export
function.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a useful use-case that would open an empty blockstore with no genesis block and then try to export txIDs. The same is true for the StateDB too. IMO, this disturbs the existing code flow unnecessarily. I understand your point too but I haven't got convinced.
IMO, empty files are okay as it conveys that the store is empty. As we have a difference of opinion, I leave this here.