github.com/klaytn/klaytn@v1.12.1/datasync/dbsyncer/query_engine.go (about)

     1  // Copyright 2019 The klaytn Authors
     2  // This file is part of the klaytn library.
     3  //
     4  // The klaytn library is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Lesser General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // The klaytn library is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    12  // GNU Lesser General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Lesser General Public License
    15  // along with the klaytn library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package dbsyncer
    18  
    19  import (
    20  	"github.com/klaytn/klaytn/blockchain/types"
    21  )
    22  
    23  type MakeQueryRequest struct {
    24  	block       *types.Block
    25  	blockBase   uint64
    26  	txIndexBase int
    27  	txs         []*types.Transaction
    28  	receipts    []*types.Receipt
    29  	inc         int
    30  
    31  	result chan *MakeQueryResult
    32  }
    33  
    34  type BulkInsertRequest struct {
    35  	bulkInserts []*BulkInsertQuery
    36  	inc         int
    37  
    38  	result chan *BulkInsertResult
    39  }
    40  
    41  type BulkInsertResult struct {
    42  	err error
    43  }
    44  
    45  type MakeQueryResult struct {
    46  	block *types.Block
    47  
    48  	cols string
    49  	val  []interface{}
    50  
    51  	scols string
    52  	sval  []interface{}
    53  	count int
    54  
    55  	tcols  string
    56  	tval   []interface{}
    57  	tcount int
    58  
    59  	err error
    60  }
    61  
    62  // QueryEngine is a helper structure to concurrently making insert query
    63  type QueryEngine struct {
    64  	ds          *DBSyncer
    65  	taskQueue   int
    66  	insertQueue int
    67  	tasks       chan *MakeQueryRequest
    68  	inserts     chan *BulkInsertRequest
    69  }
    70  
    71  func newQueryEngine(ds *DBSyncer, taskQueue int, insertQueue int) *QueryEngine {
    72  	queryEngine := &QueryEngine{
    73  		ds:          ds,
    74  		tasks:       make(chan *MakeQueryRequest, taskQueue),
    75  		inserts:     make(chan *BulkInsertRequest, insertQueue),
    76  		taskQueue:   taskQueue,
    77  		insertQueue: insertQueue,
    78  	}
    79  	for i := 0; i < taskQueue; i++ {
    80  		go queryEngine.processing()
    81  	}
    82  	for i := 0; i < insertQueue; i++ {
    83  		go queryEngine.executing()
    84  	}
    85  
    86  	return queryEngine
    87  }
    88  
    89  func (qe *QueryEngine) make(block *types.Block, txKey uint64, tx *types.Transaction, receipt *types.Receipt, result chan *MakeQueryResult) {
    90  	cols, val, txMapArg, summaryArg, err := MakeTxDBRow(block, txKey, tx, receipt)
    91  	scols, sval, count, serr := MakeSummaryDBRow(summaryArg)
    92  	tcols, tval, tcount, terr := MakeTxMappingRow(txMapArg)
    93  
    94  	defer func() {
    95  		// recover from panic caused by writing to a closed channel
    96  		if r := recover(); r != nil {
    97  			logger.Error("channel closed", "err", r)
    98  			return
    99  		}
   100  	}()
   101  	if err == nil && serr == nil && terr == nil {
   102  		result <- &MakeQueryResult{block, cols, val, scols, sval, count, tcols, tval, tcount, nil}
   103  	} else {
   104  		if err != nil {
   105  			logger.Error("fail to make row (tx)", "err", err)
   106  			result <- &MakeQueryResult{block, cols, val, scols, sval, count, tcols, tval, tcount, err}
   107  		}
   108  		if serr != nil {
   109  			logger.Error("fail to make row (summary)", "err", serr)
   110  			result <- &MakeQueryResult{block, cols, val, scols, sval, count, tcols, tval, tcount, serr}
   111  		}
   112  		if terr != nil {
   113  			logger.Error("fail to make row (senderHash)", "err", terr)
   114  			result <- &MakeQueryResult{block, cols, val, scols, sval, count, tcols, tval, tcount, terr}
   115  		}
   116  	}
   117  }
   118  
   119  func (qe *QueryEngine) execute(insertQuery *BulkInsertQuery, result chan *BulkInsertResult) {
   120  	defer func() {
   121  		// recover from panic caused by writing to a closed channel
   122  		if r := recover(); r != nil {
   123  			logger.Error("channel closed", "err", r)
   124  			return
   125  		}
   126  	}()
   127  	if err := qe.ds.bulkInsert(insertQuery.parameters, insertQuery.vals, insertQuery.blockNumber, insertQuery.insertCount); err != nil {
   128  		logger.Error("fail to bulkinsert (tx/summary/senderHash)", "err", err)
   129  		result <- &BulkInsertResult{err}
   130  	}
   131  	result <- &BulkInsertResult{nil}
   132  }
   133  
   134  func (qe *QueryEngine) processing() {
   135  	for task := range qe.tasks {
   136  		for i := 0; i < len(task.txs); i += task.inc {
   137  			txKey := task.blockBase + uint64(task.txIndexBase+i)
   138  			qe.make(task.block, txKey, task.txs[i], task.receipts[i], task.result)
   139  		}
   140  	}
   141  }
   142  
   143  func (qe *QueryEngine) executing() {
   144  	for insert := range qe.inserts {
   145  		for i := 0; i < len(insert.bulkInserts); i += insert.inc {
   146  			qe.execute(insert.bulkInserts[i], insert.result)
   147  		}
   148  	}
   149  }
   150  
   151  func (qe *QueryEngine) insertTransactions(block *types.Block, txs types.Transactions, receipts types.Receipts, result chan *MakeQueryResult) {
   152  	// If there's nothing to recover, abort
   153  	if len(txs) == 0 {
   154  		return
   155  	}
   156  
   157  	blockBase := block.NumberU64() * TX_KEY_FACTOR
   158  
   159  	// Ensure we have meaningful task sizes and schedule the recoveries
   160  	tasks := qe.taskQueue
   161  	if len(txs) < tasks*4 {
   162  		tasks = (len(txs) + 3) / 4
   163  	}
   164  	for i := 0; i < tasks; i++ {
   165  		qe.tasks <- &MakeQueryRequest{
   166  			block:       block,
   167  			blockBase:   blockBase,
   168  			txIndexBase: i,
   169  			txs:         txs[i:],
   170  			receipts:    receipts[i:],
   171  			inc:         tasks,
   172  			result:      result,
   173  		}
   174  	}
   175  }
   176  
   177  func (qe *QueryEngine) executeBulkInsert(bulkInserts []*BulkInsertQuery, result chan *BulkInsertResult) {
   178  	// If there's nothing to recover, abort
   179  	if len(bulkInserts) == 0 {
   180  		return
   181  	}
   182  
   183  	// Ensure we have meaningful task sizes and schedule the recoveries
   184  	tasks := qe.insertQueue
   185  	if len(bulkInserts) < tasks {
   186  		tasks = len(bulkInserts)
   187  	}
   188  	for i := 0; i < tasks; i++ {
   189  		qe.inserts <- &BulkInsertRequest{
   190  			bulkInserts: bulkInserts[i:],
   191  			inc:         tasks,
   192  			result:      result,
   193  		}
   194  	}
   195  }