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 }