@@ -17,6 +17,8 @@ limitations under the License.
17
17
package couchdbtxmgmt
18
18
19
19
import (
20
+ "encoding/json"
21
+ "errors"
20
22
"sync"
21
23
22
24
"github.com/golang/protobuf/proto"
@@ -48,6 +50,15 @@ type updateSet struct {
48
50
m map [string ]* versionedValue
49
51
}
50
52
53
+ // Savepoint docid (key) for couchdb
54
+ const savepointDocID = "statedb_savepoint"
55
+
56
+ // Savepoint data for couchdb
57
+ type couchSavepointData struct {
58
+ BlockNum uint64 `json:"BlockNum"`
59
+ UpdateSeq string `json:"UpdateSeq"`
60
+ }
61
+
51
62
func newUpdateSet () * updateSet {
52
63
return & updateSet {make (map [string ]* versionedValue )}
53
64
}
@@ -72,6 +83,7 @@ type CouchDBTxMgr struct {
72
83
updateSet * updateSet
73
84
commitRWLock sync.RWMutex
74
85
couchDB * couchdb.CouchDBConnectionDef // COUCHDB new properties for CouchDB
86
+ blockNum uint64 // block number corresponding to updateSet
75
87
}
76
88
77
89
// CouchConnection provides connection info for CouchDB
@@ -119,6 +131,7 @@ func (txmgr *CouchDBTxMgr) ValidateAndPrepare(block *common.Block) (*common.Bloc
119
131
invalidTxs := []* pb.InvalidTransaction {}
120
132
var valid bool
121
133
txmgr .updateSet = newUpdateSet ()
134
+ txmgr .blockNum = block .Header .Number
122
135
logger .Debugf ("Validating a block with [%d] transactions" , len (block .Data .Data ))
123
136
for txIndex , envBytes := range block .Data .Data {
124
137
// extract actions from the envelope message
@@ -159,6 +172,7 @@ func (txmgr *CouchDBTxMgr) ValidateAndPrepare(block *common.Block) (*common.Bloc
159
172
Transaction : & pb.Transaction { /* FIXME */ }, Cause : pb .InvalidTransaction_RWConflictDuringCommit })
160
173
}
161
174
}
175
+
162
176
logger .Debugf ("===COUCHDB=== Exiting CouchDBTxMgr.ValidateAndPrepare()" )
163
177
return block , invalidTxs , nil
164
178
}
@@ -258,13 +272,87 @@ func (txmgr *CouchDBTxMgr) Commit() error {
258
272
259
273
}
260
274
275
+ // Record a savepoint
276
+ err := txmgr .recordSavepoint ()
277
+ if err != nil {
278
+ logger .Errorf ("===COUCHDB=== Error during recordSavepoint: %s\n " , err .Error ())
279
+ return err
280
+ }
281
+
261
282
logger .Debugf ("===COUCHDB=== Exiting CouchDBTxMgr.Commit()" )
262
283
return nil
263
284
}
264
285
286
+ // recordSavepoint Record a savepoint in statedb.
287
+ // Couch parallelizes writes in cluster or sharded setup and ordering is not guaranteed.
288
+ // Hence we need to fence the savepoint with sync. So ensure_full_commit is called before AND after writing savepoint document
289
+ // TODO: Optimization - merge 2nd ensure_full_commit with savepoint by using X-Couch-Full-Commit header
290
+ func (txmgr * CouchDBTxMgr ) recordSavepoint () error {
291
+ var err error
292
+ var savepointDoc couchSavepointData
293
+ // ensure full commit to flush all changes until now to disk
294
+ dbResponse , err := txmgr .couchDB .EnsureFullCommit ()
295
+ if err != nil || dbResponse .Ok != true {
296
+ logger .Errorf ("====COUCHDB==== Failed to perform full commit\n " )
297
+ return errors .New ("Failed to perform full commit" )
298
+ }
299
+
300
+ // construct savepoint document
301
+ // UpdateSeq would be useful if we want to get all db changes since a logical savepoint
302
+ dbInfo , _ , err := txmgr .couchDB .GetDatabaseInfo ()
303
+ if err != nil {
304
+ logger .Errorf ("====COUCHDB==== Failed to get DB info %s\n " , err .Error ())
305
+ return err
306
+ }
307
+ savepointDoc .BlockNum = txmgr .blockNum
308
+ savepointDoc .UpdateSeq = dbInfo .UpdateSeq
309
+
310
+ savepointDocJSON , err := json .Marshal (savepointDoc )
311
+ if err != nil {
312
+ logger .Errorf ("====COUCHDB==== Failed to create savepoint data %s\n " , err .Error ())
313
+ return err
314
+ }
315
+
316
+ // SaveDoc using couchdb client and use JSON format
317
+ _ , err = txmgr .couchDB .SaveDoc (savepointDocID , "" , savepointDocJSON , nil )
318
+ if err != nil {
319
+ logger .Errorf ("====CouchDB==== Failed to save the savepoint to DB %s\n " , err .Error ())
320
+ }
321
+
322
+ // ensure full commit to flush savepoint to disk
323
+ dbResponse , err = txmgr .couchDB .EnsureFullCommit ()
324
+ if err != nil || dbResponse .Ok != true {
325
+ logger .Errorf ("====COUCHDB==== Failed to perform full commit\n " )
326
+ return errors .New ("Failed to perform full commit" )
327
+ }
328
+ return nil
329
+ }
330
+
331
+ // GetBlockNumFromSavepoint Reads the savepoint from database and returns the corresponding block number.
332
+ // If no savepoint is found, it returns 0
333
+ func (txmgr * CouchDBTxMgr ) GetBlockNumFromSavepoint () (uint64 , error ) {
334
+ var err error
335
+ savepointJSON , _ , err := txmgr .couchDB .ReadDoc (savepointDocID )
336
+ if err != nil {
337
+ // TODO: differentiate between 404 and some other error code
338
+ logger .Errorf ("====COUCHDB==== Failed to read savepoint data %s\n " , err .Error ())
339
+ return 0 , err
340
+ }
341
+
342
+ savepointDoc := & couchSavepointData {}
343
+ err = json .Unmarshal (savepointJSON , & savepointDoc )
344
+ if err != nil {
345
+ logger .Errorf ("====COUCHDB==== Failed to read savepoint data %s\n " , err .Error ())
346
+ return 0 , err
347
+ }
348
+
349
+ return savepointDoc .BlockNum , nil
350
+ }
351
+
265
352
// Rollback implements method in interface `txmgmt.TxMgr`
266
353
func (txmgr * CouchDBTxMgr ) Rollback () {
267
354
txmgr .updateSet = nil
355
+ txmgr .blockNum = 0
268
356
}
269
357
270
358
func (txmgr * CouchDBTxMgr ) getCommitedVersion (ns string , key string ) (* version.Height , error ) {
0 commit comments