Initial commit: SRDB - High-performance LSM-Tree database

- Core engine with MemTable, SST, WAL
- B+Tree indexing for SST files  
- Leveled compaction strategy
- Multi-table database management
- Schema validation and secondary indexes
- Query builder with complex conditions
- Web UI with HTMX for data visualization
- Command-line tools for diagnostics
This commit is contained in:
2025-10-08 06:38:12 +08:00
commit ae87c38776
61 changed files with 15475 additions and 0 deletions

206
wal/manager.go Normal file
View File

@@ -0,0 +1,206 @@
package wal
import (
"fmt"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
)
// Manager WAL 管理器,管理多个 WAL 文件
type Manager struct {
dir string
currentWAL *WAL
currentNumber int64
mu sync.Mutex
}
// NewManager 创建 WAL 管理器
func NewManager(dir string) (*Manager, error) {
// 确保目录存在
err := os.MkdirAll(dir, 0755)
if err != nil {
return nil, err
}
// 读取当前 WAL 编号
number, err := readCurrentNumber(dir)
if err != nil {
// 如果读取失败,从 1 开始
number = 1
}
// 打开当前 WAL
walPath := filepath.Join(dir, fmt.Sprintf("%06d.wal", number))
wal, err := Open(walPath)
if err != nil {
return nil, err
}
// 保存当前编号
err = saveCurrentNumber(dir, number)
if err != nil {
wal.Close()
return nil, err
}
return &Manager{
dir: dir,
currentWAL: wal,
currentNumber: number,
}, nil
}
// Append 追加记录到当前 WAL
func (m *Manager) Append(entry *Entry) error {
m.mu.Lock()
defer m.mu.Unlock()
return m.currentWAL.Append(entry)
}
// Sync 同步当前 WAL 到磁盘
func (m *Manager) Sync() error {
m.mu.Lock()
defer m.mu.Unlock()
return m.currentWAL.Sync()
}
// Rotate 切换到新的 WAL 文件
func (m *Manager) Rotate() (int64, error) {
m.mu.Lock()
defer m.mu.Unlock()
// 记录旧的 WAL 编号
oldNumber := m.currentNumber
// 关闭当前 WAL
err := m.currentWAL.Close()
if err != nil {
return 0, err
}
// 创建新 WAL
m.currentNumber++
walPath := filepath.Join(m.dir, fmt.Sprintf("%06d.wal", m.currentNumber))
wal, err := Open(walPath)
if err != nil {
return 0, err
}
m.currentWAL = wal
// 更新 CURRENT 文件
err = saveCurrentNumber(m.dir, m.currentNumber)
if err != nil {
return 0, err
}
return oldNumber, nil
}
// Delete 删除指定的 WAL 文件
func (m *Manager) Delete(number int64) error {
m.mu.Lock()
defer m.mu.Unlock()
walPath := filepath.Join(m.dir, fmt.Sprintf("%06d.wal", number))
return os.Remove(walPath)
}
// GetCurrentNumber 获取当前 WAL 编号
func (m *Manager) GetCurrentNumber() int64 {
m.mu.Lock()
defer m.mu.Unlock()
return m.currentNumber
}
// RecoverAll 恢复所有 WAL 文件
func (m *Manager) RecoverAll() ([]*Entry, error) {
// 查找所有 WAL 文件
pattern := filepath.Join(m.dir, "*.wal")
files, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
if len(files) == 0 {
return nil, nil
}
// 按文件名排序(确保按时间顺序)
sort.Strings(files)
var allEntries []*Entry
// 依次读取每个 WAL
for _, file := range files {
reader, err := NewReader(file)
if err != nil {
continue
}
entries, err := reader.Read()
reader.Close()
if err != nil {
continue
}
allEntries = append(allEntries, entries...)
}
return allEntries, nil
}
// ListWALFiles 列出所有 WAL 文件
func (m *Manager) ListWALFiles() ([]string, error) {
pattern := filepath.Join(m.dir, "*.wal")
files, err := filepath.Glob(pattern)
if err != nil {
return nil, err
}
sort.Strings(files)
return files, nil
}
// Close 关闭 WAL 管理器
func (m *Manager) Close() error {
m.mu.Lock()
defer m.mu.Unlock()
if m.currentWAL != nil {
return m.currentWAL.Close()
}
return nil
}
// readCurrentNumber 读取当前 WAL 编号
func readCurrentNumber(dir string) (int64, error) {
currentPath := filepath.Join(dir, "CURRENT")
data, err := os.ReadFile(currentPath)
if err != nil {
return 0, err
}
number, err := strconv.ParseInt(strings.TrimSpace(string(data)), 10, 64)
if err != nil {
return 0, err
}
return number, nil
}
// saveCurrentNumber 保存当前 WAL 编号
func saveCurrentNumber(dir string, number int64) error {
currentPath := filepath.Join(dir, "CURRENT")
data := []byte(fmt.Sprintf("%d\n", number))
return os.WriteFile(currentPath, data, 0644)
}

