github.com/aychain/blockbook@v0.1.1-0.20181121092459-6d1fc7e07c5b/db/bulkconnect.go (about) 1 package db 2 3 import ( 4 "blockbook/bchain" 5 "time" 6 7 "github.com/golang/glog" 8 "github.com/tecbot/gorocksdb" 9 ) 10 11 // bulk connect 12 // in bulk mode the data are cached and stored to db in batches 13 // it speeds up the import in two ways: 14 // 1) balances and txAddresses are modified several times during the import, there is a chance that the modifications are done before write to DB 15 // 2) rocksdb seems to handle better fewer larger batches than continuous stream of smaller batches 16 17 type bulkAddresses struct { 18 bi BlockInfo 19 addresses map[string][]outpoint 20 } 21 22 // BulkConnect is used to connect blocks in bulk, faster but if interrupted inconsistent way 23 type BulkConnect struct { 24 d *RocksDB 25 isUTXO bool 26 bulkAddresses []bulkAddresses 27 bulkAddressesCount int 28 txAddressesMap map[string]*TxAddresses 29 balances map[string]*AddrBalance 30 height uint32 31 } 32 33 const ( 34 maxBulkAddresses = 200000 35 maxBulkTxAddresses = 500000 36 partialStoreAddresses = maxBulkTxAddresses / 10 37 maxBulkBalances = 800000 38 partialStoreBalances = maxBulkBalances / 10 39 ) 40 41 // InitBulkConnect initializes bulk connect and switches DB to inconsistent state 42 func (d *RocksDB) InitBulkConnect() (*BulkConnect, error) { 43 bc := &BulkConnect{ 44 d: d, 45 isUTXO: d.chainParser.IsUTXOChain(), 46 txAddressesMap: make(map[string]*TxAddresses), 47 balances: make(map[string]*AddrBalance), 48 } 49 if err := d.SetInconsistentState(true); err != nil { 50 return nil, err 51 } 52 glog.Info("rocksdb: bulk connect init, db set to inconsistent state") 53 return bc, nil 54 } 55 56 func (b *BulkConnect) storeTxAddresses(wb *gorocksdb.WriteBatch, all bool) (int, int, error) { 57 var txm map[string]*TxAddresses 58 var sp int 59 if all { 60 txm = b.txAddressesMap 61 b.txAddressesMap = make(map[string]*TxAddresses) 62 } else { 63 txm = make(map[string]*TxAddresses) 64 for k, a := range b.txAddressesMap { 65 // store all completely spent transactions, they will not be modified again 66 r := true 67 for _, o := range a.Outputs { 68 if o.Spent == false { 69 r = false 70 break 71 } 72 } 73 if r { 74 txm[k] = a 75 delete(b.txAddressesMap, k) 76 } 77 } 78 sp = len(txm) 79 // store some other random transactions if necessary 80 if len(txm) < partialStoreAddresses { 81 for k, a := range b.txAddressesMap { 82 txm[k] = a 83 delete(b.txAddressesMap, k) 84 if len(txm) >= partialStoreAddresses { 85 break 86 } 87 } 88 } 89 } 90 if err := b.d.storeTxAddresses(wb, txm); err != nil { 91 return 0, 0, err 92 } 93 return len(txm), sp, nil 94 } 95 96 func (b *BulkConnect) parallelStoreTxAddresses(c chan error, all bool) { 97 defer close(c) 98 start := time.Now() 99 wb := gorocksdb.NewWriteBatch() 100 defer wb.Destroy() 101 count, sp, err := b.storeTxAddresses(wb, all) 102 if err != nil { 103 c <- err 104 return 105 } 106 if err := b.d.db.Write(b.d.wo, wb); err != nil { 107 c <- err 108 return 109 } 110 glog.Info("rocksdb: height ", b.height, ", stored ", count, " (", sp, " spent) txAddresses, ", len(b.txAddressesMap), " remaining, done in ", time.Since(start)) 111 c <- nil 112 } 113 114 func (b *BulkConnect) storeBalances(wb *gorocksdb.WriteBatch, all bool) (int, error) { 115 var bal map[string]*AddrBalance 116 if all { 117 bal = b.balances 118 b.balances = make(map[string]*AddrBalance) 119 } else { 120 bal = make(map[string]*AddrBalance) 121 // store some random balances 122 for k, a := range b.balances { 123 bal[k] = a 124 delete(b.balances, k) 125 if len(bal) >= partialStoreBalances { 126 break 127 } 128 } 129 } 130 if err := b.d.storeBalances(wb, bal); err != nil { 131 return 0, err 132 } 133 return len(bal), nil 134 } 135 136 func (b *BulkConnect) parallelStoreBalances(c chan error, all bool) { 137 defer close(c) 138 start := time.Now() 139 wb := gorocksdb.NewWriteBatch() 140 defer wb.Destroy() 141 count, err := b.storeBalances(wb, all) 142 if err != nil { 143 c <- err 144 return 145 } 146 if err := b.d.db.Write(b.d.wo, wb); err != nil { 147 c <- err 148 return 149 } 150 glog.Info("rocksdb: height ", b.height, ", stored ", count, " balances, ", len(b.balances), " remaining, done in ", time.Since(start)) 151 c <- nil 152 } 153 154 func (b *BulkConnect) storeBulkAddresses(wb *gorocksdb.WriteBatch) error { 155 for _, ba := range b.bulkAddresses { 156 if err := b.d.storeAddresses(wb, ba.bi.Height, ba.addresses); err != nil { 157 return err 158 } 159 if err := b.d.writeHeight(wb, ba.bi.Height, &ba.bi, opInsert); err != nil { 160 return err 161 } 162 } 163 b.bulkAddressesCount = 0 164 b.bulkAddresses = b.bulkAddresses[:0] 165 return nil 166 } 167 168 // ConnectBlock connects block in bulk mode 169 func (b *BulkConnect) ConnectBlock(block *bchain.Block, storeBlockTxs bool) error { 170 b.height = block.Height 171 if !b.isUTXO { 172 return b.d.ConnectBlock(block) 173 } 174 addresses := make(map[string][]outpoint) 175 if err := b.d.processAddressesUTXO(block, addresses, b.txAddressesMap, b.balances); err != nil { 176 return err 177 } 178 var storeAddressesChan, storeBalancesChan chan error 179 var sa bool 180 if len(b.txAddressesMap) > maxBulkTxAddresses || len(b.balances) > maxBulkBalances { 181 sa = true 182 if len(b.txAddressesMap)+partialStoreAddresses > maxBulkTxAddresses { 183 storeAddressesChan = make(chan error) 184 go b.parallelStoreTxAddresses(storeAddressesChan, false) 185 } 186 if len(b.balances)+partialStoreBalances > maxBulkBalances { 187 storeBalancesChan = make(chan error) 188 go b.parallelStoreBalances(storeBalancesChan, false) 189 } 190 } 191 b.bulkAddresses = append(b.bulkAddresses, bulkAddresses{ 192 bi: BlockInfo{ 193 Hash: block.Hash, 194 Time: block.Time, 195 Txs: uint32(len(block.Txs)), 196 Size: uint32(block.Size), 197 Height: block.Height, 198 }, 199 addresses: addresses, 200 }) 201 b.bulkAddressesCount += len(addresses) 202 // open WriteBatch only if going to write 203 if sa || b.bulkAddressesCount > maxBulkAddresses || storeBlockTxs { 204 start := time.Now() 205 wb := gorocksdb.NewWriteBatch() 206 defer wb.Destroy() 207 bac := b.bulkAddressesCount 208 if sa || b.bulkAddressesCount > maxBulkAddresses { 209 if err := b.storeBulkAddresses(wb); err != nil { 210 return err 211 } 212 } 213 if storeBlockTxs { 214 if err := b.d.storeAndCleanupBlockTxs(wb, block); err != nil { 215 return err 216 } 217 } 218 if err := b.d.db.Write(b.d.wo, wb); err != nil { 219 return err 220 } 221 if bac > b.bulkAddressesCount { 222 glog.Info("rocksdb: height ", b.height, ", stored ", bac, " addresses, done in ", time.Since(start)) 223 } 224 } 225 if storeAddressesChan != nil { 226 if err := <-storeAddressesChan; err != nil { 227 return err 228 } 229 } 230 if storeBalancesChan != nil { 231 if err := <-storeBalancesChan; err != nil { 232 return err 233 } 234 } 235 return nil 236 } 237 238 // Close flushes the cached data and switches DB from inconsistent state open 239 // after Close, the BulkConnect cannot be used 240 func (b *BulkConnect) Close() error { 241 glog.Info("rocksdb: bulk connect closing") 242 start := time.Now() 243 storeAddressesChan := make(chan error) 244 go b.parallelStoreTxAddresses(storeAddressesChan, true) 245 storeBalancesChan := make(chan error) 246 go b.parallelStoreBalances(storeBalancesChan, true) 247 wb := gorocksdb.NewWriteBatch() 248 defer wb.Destroy() 249 bac := b.bulkAddressesCount 250 if err := b.storeBulkAddresses(wb); err != nil { 251 return err 252 } 253 if err := b.d.db.Write(b.d.wo, wb); err != nil { 254 return err 255 } 256 glog.Info("rocksdb: height ", b.height, ", stored ", bac, " addresses, done in ", time.Since(start)) 257 if err := <-storeAddressesChan; err != nil { 258 return err 259 } 260 if err := <-storeBalancesChan; err != nil { 261 return err 262 } 263 if err := b.d.SetInconsistentState(false); err != nil { 264 return err 265 } 266 glog.Info("rocksdb: bulk connect closed, db set to open state") 267 b.d = nil 268 return nil 269 }