Skip to content

Commit 0df6a8d

Browse files
committed
Disable WAL for block storage DB
(Rocks) DB WAL adds overheads while using the DB for saving checkpoints for block storage. Avoiding writing to WAL translates the write workload (appending blocks to the blockfile) into a sequential disk writes. This commit changes the way checkpoints are saved - checkpoints are written to DB as before, however since WAL is disabled, the checkpoint stays in-memory only. The in-memory checkpoint is flushed explicitly to disk (via DB flush) at the time of new block file creation. The in-memory checkpoint would implicitly also be flushed to memory at the time of DB shutdown. However, if a crash takes place, the checkpoint available in the DB would be behind the actual block file status. In order to handle this crash scenario, this commit also adds code to perform a scan of the block file beyond the last saved checkpoint and update the checkpoint. Change-Id: Ie88646b225abaa50b595833f5e7ed8d4facae920 Signed-off-by: manish <[email protected]>
1 parent 9ec4873 commit 0df6a8d

File tree

7 files changed

+219
-89
lines changed

7 files changed

+219
-89
lines changed

core/ledgernext/blkstorage/fsblkstorage/blockfile_mgr.go

+106-41
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,12 @@ func newBlockfileMgr(conf *Conf) *blockfileMgr {
6464
}
6565
if cpInfo == nil {
6666
cpInfo = &checkpointInfo{latestFileChunkSuffixNum: 0, latestFileChunksize: 0}
67-
err = mgr.saveCurrentInfo(cpInfo)
67+
err = mgr.saveCurrentInfo(cpInfo, true)
6868
if err != nil {
6969
panic(fmt.Sprintf("Could not save next block file info to db: %s", err))
7070
}
7171
}
72+
updateCPInfo(conf, cpInfo)
7273
currentFileWriter, err := newBlockfileWriter(deriveBlockfilePath(rootDir, cpInfo.latestFileChunkSuffixNum))
7374
if err != nil {
7475
panic(fmt.Sprintf("Could not open writer to current file: %s", err))
@@ -81,9 +82,12 @@ func newBlockfileMgr(conf *Conf) *blockfileMgr {
8182
mgr.index = newBlockIndex(db)
8283
mgr.cpInfo = cpInfo
8384
mgr.currentFileWriter = currentFileWriter
84-
8585
// init BlockchainInfo
86-
bcInfo := &protos.BlockchainInfo{Height: 0, CurrentBlockHash: nil, PreviousBlockHash: nil}
86+
bcInfo := &protos.BlockchainInfo{
87+
Height: 0,
88+
CurrentBlockHash: nil,
89+
PreviousBlockHash: nil}
90+
8791
if cpInfo.lastBlockNumber > 0 {
8892
lastBlock, err := mgr.retrieveSerBlockByNumber(cpInfo.lastBlockNumber)
8993
if err != nil {
@@ -104,11 +108,37 @@ func newBlockfileMgr(conf *Conf) *blockfileMgr {
104108
}
105109

106110
func initDB(conf *Conf) *db.DB {
107-
dbInst := db.CreateDB(&db.Conf{DBPath: conf.dbPath, CFNames: []string{blockIndexCF}})
111+
dbInst := db.CreateDB(&db.Conf{
112+
DBPath: conf.dbPath,
113+
CFNames: []string{blockIndexCF},
114+
DisableWAL: true})
115+
108116
dbInst.Open()
109117
return dbInst
110118
}
111119

120+
func updateCPInfo(conf *Conf, cpInfo *checkpointInfo) {
121+
logger.Debugf("Starting checkpoint=%s", cpInfo)
122+
rootDir := conf.blockfilesDir
123+
filePath := deriveBlockfilePath(rootDir, cpInfo.latestFileChunkSuffixNum)
124+
exists, size, err := util.FileExists(filePath)
125+
if err != nil {
126+
panic(fmt.Sprintf("Error in checking whether file [%s] exists: %s", filePath, err))
127+
}
128+
logger.Debugf("status of file [%s]: exists=[%t], size=[%d]", filePath, exists, size)
129+
if !exists || int(size) == cpInfo.latestFileChunksize {
130+
// check point info is in sync with the file on disk
131+
return
132+
}
133+
endOffsetLastBlock, numBlocks, err := scanForLastCompleteBlock(filePath, int64(cpInfo.latestFileChunksize))
134+
if err != nil {
135+
panic(fmt.Sprintf("Could not open current file for detecting last block in the file: %s", err))
136+
}
137+
cpInfo.lastBlockNumber += uint64(numBlocks)
138+
cpInfo.latestFileChunksize = int(endOffsetLastBlock)
139+
logger.Debugf("Checkpoint after updates by scanning the last file segment:%s", cpInfo)
140+
}
141+
112142
func deriveBlockfilePath(rootDir string, suffixNum int) string {
113143
return rootDir + "/" + blockfilePrefix + fmt.Sprintf("%06d", suffixNum)
114144
}
@@ -123,13 +153,18 @@ func (mgr *blockfileMgr) close() {
123153
}
124154

125155
func (mgr *blockfileMgr) moveToNextFile() {
126-
nextFileInfo := &checkpointInfo{latestFileChunkSuffixNum: mgr.cpInfo.latestFileChunkSuffixNum + 1, latestFileChunksize: 0}
127-
nextFileWriter, err := newBlockfileWriter(deriveBlockfilePath(mgr.rootDir, nextFileInfo.latestFileChunkSuffixNum))
156+
nextFileInfo := &checkpointInfo{
157+
latestFileChunkSuffixNum: mgr.cpInfo.latestFileChunkSuffixNum + 1,
158+
latestFileChunksize: 0}
159+
160+
nextFileWriter, err := newBlockfileWriter(
161+
deriveBlockfilePath(mgr.rootDir, nextFileInfo.latestFileChunkSuffixNum))
162+
128163
if err != nil {
129164
panic(fmt.Sprintf("Could not open writer to next file: %s", err))
130165
}
131166
mgr.currentFileWriter.close()
132-
err = mgr.saveCurrentInfo(nextFileInfo)
167+
err = mgr.saveCurrentInfo(nextFileInfo, true)
133168
if err != nil {
134169
panic(fmt.Sprintf("Could not save next block file info to db: %s", err))
135170
}
@@ -145,49 +180,44 @@ func (mgr *blockfileMgr) addBlock(block *protos.Block2) error {
145180
blockBytes := serBlock.GetBytes()
146181
blockHash := serBlock.ComputeHash()
147182
txOffsets, err := serBlock.GetTxOffsets()
183+
currentOffset := mgr.cpInfo.latestFileChunksize
148184
if err != nil {
149185
return fmt.Errorf("Error while serializing block: %s", err)
150186
}
151-
currentOffset := mgr.cpInfo.latestFileChunksize
152-
length := len(blockBytes)
153-
encodedLen := proto.EncodeVarint(uint64(length))
154-
totalLen := length + len(encodedLen)
155-
if currentOffset+totalLen > mgr.conf.maxBlockfileSize {
187+
blockBytesLen := len(blockBytes)
188+
blockBytesEncodedLen := proto.EncodeVarint(uint64(blockBytesLen))
189+
totalBytesToAppend := blockBytesLen + len(blockBytesEncodedLen)
190+
191+
if currentOffset+totalBytesToAppend > mgr.conf.maxBlockfileSize {
156192
mgr.moveToNextFile()
157193
currentOffset = 0
158194
}
159-
err = mgr.currentFileWriter.append(encodedLen)
160-
if err != nil {
161-
err1 := mgr.currentFileWriter.truncateFile(mgr.cpInfo.latestFileChunksize)
162-
if err1 != nil {
163-
panic(fmt.Sprintf("Could not truncate current file to known size after an error while appending a block: %s", err))
164-
}
165-
return fmt.Errorf("Error while appending block to file: %s", err)
195+
err = mgr.currentFileWriter.append(blockBytesEncodedLen, false)
196+
if err == nil {
197+
err = mgr.currentFileWriter.append(blockBytes, true)
166198
}
167-
168-
err = mgr.currentFileWriter.append(blockBytes)
169199
if err != nil {
170-
err1 := mgr.currentFileWriter.truncateFile(mgr.cpInfo.latestFileChunksize)
171-
if err1 != nil {
172-
panic(fmt.Sprintf("Could not truncate current file to known size after an error while appending a block: %s", err))
200+
truncateErr := mgr.currentFileWriter.truncateFile(mgr.cpInfo.latestFileChunksize)
201+
if truncateErr != nil {
202+
panic(fmt.Sprintf("Could not truncate current file to known size after an error during block append: %s", err))
173203
}
174204
return fmt.Errorf("Error while appending block to file: %s", err)
175205
}
176206

177-
mgr.cpInfo.latestFileChunksize += totalLen
207+
mgr.cpInfo.latestFileChunksize += totalBytesToAppend
178208
mgr.cpInfo.lastBlockNumber++
179-
err = mgr.saveCurrentInfo(mgr.cpInfo)
209+
err = mgr.saveCurrentInfo(mgr.cpInfo, false)
180210
if err != nil {
181-
mgr.cpInfo.latestFileChunksize -= totalLen
182-
err1 := mgr.currentFileWriter.truncateFile(mgr.cpInfo.latestFileChunksize)
183-
if err1 != nil {
184-
panic(fmt.Sprintf("Could not truncate current file to known size after an error while appending a block: %s", err))
211+
mgr.cpInfo.latestFileChunksize -= totalBytesToAppend
212+
truncateErr := mgr.currentFileWriter.truncateFile(mgr.cpInfo.latestFileChunksize)
213+
if truncateErr != nil {
214+
panic(fmt.Sprintf("Error in truncating current file to known size after an error in saving checkpoint info: %s", err))
185215
}
186216
return fmt.Errorf("Error while saving current file info to db: %s", err)
187217
}
188218
blockFLP := &fileLocPointer{fileSuffixNum: mgr.cpInfo.latestFileChunkSuffixNum}
189219
blockFLP.offset = currentOffset
190-
mgr.index.indexBlock(mgr.cpInfo.lastBlockNumber, blockHash, blockFLP, length, len(encodedLen), txOffsets)
220+
mgr.index.indexBlock(mgr.cpInfo.lastBlockNumber, blockHash, blockFLP, blockBytesLen, len(blockBytesEncodedLen), txOffsets)
191221
mgr.updateBlockchainInfo(blockHash, block)
192222
return nil
193223
}
@@ -198,7 +228,11 @@ func (mgr *blockfileMgr) getBlockchainInfo() *protos.BlockchainInfo {
198228

199229
func (mgr *blockfileMgr) updateBlockchainInfo(latestBlockHash []byte, latestBlock *protos.Block2) {
200230
currentBCInfo := mgr.getBlockchainInfo()
201-
newBCInfo := &protos.BlockchainInfo{Height: currentBCInfo.Height + 1, CurrentBlockHash: latestBlockHash, PreviousBlockHash: latestBlock.PreviousBlockHash}
231+
newBCInfo := &protos.BlockchainInfo{
232+
Height: currentBCInfo.Height + 1,
233+
CurrentBlockHash: latestBlockHash,
234+
PreviousBlockHash: latestBlock.PreviousBlockHash}
235+
202236
mgr.bcInfo.Store(newBCInfo)
203237
}
204238

@@ -315,33 +349,59 @@ func (mgr *blockfileMgr) fetchRawBytes(lp *fileLocPointer) ([]byte, error) {
315349
}
316350

317351
func (mgr *blockfileMgr) loadCurrentInfo() (*checkpointInfo, error) {
318-
b, err := mgr.db.Get(mgr.defaultCF, blkMgrInfoKey)
319-
if err != nil {
320-
return nil, err
321-
}
322-
if b == nil {
352+
var b []byte
353+
var err error
354+
if b, err = mgr.db.Get(mgr.defaultCF, blkMgrInfoKey); b == nil || err != nil {
323355
return nil, err
324356
}
325357
i := &checkpointInfo{}
326358
if err = i.unmarshal(b); err != nil {
327359
return nil, err
328360
}
361+
logger.Debugf("loaded checkpointInfo:%s", i)
329362
return i, nil
330363
}
331364

332-
func (mgr *blockfileMgr) saveCurrentInfo(i *checkpointInfo) error {
365+
func (mgr *blockfileMgr) saveCurrentInfo(i *checkpointInfo, flush bool) error {
333366
b, err := i.marshal()
334367
if err != nil {
335368
return err
336369
}
337-
err = mgr.db.Put(mgr.defaultCF, blkMgrInfoKey, b)
338-
if err != nil {
370+
if err = mgr.db.Put(mgr.defaultCF, blkMgrInfoKey, b); err != nil {
339371
return err
340372
}
373+
if flush {
374+
if err = mgr.db.Flush(true); err != nil {
375+
return err
376+
}
377+
logger.Debugf("saved checkpointInfo:%s", i)
378+
}
341379
return nil
342380
}
343381

344-
// blkMgrInfo
382+
func scanForLastCompleteBlock(filePath string, startingOffset int64) (int64, int, error) {
383+
logger.Debugf("scanForLastCompleteBlock(): filePath=[%s], startingOffset=[%d]", filePath, startingOffset)
384+
numBlocks := 0
385+
blockStream, err := newBlockStream(filePath, startingOffset)
386+
if err != nil {
387+
return 0, 0, err
388+
}
389+
defer blockStream.close()
390+
for {
391+
blockBytes, err := blockStream.nextBlockBytes()
392+
if blockBytes == nil || err != nil {
393+
logger.Debugf(`scanForLastCompleteBlock(): error=[%s].
394+
This may happen if a crash has happened during block appending.
395+
Returning current offset as a last complete block's end offset`, err)
396+
break
397+
}
398+
numBlocks++
399+
}
400+
logger.Debugf("scanForLastCompleteBlock(): last complete block ends at offset=[%d]", blockStream.currentFileOffset)
401+
return blockStream.currentFileOffset, numBlocks, err
402+
}
403+
404+
// checkpointInfo
345405
type checkpointInfo struct {
346406
latestFileChunkSuffixNum int
347407
latestFileChunksize int
@@ -385,3 +445,8 @@ func (i *checkpointInfo) unmarshal(b []byte) error {
385445

386446
return nil
387447
}
448+
449+
func (i *checkpointInfo) String() string {
450+
return fmt.Sprintf("latestFileChunkSuffixNum=[%d], latestFileChunksize=[%d], lastBlockNumber=[%d]",
451+
i.latestFileChunkSuffixNum, i.latestFileChunksize, i.lastBlockNumber)
452+
}

core/ledgernext/blkstorage/fsblkstorage/blockfile_mgr_test.go

+63-3
Original file line numberDiff line numberDiff line change
@@ -36,23 +36,83 @@ func TestBlockfileMgrBlockReadWrite(t *testing.T) {
3636
blkfileMgrWrapper.testGetBlockByNumber(blocks, 1)
3737
}
3838

39+
func TestBlockfileMgrCrashDuringWriting(t *testing.T) {
40+
testBlockfileMgrCrashDuringWriting(t, 10, 2, 1000, 10)
41+
testBlockfileMgrCrashDuringWriting(t, 10, 2, 1000, 1)
42+
testBlockfileMgrCrashDuringWriting(t, 10, 2, 1000, 0)
43+
testBlockfileMgrCrashDuringWriting(t, 0, 0, 1000, 10)
44+
}
45+
46+
func testBlockfileMgrCrashDuringWriting(t *testing.T, numBlocksBeforeCheckpoint int,
47+
numBlocksAfterCheckpoint int, numLastBlockBytes int, numPartialBytesToWrite int) {
48+
env := newTestEnv(t)
49+
defer env.Cleanup()
50+
blkfileMgrWrapper := newTestBlockfileWrapper(t, env)
51+
blocksBeforeCP := testutil.ConstructTestBlocks(t, numBlocksBeforeCheckpoint)
52+
blkfileMgrWrapper.addBlocks(blocksBeforeCP)
53+
currentCPInfo := blkfileMgrWrapper.blockfileMgr.cpInfo
54+
cpInfo1 := &checkpointInfo{
55+
currentCPInfo.latestFileChunkSuffixNum,
56+
currentCPInfo.latestFileChunksize,
57+
currentCPInfo.lastBlockNumber}
58+
59+
blocksAfterCP := testutil.ConstructTestBlocks(t, numBlocksAfterCheckpoint)
60+
blkfileMgrWrapper.addBlocks(blocksAfterCP)
61+
cpInfo2 := blkfileMgrWrapper.blockfileMgr.cpInfo
62+
63+
// simulate a crash scenario
64+
lastBlockBytes := []byte{}
65+
encodedLen := proto.EncodeVarint(uint64(numLastBlockBytes))
66+
randomBytes := testutil.ConstructRandomBytes(t, numLastBlockBytes)
67+
lastBlockBytes = append(lastBlockBytes, encodedLen...)
68+
lastBlockBytes = append(lastBlockBytes, randomBytes...)
69+
partialBytes := lastBlockBytes[:numPartialBytesToWrite]
70+
blkfileMgrWrapper.blockfileMgr.currentFileWriter.append(partialBytes, true)
71+
blkfileMgrWrapper.blockfileMgr.saveCurrentInfo(cpInfo1, true)
72+
blkfileMgrWrapper.close()
73+
74+
// simulate a start after a crash
75+
blkfileMgrWrapper = newTestBlockfileWrapper(t, env)
76+
defer blkfileMgrWrapper.close()
77+
cpInfo3 := blkfileMgrWrapper.blockfileMgr.cpInfo
78+
testutil.AssertEquals(t, cpInfo3, cpInfo2)
79+
80+
// add fresh blocks after restart
81+
numBlocksAfterRestart := 2
82+
blocksAfterRestart := testutil.ConstructTestBlocks(t, numBlocksAfterRestart)
83+
blkfileMgrWrapper.addBlocks(blocksAfterRestart)
84+
85+
// itrerate for all blocks
86+
allBlocks := []*protos.Block2{}
87+
allBlocks = append(allBlocks, blocksBeforeCP...)
88+
allBlocks = append(allBlocks, blocksAfterCP...)
89+
allBlocks = append(allBlocks, blocksAfterRestart...)
90+
numTotalBlocks := len(allBlocks)
91+
testBlockfileMgrBlockIterator(t, blkfileMgrWrapper.blockfileMgr, 1, numTotalBlocks, allBlocks)
92+
}
93+
3994
func TestBlockfileMgrBlockIterator(t *testing.T) {
4095
env := newTestEnv(t)
4196
defer env.Cleanup()
4297
blkfileMgrWrapper := newTestBlockfileWrapper(t, env)
4398
defer blkfileMgrWrapper.close()
4499
blocks := testutil.ConstructTestBlocks(t, 10)
45100
blkfileMgrWrapper.addBlocks(blocks)
46-
itr, err := blkfileMgrWrapper.blockfileMgr.retrieveBlocks(1, 8)
101+
testBlockfileMgrBlockIterator(t, blkfileMgrWrapper.blockfileMgr, 1, 8, blocks[0:8])
102+
}
103+
104+
func testBlockfileMgrBlockIterator(t *testing.T, blockfileMgr *blockfileMgr,
105+
firstBlockNum int, lastBlockNum int, expectedBlocks []*protos.Block2) {
106+
itr, err := blockfileMgr.retrieveBlocks(uint64(firstBlockNum), uint64(lastBlockNum))
47107
defer itr.Close()
48108
testutil.AssertNoError(t, err, "Error while getting blocks iterator")
49109
numBlocksItrated := 0
50110
for ; itr.Next(); numBlocksItrated++ {
51111
block, err := itr.Get()
52112
testutil.AssertNoError(t, err, fmt.Sprintf("Error while getting block number [%d] from iterator", numBlocksItrated))
53-
testutil.AssertEquals(t, block.(*BlockHolder).GetBlock(), blocks[numBlocksItrated])
113+
testutil.AssertEquals(t, block.(*BlockHolder).GetBlock(), expectedBlocks[numBlocksItrated])
54114
}
55-
testutil.AssertEquals(t, numBlocksItrated, 8)
115+
testutil.AssertEquals(t, numBlocksItrated, lastBlockNum-firstBlockNum+1)
56116
}
57117

58118
func TestBlockfileMgrBlockchainInfo(t *testing.T) {

core/ledgernext/blkstorage/fsblkstorage/blockfile_rw.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@ func (w *blockfileWriter) truncateFile(targetSize int) error {
4747
return nil
4848
}
4949

50-
func (w *blockfileWriter) append(b []byte) error {
50+
func (w *blockfileWriter) append(b []byte, sync bool) error {
5151
_, err := w.file.Write(b)
5252
if err != nil {
5353
return err
5454
}
55-
w.file.Sync()
55+
if sync {
56+
return w.file.Sync()
57+
}
5658
return nil
5759
}
5860

@@ -97,8 +99,9 @@ func (r *blockfileReader) close() error {
9799
}
98100

99101
type blockStream struct {
100-
file *os.File
101-
reader *bufio.Reader
102+
file *os.File
103+
reader *bufio.Reader
104+
currentFileOffset int64
102105
}
103106

104107
func newBlockStream(filePath string, offset int64) (*blockStream, error) {
@@ -115,7 +118,7 @@ func newBlockStream(filePath string, offset int64) (*blockStream, error) {
115118
if newPosition != offset {
116119
panic(fmt.Sprintf("Could not seek file [%s] to given offset [%d]. New position = [%d]", filePath, offset, newPosition))
117120
}
118-
s := &blockStream{file, bufio.NewReader(file)}
121+
s := &blockStream{file, bufio.NewReader(file), offset}
119122
return s, nil
120123
}
121124

@@ -133,6 +136,7 @@ func (s *blockStream) nextBlockBytes() ([]byte, error) {
133136
if _, err = io.ReadAtLeast(s.reader, blockBytes, int(len)); err != nil {
134137
return nil, err
135138
}
139+
s.currentFileOffset += int64(n) + int64(len)
136140
return blockBytes, nil
137141
}
138142

0 commit comments

Comments
 (0)