208
wal/wal.go Normal file
View File

@@ -0,0 +1,208 @@
package wal
import (
"encoding/binary"
"hash/crc32"
"io"
"os"
"sync"
)
const (
// Entry 类型
EntryTypePut = 1
EntryTypeDelete = 2 // 预留,暂不支持
// Entry Header 大小
EntryHeaderSize = 17 // CRC32(4) + Length(4) + Type(1) + Seq(8)
)
// Entry WAL 条目
type Entry struct {
Type byte // 操作类型
Seq int64 // _seq
Data []byte // 数据
CRC32 uint32 // 校验和
}
// WAL Write-Ahead Log
type WAL struct {
file *os.File
offset int64
mu sync.Mutex
}
// Open 打开 WAL 文件
func Open(path string) (*WAL, error) {
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
if err != nil {
return nil, err
}
// 获取当前文件大小
stat, err := file.Stat()
if err != nil {
file.Close()
return nil, err
}
return &WAL{
file: file,
offset: stat.Size(),
}, nil
}
// Append 追加一条记录
func (w *WAL) Append(entry *Entry) error {
w.mu.Lock()
defer w.mu.Unlock()
// 序列化 Entry
data := w.marshalEntry(entry)
// 写入文件
_, err := w.file.Write(data)
if err != nil {
return err
}
w.offset += int64(len(data))
return nil
}
// Sync 同步到磁盘
func (w *WAL) Sync() error {
w.mu.Lock()
defer w.mu.Unlock()
return w.file.Sync()
}
// Close 关闭 WAL
func (w *WAL) Close() error {
w.mu.Lock()
defer w.mu.Unlock()
return w.file.Close()
}
// Truncate 清空 WAL
func (w *WAL) Truncate() error {
w.mu.Lock()
defer w.mu.Unlock()
err := w.file.Truncate(0)
if err != nil {
return err
}
_, err = w.file.Seek(0, 0)
if err != nil {
return err
}
w.offset = 0
return nil
}
// marshalEntry 序列化 Entry
func (w *WAL) marshalEntry(entry *Entry) []byte {
dataLen := len(entry.Data)
totalLen := EntryHeaderSize + dataLen
buf := make([]byte, totalLen)
// 计算 CRC32 (不包括 CRC32 字段本身)
crcData := buf[4:totalLen]
binary.LittleEndian.PutUint32(crcData[0:4], uint32(dataLen))
crcData[4] = entry.Type
binary.LittleEndian.PutUint64(crcData[5:13], uint64(entry.Seq))
copy(crcData[13:], entry.Data)
crc := crc32.ChecksumIEEE(crcData)
// 写入 CRC32
binary.LittleEndian.PutUint32(buf[0:4], crc)
return buf
}
// Reader WAL 读取器
type Reader struct {
file *os.File
}
// NewReader 创建 WAL 读取器
func NewReader(path string) (*Reader, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
return &Reader{
file: file,
}, nil
}
// Read 读取所有 Entry
func (r *Reader) Read() ([]*Entry, error) {
var entries []*Entry
for {
entry, err := r.readEntry()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
entries = append(entries, entry)
}
return entries, nil
}
// Close 关闭读取器
func (r *Reader) Close() error {
return r.file.Close()
}
// readEntry 读取一条 Entry
func (r *Reader) readEntry() (*Entry, error) {
// 读取 Header
header := make([]byte, EntryHeaderSize)
_, err := io.ReadFull(r.file, header)
if err != nil {
return nil, err
}
// 解析 Header
crc := binary.LittleEndian.Uint32(header[0:4])
dataLen := binary.LittleEndian.Uint32(header[4:8])
entryType := header[8]
seq := int64(binary.LittleEndian.Uint64(header[9:17]))
// 读取 Data
data := make([]byte, dataLen)
_, err = io.ReadFull(r.file, data)
if err != nil {
return nil, err
}
// 验证 CRC32
crcData := make([]byte, EntryHeaderSize-4+int(dataLen))
copy(crcData[0:EntryHeaderSize-4], header[4:])
copy(crcData[EntryHeaderSize-4:], data)
if crc32.ChecksumIEEE(crcData) != crc {
return nil, io.ErrUnexpectedEOF // CRC 校验失败
}
return &Entry{
Type: entryType,
Seq: seq,
Data: data,
CRC32: crc,
}, nil
}

