kevo/pkg/sstable/footer/footer.go
Jeremy Tregunna 6fc3be617d
Some checks failed
Go Tests / Run Tests (1.24.2) (push) Has been cancelled
feat: Initial release of kevo storage engine.
Adds a complete LSM-based storage engine with these features:
- Single-writer based architecture for the storage engine
- WAL for durability, and hey it's configurable
- MemTable with skip list implementation for fast read/writes
- SSTable with block-based structure for on-disk level-based storage
- Background compaction with tiered strategy
- ACID transactions
- Good documentation (I hope)
2025-04-20 14:06:50 -06:00

122 lines
3.6 KiB
Go

package footer
import (
"encoding/binary"
"fmt"
"io"
"time"
"github.com/cespare/xxhash/v2"
)
const (
// FooterSize is the fixed size of the footer in bytes
FooterSize = 52
// FooterMagic is a magic number to verify we're reading a valid footer
FooterMagic = uint64(0xFACEFEEDFACEFEED)
// CurrentVersion is the current file format version
CurrentVersion = uint32(1)
)
// Footer contains metadata for an SSTable file
type Footer struct {
// Magic number for integrity checking
Magic uint64
// Version of the file format
Version uint32
// Timestamp of when the file was created
Timestamp int64
// Offset where the index block starts
IndexOffset uint64
// Size of the index block in bytes
IndexSize uint32
// Total number of key/value pairs
NumEntries uint32
// Smallest key in the file
MinKeyOffset uint32
// Largest key in the file
MaxKeyOffset uint32
// Checksum of all footer fields excluding the checksum itself
Checksum uint64
}
// NewFooter creates a new footer with the given parameters
func NewFooter(indexOffset uint64, indexSize uint32, numEntries uint32,
minKeyOffset, maxKeyOffset uint32) *Footer {
return &Footer{
Magic: FooterMagic,
Version: CurrentVersion,
Timestamp: time.Now().UnixNano(),
IndexOffset: indexOffset,
IndexSize: indexSize,
NumEntries: numEntries,
MinKeyOffset: minKeyOffset,
MaxKeyOffset: maxKeyOffset,
Checksum: 0, // Will be calculated during serialization
}
}
// Encode serializes the footer to a byte slice
func (f *Footer) Encode() []byte {
result := make([]byte, FooterSize)
// Encode all fields directly into the buffer
binary.LittleEndian.PutUint64(result[0:8], f.Magic)
binary.LittleEndian.PutUint32(result[8:12], f.Version)
binary.LittleEndian.PutUint64(result[12:20], uint64(f.Timestamp))
binary.LittleEndian.PutUint64(result[20:28], f.IndexOffset)
binary.LittleEndian.PutUint32(result[28:32], f.IndexSize)
binary.LittleEndian.PutUint32(result[32:36], f.NumEntries)
binary.LittleEndian.PutUint32(result[36:40], f.MinKeyOffset)
binary.LittleEndian.PutUint32(result[40:44], f.MaxKeyOffset)
// Calculate checksum of all fields excluding the checksum itself
f.Checksum = xxhash.Sum64(result[:44])
binary.LittleEndian.PutUint64(result[44:], f.Checksum)
return result
}
// WriteTo writes the footer to an io.Writer
func (f *Footer) WriteTo(w io.Writer) (int64, error) {
data := f.Encode()
n, err := w.Write(data)
return int64(n), err
}
// Decode parses a footer from a byte slice
func Decode(data []byte) (*Footer, error) {
if len(data) < FooterSize {
return nil, fmt.Errorf("footer data too small: %d bytes, expected %d",
len(data), FooterSize)
}
footer := &Footer{
Magic: binary.LittleEndian.Uint64(data[0:8]),
Version: binary.LittleEndian.Uint32(data[8:12]),
Timestamp: int64(binary.LittleEndian.Uint64(data[12:20])),
IndexOffset: binary.LittleEndian.Uint64(data[20:28]),
IndexSize: binary.LittleEndian.Uint32(data[28:32]),
NumEntries: binary.LittleEndian.Uint32(data[32:36]),
MinKeyOffset: binary.LittleEndian.Uint32(data[36:40]),
MaxKeyOffset: binary.LittleEndian.Uint32(data[40:44]),
Checksum: binary.LittleEndian.Uint64(data[44:]),
}
// Verify magic number
if footer.Magic != FooterMagic {
return nil, fmt.Errorf("invalid footer magic: %x, expected %x",
footer.Magic, FooterMagic)
}
// Verify checksum
expectedChecksum := xxhash.Sum64(data[:44])
if footer.Checksum != expectedChecksum {
return nil, fmt.Errorf("footer checksum mismatch: file has %d, calculated %d",
footer.Checksum, expectedChecksum)
}
return footer, nil
}