github.com/zuoyebang/bitalosdb@v1.1.1-0.20240516111551-79a8c4d8ce20/bithash/table.go (about)

     1  // Copyright 2021 The Bitalosdb author(hustxrb@163.com) and other contributors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package bithash
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/binary"
    20  	"io"
    21  
    22  	"github.com/cockroachdb/errors"
    23  	"github.com/zuoyebang/bitalosdb/internal/base"
    24  )
    25  
    26  const (
    27  	blockHandleLen        = 8
    28  	blockHandleSum        = 3
    29  	bithashMagicLen       = 8
    30  	bithashFooterLen      = 1 + 1*blockHandleLen + 4 + bithashMagicLen
    31  	bithashMagicKey       = "\xf7\xcf\xf4\x85\xb7\x41\xe2\x88"
    32  	bithashMagicKeyOffset = bithashFooterLen - bithashMagicLen
    33  	bithashVersionOffset  = bithashMagicKeyOffset - 4
    34  	bithashFormatVersion2 = 2
    35  	checksumCRC32c        = 1
    36  )
    37  
    38  const (
    39  	MetaIndexHashBH   = "indexhash_blockhandle"
    40  	MetaConflictBH    = "conflict_blockhandle"
    41  	MetaDataBH        = "data_blockhandle"
    42  	IndexHashData     = "indexhash_data"
    43  	IndexHashChecksum = "indexhash_checksum"
    44  )
    45  
    46  // +------------+-------------+--------------+------------+
    47  // | CRC (1B)   | MetaBH (4B) | Version (4B) | Magic (*B) |
    48  // +------------+-------------+--------------+------------+
    49  type footer struct {
    50  	metaBH BlockHandle
    51  }
    52  
    53  func (f footer) encode(buf []byte) []byte {
    54  	buf = buf[:bithashFooterLen]
    55  	for i := range buf {
    56  		buf[i] = 0
    57  	}
    58  
    59  	buf[0] = checksumCRC32c
    60  	encodeBlockHandle(buf[1:], f.metaBH)
    61  	binary.LittleEndian.PutUint32(buf[bithashVersionOffset:], bithashFormatVersion2)
    62  	copy(buf[bithashMagicKeyOffset:], bithashMagicKey)
    63  
    64  	return buf
    65  }
    66  
    67  func checkTableFooter(f ReadableFile) bool {
    68  	stat, err := f.Stat()
    69  	if err != nil {
    70  		return false
    71  	}
    72  
    73  	footerOffset := stat.Size() - bithashFooterLen
    74  	if footerOffset < 0 {
    75  		return false
    76  	}
    77  
    78  	buf := [bithashMagicLen]byte{}
    79  	n, err := f.ReadAt(buf[:], footerOffset+bithashMagicKeyOffset)
    80  	if err != nil && err != io.EOF {
    81  		return false
    82  	}
    83  
    84  	return bytes.Equal(buf[:n], []byte(bithashMagicKey))
    85  }
    86  
    87  func readTableFooter(f ReadableFile) (footer, error) {
    88  	stat, err := f.Stat()
    89  	if err != nil {
    90  		return footer{}, errors.Errorf("bithash invalid table could not stat file err:%s", err.Error())
    91  	}
    92  	if stat.Size() < bithashFooterLen {
    93  		return footer{}, ErrBhInvalidTableSize
    94  	}
    95  
    96  	var buf [bithashFooterLen]byte
    97  
    98  	off := stat.Size() - bithashFooterLen
    99  	n, err := f.ReadAt(buf[:], off)
   100  	if err != nil && err != io.EOF {
   101  		return footer{}, errors.Errorf("bithash invalid table could not read footer err:%s", err.Error())
   102  	}
   103  	if n < bithashFooterLen {
   104  		return footer{}, errors.Errorf("bithash invalid table (footer too short):%d", len(buf))
   105  	}
   106  
   107  	return decodeTableFooter(buf[:n], uint64(stat.Size()))
   108  }
   109  
   110  func decodeTableFooter(buf []byte, end uint64) (footer, error) {
   111  	var ft footer
   112  	buf = buf[len(buf)-bithashFooterLen:]
   113  	version := binary.LittleEndian.Uint32(buf[bithashVersionOffset:bithashMagicKeyOffset])
   114  	if version != bithashFormatVersion2 {
   115  		return ft, errors.Errorf("bithash unsupported format version:%d", version)
   116  	}
   117  
   118  	ft.metaBH = decodeBlockHandle(buf[1:])
   119  	if uint64(ft.metaBH.Offset+ft.metaBH.Length) > end {
   120  		return ft, ErrBhInvalidTableMeta
   121  	}
   122  
   123  	return ft, nil
   124  }
   125  
   126  func (b *Bithash) initTables() error {
   127  	for fn, fileMeta := range b.meta.mu.filesMeta {
   128  		filename := MakeFilepath(b.fs, b.dirname, fileTypeTable, fn)
   129  		f, err := b.fs.Open(filename)
   130  		if err != nil {
   131  			return err
   132  		}
   133  
   134  		b.logger.Infof("bithash initTables file:%s fileMeta:%s", base.GetFilePathBase(filename), fileMeta.String())
   135  
   136  		if fileMeta.state == fileMetaStateCompact {
   137  			b.meta.freeFileMetadata(fn)
   138  			b.stats.FileTotal.Add(^uint32(0))
   139  			_ = b.fs.Remove(filename)
   140  			continue
   141  		}
   142  
   143  		isRebuildTable := false
   144  
   145  		if fileMeta.state != fileMetaStateImmutable {
   146  			if checkTableFooter(f) {
   147  				b.meta.updateFileState(fn, fileMetaStateImmutable)
   148  			} else {
   149  				isRebuildTable = true
   150  			}
   151  		}
   152  
   153  		if !isRebuildTable {
   154  			_, err = b.openTable(fn, f, filename)
   155  			if err != nil {
   156  				b.logger.Warnf("bithash initTables openTable fail file:%s err:%s", base.GetFilePathBase(filename), err)
   157  				if err == ErrBhNewReaderFail {
   158  					isRebuildTable = true
   159  				} else if err = f.Close(); err != nil {
   160  					return err
   161  				}
   162  			}
   163  		}
   164  
   165  		if isRebuildTable {
   166  			if err = f.Close(); err != nil {
   167  				return err
   168  			}
   169  			if err = b.rebuildTable(filename, fn); err != nil {
   170  				return err
   171  			}
   172  		}
   173  	}
   174  
   175  	return nil
   176  }
   177  
   178  func (b *Bithash) rebuildTable(filename string, fn FileNum) (err error) {
   179  	b.meta.updateFileState(fn, fileMetaStateWrite)
   180  
   181  	f, err := b.fs.OpenWR(filename)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	defer func() {
   187  		if err != nil {
   188  			f.Close()
   189  		}
   190  	}()
   191  
   192  	w := newWriter(b, f, filename, fn)
   193  	if err = w.rebuild(); err != nil {
   194  		return err
   195  	}
   196  
   197  	b.stats.KeyTotal.Add(uint64(w.meta.keyNum))
   198  
   199  	isFull := w.isWriteFull()
   200  	if isFull {
   201  		b.closeTableAsync(w, false)
   202  	} else {
   203  		b.pushMutableWriters(w)
   204  		b.addRwwWriters(w)
   205  	}
   206  
   207  	b.logger.Infof("bithash rebuild table success file:%s isFull:%v", base.GetFilePathBase(filename), isFull)
   208  
   209  	return nil
   210  }
   211  
   212  func (b *Bithash) openTable(fileNum FileNum, file File, filename string) (r *Reader, err error) {
   213  	b.tLock.Lock()
   214  	defer b.tLock.Unlock()
   215  
   216  	if b.meta.getPos(fileNum) == 0 {
   217  		return nil, ErrBhFileNumError
   218  	}
   219  
   220  	if file == nil {
   221  		filename = MakeFilepath(b.fs, b.dirname, fileTypeTable, fileNum)
   222  		file, err = b.fs.Open(filename)
   223  		if err != nil {
   224  			return nil, ErrBhOpenTableFile
   225  		}
   226  	}
   227  
   228  	r, err = NewReader(b, file, FileReopenOpt{fs: b.fs, filename: filename, fileNum: fileNum, readOnly: true})
   229  	if err != nil {
   230  		b.logger.Errorf("bithash openTable NewReader file:%s err:%s", filename, err.Error())
   231  		return nil, ErrBhNewReaderFail
   232  	}
   233  
   234  	b.addReaders(r)
   235  
   236  	return r, nil
   237  }
   238  
   239  func (b *Bithash) closeTable(w *Writer, force bool) error {
   240  	if !b.meta.isFileWriting(w.fileNum) {
   241  		return nil
   242  	}
   243  
   244  	w.closing.Store(true)
   245  	defer w.closing.Store(false)
   246  
   247  	if err := w.writeTable(force); err != nil {
   248  		b.logger.Errorf("bithash writeTable fail file:%s err:%s", w.filename, err)
   249  		return err
   250  	}
   251  
   252  	b.meta.updateFileByClosed(w.fileNum, w.meta)
   253  
   254  	if checkTableFooter(w.reader) {
   255  		b.meta.updateFileState(w.fileNum, fileMetaStateImmutable)
   256  	}
   257  
   258  	if _, err := b.openTable(w.fileNum, nil, ""); err != nil {
   259  		b.logger.Errorf("bithash openTable fail file:%s err:%s", w.filename, err)
   260  		return err
   261  	}
   262  
   263  	b.deleteRwwWriters(w.fileNum)
   264  
   265  	if err := w.close(); err != nil {
   266  		b.logger.Errorf("bithash close writer fail file:%s err:%s", w.filename, err)
   267  		return err
   268  	}
   269  
   270  	b.logger.Infof("[BITHASH %d] closeTable success file:%s", b.index, base.GetFilePathBase(w.filename))
   271  	return nil
   272  }
   273  
   274  func (b *Bithash) closeTableAsync(w *Writer, force bool) {
   275  	b.closeTableWg.Add(1)
   276  	go func() {
   277  		defer b.closeTableWg.Done()
   278  		if err := b.closeTable(w, force); err != nil {
   279  			w.b.logger.Errorf("bithash closeTable fail filename:%s err:%v", w.filename, err)
   280  		}
   281  	}()
   282  }
   283  
   284  func (b *Bithash) fileSize(fileNum FileNum) int64 {
   285  	filename := MakeFilepath(b.fs, b.dirname, fileTypeTable, fileNum)
   286  	if info, err := b.fs.Stat(filename); err != nil {
   287  		return 0
   288  	} else {
   289  		return info.Size()
   290  	}
   291  }
   292  
   293  func (b *Bithash) NewTableIter(fileNum FileNum) (*TableIterator, error) {
   294  	if !b.meta.isFileImmutable(fileNum) {
   295  		return nil, ErrBhFileNotImmutable
   296  	}
   297  
   298  	filename := MakeFilepath(b.fs, b.dirname, fileTypeTable, fileNum)
   299  	f, err := b.fs.Open(filename)
   300  	if err != nil {
   301  		return nil, err
   302  	}
   303  
   304  	iter := &TableIterator{
   305  		reader:  f,
   306  		fileNum: fileNum,
   307  		offset:  0,
   308  		kvBuf:   make([]byte, 1024, 1024),
   309  	}
   310  	return iter, nil
   311  }
   312  
   313  type TableIterator struct {
   314  	reader    ReadableFile
   315  	fileNum   FileNum
   316  	offset    int64
   317  	br        block2Reader
   318  	header    [recordHeaderSize]byte
   319  	kvBuf     []byte
   320  	iterKey   *InternalKey
   321  	iterValue []byte
   322  	eof       bool
   323  	err       error
   324  }
   325  
   326  func (i *TableIterator) Valid() bool {
   327  	if i.eof || i.err != nil {
   328  		return false
   329  	}
   330  	return true
   331  }
   332  
   333  func (i *TableIterator) First() (key *InternalKey, value []byte, fileNum FileNum) {
   334  	return i.findEntry()
   335  }
   336  
   337  func (i *TableIterator) Next() (key *InternalKey, value []byte, fileNum FileNum) {
   338  	return i.findEntry()
   339  }
   340  
   341  func (i *TableIterator) Close() error {
   342  	if i.reader == nil {
   343  		return nil
   344  	}
   345  	i.err = i.reader.Close()
   346  	i.reader = nil
   347  	return i.err
   348  }
   349  
   350  func (i *TableIterator) findEntry() (key *InternalKey, value []byte, fileNum FileNum) {
   351  	if !i.Valid() {
   352  		return
   353  	}
   354  
   355  	n, err := i.reader.ReadAt(i.header[:], i.offset)
   356  	if err != nil || n != recordHeaderSize {
   357  		i.eof = true
   358  		i.err = err
   359  		return
   360  	}
   361  	ikeySize, valueSize, fn := i.br.readRecordHeader(i.header[:])
   362  	if ikeySize <= 0 || valueSize <= 0 {
   363  		i.eof = true
   364  		return
   365  	}
   366  
   367  	kvLen := ikeySize + valueSize
   368  	if cap(i.kvBuf) < int(kvLen) {
   369  		i.kvBuf = make([]byte, 0, kvLen*2)
   370  	}
   371  	i.kvBuf = i.kvBuf[:kvLen]
   372  	n, err = i.reader.ReadAt(i.kvBuf, i.offset+recordHeaderSize)
   373  	if err != nil || n != int(kvLen) {
   374  		i.eof = true
   375  		i.err = err
   376  		return
   377  	}
   378  
   379  	i.iterKey, i.iterValue = i.br.readKV(i.kvBuf, ikeySize, valueSize)
   380  	i.offset += int64(recordHeaderSize + kvLen)
   381  
   382  	return i.iterKey, i.iterValue, fn
   383  }