130
wal/wal_test.go Normal file
View File

@@ -0,0 +1,130 @@
package wal
import (
"os"
"testing"
)
func TestWAL(t *testing.T) {
// 1. 创建 WAL
wal, err := Open("test.wal")
if err != nil {
t.Fatal(err)
}
defer os.Remove("test.wal")
// 2. 写入数据
for i := int64(1); i <= 100; i++ {
entry := &Entry{
Type: EntryTypePut,
Seq: i,
Data: []byte("value_" + string(rune(i))),
}
err := wal.Append(entry)
if err != nil {
t.Fatal(err)
}
}
// 3. Sync
err = wal.Sync()
if err != nil {
t.Fatal(err)
}
wal.Close()
t.Log("Written 100 entries")
// 4. 读取数据
reader, err := NewReader("test.wal")
if err != nil {
t.Fatal(err)
}
defer reader.Close()
entries, err := reader.Read()
if err != nil {
t.Fatal(err)
}
if len(entries) != 100 {
t.Errorf("Expected 100 entries, got %d", len(entries))
}
// 验证数据
for i, entry := range entries {
expectedSeq := int64(i + 1)
if entry.Seq != expectedSeq {
t.Errorf("Entry %d: expected Seq=%d, got %d", i, expectedSeq, entry.Seq)
}
if entry.Type != EntryTypePut {
t.Errorf("Entry %d: expected Type=%d, got %d", i, EntryTypePut, entry.Type)
}
}
t.Log("All tests passed!")
}
func TestWALTruncate(t *testing.T) {
// 创建 WAL
wal, err := Open("test_truncate.wal")
if err != nil {
t.Fatal(err)
}
defer os.Remove("test_truncate.wal")
// 写入数据
for i := int64(1); i <= 10; i++ {
entry := &Entry{
Type: EntryTypePut,
Seq: i,
Data: []byte("value"),
}
wal.Append(entry)
}
// Truncate
err = wal.Truncate()
if err != nil {
t.Fatal(err)
}
wal.Close()
// 验证文件为空
reader, err := NewReader("test_truncate.wal")
if err != nil {
t.Fatal(err)
}
defer reader.Close()
entries, err := reader.Read()
if err != nil {
t.Fatal(err)
}
if len(entries) != 0 {
t.Errorf("Expected 0 entries after truncate, got %d", len(entries))
}
t.Log("Truncate test passed!")
}
func BenchmarkWALAppend(b *testing.B) {
wal, _ := Open("bench.wal")
defer os.Remove("bench.wal")
defer wal.Close()
entry := &Entry{
Type: EntryTypePut,
Seq: 1,
Data: make([]byte, 100),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
entry.Seq = int64(i)
wal.Append(entry)
}
}