- 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
207 lines
3.8 KiB
Go
207 lines
3.8 KiB
Go
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)
|
|
}
|