github.com/bchainhub/blockbook@v0.3.2/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 addressesMap 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 chainType bchain.ChainType 26 bulkAddresses []bulkAddresses 27 bulkAddressesCount int 28 txAddressesMap map[string]*TxAddresses 29 balances map[string]*AddrBalance 30 addressContracts map[string]*AddrContracts 31 height uint32 32 } 33 34 const ( 35 maxBulkAddresses = 80000 36 maxBulkTxAddresses = 500000 37 partialStoreAddresses = maxBulkTxAddresses / 10 38 maxBulkBalances = 700000 39 partialStoreBalances = maxBulkBalances / 10 40 maxBulkAddrContracts = 1200000 41 partialStoreAddrContracts = maxBulkAddrContracts / 10 42 ) 43 44 // InitBulkConnect initializes bulk connect and switches DB to inconsistent state 45 func (d *RocksDB) InitBulkConnect() (*BulkConnect, error) { 46 b := &BulkConnect{ 47 d: d, 48 chainType: d.chainParser.GetChainType(), 49 txAddressesMap: make(map[string]*TxAddresses), 50 balances: make(map[string]*AddrBalance), 51 addressContracts: make(map[string]*AddrContracts), 52 } 53 if err := d.SetInconsistentState(true); err != nil { 54 return nil, err 55 } 56 glog.Info("rocksdb: bulk connect init, db set to inconsistent state") 57 return b, nil 58 } 59 60 func (b *BulkConnect) storeTxAddresses(wb *gorocksdb.WriteBatch, all bool) (int, int, error) { 61 var txm map[string]*TxAddresses 62 var sp int 63 if all { 64 txm = b.txAddressesMap 65 b.txAddressesMap = make(map[string]*TxAddresses) 66 } else { 67 txm = make(map[string]*TxAddresses) 68 for k, a := range b.txAddressesMap { 69 // store all completely spent transactions, they will not be modified again 70 r := true 71 for _, o := range a.Outputs { 72 if o.Spent == false { 73 r = false 74 break 75 } 76 } 77 if r { 78 txm[k] = a 79 delete(b.txAddressesMap, k) 80 } 81 } 82 sp = len(txm) 83 // store some other random transactions if necessary 84 if len(txm) < partialStoreAddresses { 85 for k, a := range b.txAddressesMap { 86 txm[k] = a 87 delete(b.txAddressesMap, k) 88 if len(txm) >= partialStoreAddresses { 89 break 90 } 91 } 92 } 93 } 94 if err := b.d.storeTxAddresses(wb, txm); err != nil { 95 return 0, 0, err 96 } 97 return len(txm), sp, nil 98 } 99 100 func (b *BulkConnect) parallelStoreTxAddresses(c chan error, all bool) { 101 defer close(c) 102 start := time.Now() 103 wb := gorocksdb.NewWriteBatch() 104 defer wb.Destroy() 105 count, sp, err := b.storeTxAddresses(wb, all) 106 if err != nil { 107 c <- err 108 return 109 } 110 if err := b.d.db.Write(b.d.wo, wb); err != nil { 111 c <- err 112 return 113 } 114 glog.Info("rocksdb: height ", b.height, ", stored ", count, " (", sp, " spent) txAddresses, ", len(b.txAddressesMap), " remaining, done in ", time.Since(start)) 115 c <- nil 116 } 117 118 func (b *BulkConnect) storeBalances(wb *gorocksdb.WriteBatch, all bool) (int, error) { 119 var bal map[string]*AddrBalance 120 if all { 121 bal = b.balances 122 b.balances = make(map[string]*AddrBalance) 123 } else { 124 bal = make(map[string]*AddrBalance) 125 // store some random balances 126 for k, a := range b.balances { 127 bal[k] = a 128 delete(b.balances, k) 129 if len(bal) >= partialStoreBalances { 130 break 131 } 132 } 133 } 134 if err := b.d.storeBalances(wb, bal); err != nil { 135 return 0, err 136 } 137 return len(bal), nil 138 } 139 140 func (b *BulkConnect) parallelStoreBalances(c chan error, all bool) { 141 defer close(c) 142 start := time.Now() 143 wb := gorocksdb.NewWriteBatch() 144 defer wb.Destroy() 145 count, err := b.storeBalances(wb, all) 146 if err != nil { 147 c <- err 148 return 149 } 150 if err := b.d.db.Write(b.d.wo, wb); err != nil { 151 c <- err 152 return 153 } 154 glog.Info("rocksdb: height ", b.height, ", stored ", count, " balances, ", len(b.balances), " remaining, done in ", time.Since(start)) 155 c <- nil 156 } 157 158 func (b *BulkConnect) storeBulkAddresses(wb *gorocksdb.WriteBatch) error { 159 for _, ba := range b.bulkAddresses { 160 if err := b.d.storeAddresses(wb, ba.bi.Height, ba.addresses); err != nil { 161 return err 162 } 163 if err := b.d.writeHeight(wb, ba.bi.Height, &ba.bi, opInsert); err != nil { 164 return err 165 } 166 } 167 b.bulkAddressesCount = 0 168 b.bulkAddresses = b.bulkAddresses[:0] 169 return nil 170 } 171 172 func (b *BulkConnect) connectBlockBitcoinType(block *bchain.Block, storeBlockTxs bool) error { 173 addresses := make(addressesMap) 174 if err := b.d.processAddressesBitcoinType(block, addresses, b.txAddressesMap, b.balances); err != nil { 175 return err 176 } 177 var storeAddressesChan, storeBalancesChan chan error 178 var sa bool 179 if len(b.txAddressesMap) > maxBulkTxAddresses || len(b.balances) > maxBulkBalances { 180 sa = true 181 if len(b.txAddressesMap)+partialStoreAddresses > maxBulkTxAddresses { 182 storeAddressesChan = make(chan error) 183 go b.parallelStoreTxAddresses(storeAddressesChan, false) 184 } 185 if len(b.balances)+partialStoreBalances > maxBulkBalances { 186 storeBalancesChan = make(chan error) 187 go b.parallelStoreBalances(storeBalancesChan, false) 188 } 189 } 190 b.bulkAddresses = append(b.bulkAddresses, bulkAddresses{ 191 bi: BlockInfo{ 192 Hash: block.Hash, 193 Time: block.Time, 194 Txs: uint32(len(block.Txs)), 195 Size: uint32(block.Size), 196 Height: block.Height, 197 }, 198 addresses: addresses, 199 }) 200 b.bulkAddressesCount += len(addresses) 201 // open WriteBatch only if going to write 202 if sa || b.bulkAddressesCount > maxBulkAddresses || storeBlockTxs { 203 start := time.Now() 204 wb := gorocksdb.NewWriteBatch() 205 defer wb.Destroy() 206 bac := b.bulkAddressesCount 207 if sa || b.bulkAddressesCount > maxBulkAddresses { 208 if err := b.storeBulkAddresses(wb); err != nil { 209 return err 210 } 211 } 212 if storeBlockTxs { 213 if err := b.d.storeAndCleanupBlockTxs(wb, block); err != nil { 214 return err 215 } 216 } 217 if err := b.d.db.Write(b.d.wo, wb); err != nil { 218 return err 219 } 220 if bac > b.bulkAddressesCount { 221 glog.Info("rocksdb: height ", b.height, ", stored ", bac, " addresses, done in ", time.Since(start)) 222 } 223 } 224 if storeAddressesChan != nil { 225 if err := <-storeAddressesChan; err != nil { 226 return err 227 } 228 } 229 if storeBalancesChan != nil { 230 if err := <-storeBalancesChan; err != nil { 231 return err 232 } 233 } 234 return nil 235 } 236 237 func (b *BulkConnect) storeAddressContracts(wb *gorocksdb.WriteBatch, all bool) (int, error) { 238 var ac map[string]*AddrContracts 239 if all { 240 ac = b.addressContracts 241 b.addressContracts = make(map[string]*AddrContracts) 242 } else { 243 ac = make(map[string]*AddrContracts) 244 // store some random address contracts 245 for k, a := range b.addressContracts { 246 ac[k] = a 247 delete(b.addressContracts, k) 248 if len(ac) >= partialStoreAddrContracts { 249 break 250 } 251 } 252 } 253 if err := b.d.storeAddressContracts(wb, ac); err != nil { 254 return 0, err 255 } 256 return len(ac), nil 257 } 258 259 func (b *BulkConnect) parallelStoreAddressContracts(c chan error, all bool) { 260 defer close(c) 261 start := time.Now() 262 wb := gorocksdb.NewWriteBatch() 263 defer wb.Destroy() 264 count, err := b.storeAddressContracts(wb, all) 265 if err != nil { 266 c <- err 267 return 268 } 269 if err := b.d.db.Write(b.d.wo, wb); err != nil { 270 c <- err 271 return 272 } 273 glog.Info("rocksdb: height ", b.height, ", stored ", count, " addressContracts, ", len(b.addressContracts), " remaining, done in ", time.Since(start)) 274 c <- nil 275 } 276 277 func (b *BulkConnect) connectBlockEthereumType(block *bchain.Block, storeBlockTxs bool) error { 278 addresses := make(addressesMap) 279 blockTxs, err := b.d.processAddressesEthereumType(block, addresses, b.addressContracts) 280 if err != nil { 281 return err 282 } 283 var storeAddrContracts chan error 284 var sa bool 285 if len(b.addressContracts) > maxBulkAddrContracts { 286 sa = true 287 storeAddrContracts = make(chan error) 288 go b.parallelStoreAddressContracts(storeAddrContracts, false) 289 } 290 b.bulkAddresses = append(b.bulkAddresses, bulkAddresses{ 291 bi: BlockInfo{ 292 Hash: block.Hash, 293 Time: block.Time, 294 Txs: uint32(len(block.Txs)), 295 Size: uint32(block.Size), 296 Height: block.Height, 297 }, 298 addresses: addresses, 299 }) 300 b.bulkAddressesCount += len(addresses) 301 // open WriteBatch only if going to write 302 if sa || b.bulkAddressesCount > maxBulkAddresses || storeBlockTxs { 303 start := time.Now() 304 wb := gorocksdb.NewWriteBatch() 305 defer wb.Destroy() 306 bac := b.bulkAddressesCount 307 if sa || b.bulkAddressesCount > maxBulkAddresses { 308 if err := b.storeBulkAddresses(wb); err != nil { 309 return err 310 } 311 } 312 if storeBlockTxs { 313 if err := b.d.storeAndCleanupBlockTxsEthereumType(wb, block, blockTxs); err != nil { 314 return err 315 } 316 } 317 if err := b.d.db.Write(b.d.wo, wb); err != nil { 318 return err 319 } 320 if bac > b.bulkAddressesCount { 321 glog.Info("rocksdb: height ", b.height, ", stored ", bac, " addresses, done in ", time.Since(start)) 322 } 323 } 324 if storeAddrContracts != nil { 325 if err := <-storeAddrContracts; err != nil { 326 return err 327 } 328 } 329 return nil 330 } 331 332 // ConnectBlock connects block in bulk mode 333 func (b *BulkConnect) ConnectBlock(block *bchain.Block, storeBlockTxs bool) error { 334 b.height = block.Height 335 if b.chainType == bchain.ChainBitcoinType { 336 return b.connectBlockBitcoinType(block, storeBlockTxs) 337 } else if b.chainType == bchain.ChainEthereumType { 338 return b.connectBlockEthereumType(block, storeBlockTxs) 339 } 340 // for default is to connect blocks in non bulk mode 341 return b.d.ConnectBlock(block) 342 } 343 344 // Close flushes the cached data and switches DB from inconsistent state open 345 // after Close, the BulkConnect cannot be used 346 func (b *BulkConnect) Close() error { 347 glog.Info("rocksdb: bulk connect closing") 348 start := time.Now() 349 var storeTxAddressesChan, storeBalancesChan, storeAddressContractsChan chan error 350 if b.chainType == bchain.ChainBitcoinType { 351 storeTxAddressesChan = make(chan error) 352 go b.parallelStoreTxAddresses(storeTxAddressesChan, true) 353 storeBalancesChan = make(chan error) 354 go b.parallelStoreBalances(storeBalancesChan, true) 355 } else if b.chainType == bchain.ChainEthereumType { 356 storeAddressContractsChan = make(chan error) 357 go b.parallelStoreAddressContracts(storeAddressContractsChan, true) 358 } 359 wb := gorocksdb.NewWriteBatch() 360 defer wb.Destroy() 361 bac := b.bulkAddressesCount 362 if err := b.storeBulkAddresses(wb); err != nil { 363 return err 364 } 365 if err := b.d.db.Write(b.d.wo, wb); err != nil { 366 return err 367 } 368 glog.Info("rocksdb: height ", b.height, ", stored ", bac, " addresses, done in ", time.Since(start)) 369 if storeTxAddressesChan != nil { 370 if err := <-storeTxAddressesChan; err != nil { 371 return err 372 } 373 } 374 if storeBalancesChan != nil { 375 if err := <-storeBalancesChan; err != nil { 376 return err 377 } 378 } 379 if storeAddressContractsChan != nil { 380 if err := <-storeAddressContractsChan; err != nil { 381 return err 382 } 383 } 384 var err error 385 b.d.is.BlockTimes, err = b.d.loadBlockTimes() 386 if err != nil { 387 return err 388 } 389 390 if err := b.d.SetInconsistentState(false); err != nil { 391 return err 392 } 393 glog.Info("rocksdb: bulk connect closed, db set to open state") 394 b.d = nil 395 return nil 396 }