github.com/ethw3/go-ethereuma@v0.0.0-20221013053120-c14602a4c23c/core/rawdb/freezer_batch.go (about)

     1  // Copyright 2021 The go-ethereum Authors
     2  // This file is part of the go-ethereum library.
     3  //
     4  // The go-ethereum 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 go-ethereum 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 go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package rawdb
    18  
    19  import (
    20  	"fmt"
    21  	"sync/atomic"
    22  
    23  	"github.com/ethw3/go-ethereuma/common/math"
    24  	"github.com/ethw3/go-ethereuma/rlp"
    25  	"github.com/golang/snappy"
    26  )
    27  
    28  // This is the maximum amount of data that will be buffered in memory
    29  // for a single freezer table batch.
    30  const freezerBatchBufferLimit = 2 * 1024 * 1024
    31  
    32  // freezerBatch is a write operation of multiple items on a freezer.
    33  type freezerBatch struct {
    34  	tables map[string]*freezerTableBatch
    35  }
    36  
    37  func newFreezerBatch(f *Freezer) *freezerBatch {
    38  	batch := &freezerBatch{tables: make(map[string]*freezerTableBatch, len(f.tables))}
    39  	for kind, table := range f.tables {
    40  		batch.tables[kind] = table.newBatch()
    41  	}
    42  	return batch
    43  }
    44  
    45  // Append adds an RLP-encoded item of the given kind.
    46  func (batch *freezerBatch) Append(kind string, num uint64, item interface{}) error {
    47  	return batch.tables[kind].Append(num, item)
    48  }
    49  
    50  // AppendRaw adds an item of the given kind.
    51  func (batch *freezerBatch) AppendRaw(kind string, num uint64, item []byte) error {
    52  	return batch.tables[kind].AppendRaw(num, item)
    53  }
    54  
    55  // reset initializes the batch.
    56  func (batch *freezerBatch) reset() {
    57  	for _, tb := range batch.tables {
    58  		tb.reset()
    59  	}
    60  }
    61  
    62  // commit is called at the end of a write operation and
    63  // writes all remaining data to tables.
    64  func (batch *freezerBatch) commit() (item uint64, writeSize int64, err error) {
    65  	// Check that count agrees on all batches.
    66  	item = uint64(math.MaxUint64)
    67  	for name, tb := range batch.tables {
    68  		if item < math.MaxUint64 && tb.curItem != item {
    69  			return 0, 0, fmt.Errorf("table %s is at item %d, want %d", name, tb.curItem, item)
    70  		}
    71  		item = tb.curItem
    72  	}
    73  
    74  	// Commit all table batches.
    75  	for _, tb := range batch.tables {
    76  		if err := tb.commit(); err != nil {
    77  			return 0, 0, err
    78  		}
    79  		writeSize += tb.totalBytes
    80  	}
    81  	return item, writeSize, nil
    82  }
    83  
    84  // freezerTableBatch is a batch for a freezer table.
    85  type freezerTableBatch struct {
    86  	t *freezerTable
    87  
    88  	sb          *snappyBuffer
    89  	encBuffer   writeBuffer
    90  	dataBuffer  []byte
    91  	indexBuffer []byte
    92  	curItem     uint64 // expected index of next append
    93  	totalBytes  int64  // counts written bytes since reset
    94  }
    95  
    96  // newBatch creates a new batch for the freezer table.
    97  func (t *freezerTable) newBatch() *freezerTableBatch {
    98  	batch := &freezerTableBatch{t: t}
    99  	if !t.noCompression {
   100  		batch.sb = new(snappyBuffer)
   101  	}
   102  	batch.reset()
   103  	return batch
   104  }
   105  
   106  // reset clears the batch for reuse.
   107  func (batch *freezerTableBatch) reset() {
   108  	batch.dataBuffer = batch.dataBuffer[:0]
   109  	batch.indexBuffer = batch.indexBuffer[:0]
   110  	batch.curItem = atomic.LoadUint64(&batch.t.items)
   111  	batch.totalBytes = 0
   112  }
   113  
   114  // Append rlp-encodes and adds data at the end of the freezer table. The item number is a
   115  // precautionary parameter to ensure data correctness, but the table will reject already
   116  // existing data.
   117  func (batch *freezerTableBatch) Append(item uint64, data interface{}) error {
   118  	if item != batch.curItem {
   119  		return fmt.Errorf("%w: have %d want %d", errOutOrderInsertion, item, batch.curItem)
   120  	}
   121  
   122  	// Encode the item.
   123  	batch.encBuffer.Reset()
   124  	if err := rlp.Encode(&batch.encBuffer, data); err != nil {
   125  		return err
   126  	}
   127  	encItem := batch.encBuffer.data
   128  	if batch.sb != nil {
   129  		encItem = batch.sb.compress(encItem)
   130  	}
   131  	return batch.appendItem(encItem)
   132  }
   133  
   134  // AppendRaw injects a binary blob at the end of the freezer table. The item number is a
   135  // precautionary parameter to ensure data correctness, but the table will reject already
   136  // existing data.
   137  func (batch *freezerTableBatch) AppendRaw(item uint64, blob []byte) error {
   138  	if item != batch.curItem {
   139  		return fmt.Errorf("%w: have %d want %d", errOutOrderInsertion, item, batch.curItem)
   140  	}
   141  
   142  	encItem := blob
   143  	if batch.sb != nil {
   144  		encItem = batch.sb.compress(blob)
   145  	}
   146  	return batch.appendItem(encItem)
   147  }
   148  
   149  func (batch *freezerTableBatch) appendItem(data []byte) error {
   150  	// Check if item fits into current data file.
   151  	itemSize := int64(len(data))
   152  	itemOffset := batch.t.headBytes + int64(len(batch.dataBuffer))
   153  	if itemOffset+itemSize > int64(batch.t.maxFileSize) {
   154  		// It doesn't fit, go to next file first.
   155  		if err := batch.commit(); err != nil {
   156  			return err
   157  		}
   158  		if err := batch.t.advanceHead(); err != nil {
   159  			return err
   160  		}
   161  		itemOffset = 0
   162  	}
   163  
   164  	// Put data to buffer.
   165  	batch.dataBuffer = append(batch.dataBuffer, data...)
   166  	batch.totalBytes += itemSize
   167  
   168  	// Put index entry to buffer.
   169  	entry := indexEntry{filenum: batch.t.headId, offset: uint32(itemOffset + itemSize)}
   170  	batch.indexBuffer = entry.append(batch.indexBuffer)
   171  	batch.curItem++
   172  
   173  	return batch.maybeCommit()
   174  }
   175  
   176  // maybeCommit writes the buffered data if the buffer is full enough.
   177  func (batch *freezerTableBatch) maybeCommit() error {
   178  	if len(batch.dataBuffer) > freezerBatchBufferLimit {
   179  		return batch.commit()
   180  	}
   181  	return nil
   182  }
   183  
   184  // commit writes the batched items to the backing freezerTable.
   185  func (batch *freezerTableBatch) commit() error {
   186  	// Write data.
   187  	_, err := batch.t.head.Write(batch.dataBuffer)
   188  	if err != nil {
   189  		return err
   190  	}
   191  	dataSize := int64(len(batch.dataBuffer))
   192  	batch.dataBuffer = batch.dataBuffer[:0]
   193  
   194  	// Write indices.
   195  	_, err = batch.t.index.Write(batch.indexBuffer)
   196  	if err != nil {
   197  		return err
   198  	}
   199  	indexSize := int64(len(batch.indexBuffer))
   200  	batch.indexBuffer = batch.indexBuffer[:0]
   201  
   202  	// Update headBytes of table.
   203  	batch.t.headBytes += dataSize
   204  	atomic.StoreUint64(&batch.t.items, batch.curItem)
   205  
   206  	// Update metrics.
   207  	batch.t.sizeGauge.Inc(dataSize + indexSize)
   208  	batch.t.writeMeter.Mark(dataSize + indexSize)
   209  	return nil
   210  }
   211  
   212  // snappyBuffer writes snappy in block format, and can be reused. It is
   213  // reset when WriteTo is called.
   214  type snappyBuffer struct {
   215  	dst []byte
   216  }
   217  
   218  // compress snappy-compresses the data.
   219  func (s *snappyBuffer) compress(data []byte) []byte {
   220  	// The snappy library does not care what the capacity of the buffer is,
   221  	// but only checks the length. If the length is too small, it will
   222  	// allocate a brand new buffer.
   223  	// To avoid that, we check the required size here, and grow the size of the
   224  	// buffer to utilize the full capacity.
   225  	if n := snappy.MaxEncodedLen(len(data)); len(s.dst) < n {
   226  		if cap(s.dst) < n {
   227  			s.dst = make([]byte, n)
   228  		}
   229  		s.dst = s.dst[:n]
   230  	}
   231  
   232  	s.dst = snappy.Encode(s.dst, data)
   233  	return s.dst
   234  }
   235  
   236  // writeBuffer implements io.Writer for a byte slice.
   237  type writeBuffer struct {
   238  	data []byte
   239  }
   240  
   241  func (wb *writeBuffer) Write(data []byte) (int, error) {
   242  	wb.data = append(wb.data, data...)
   243  	return len(data), nil
   244  }
   245  
   246  func (wb *writeBuffer) Reset() {
   247  	wb.data = wb.data[:0]
   248  }