diff --git a/compaction.go b/compaction.go index c43ba13..0cf62d5 100644 --- a/compaction.go +++ b/compaction.go @@ -94,10 +94,8 @@ func (p *Picker) UpdateLevelLimits(l0, l1, l2, l3 int64) { } // getLevelSizeLimit 获取层级大小限制(从配置读取) +// 注意:层级配置在 UpdateLevelLimits 后不会改变,因此不需要加锁 func (p *Picker) getLevelSizeLimit(level int) int64 { - p.mu.Lock() - defer p.mu.Unlock() - switch level { case 0: return p.level0SizeLimit @@ -911,8 +909,8 @@ type CompactionManager struct { sstManager *SSTableManager // 添加 sstManager 引用,用于同步删除 readers sstDir string - // 配置(从 Database Options 传递) - configMu sync.RWMutex + // 配置(从 Database Options 传递,启动后不可变) + configMu sync.Mutex // 仅用于 ApplyConfig,防御性编程 logger *slog.Logger level0SizeLimit int64 level1SizeLimit int64 @@ -1017,11 +1015,9 @@ func (m *CompactionManager) Stop() { func (m *CompactionManager) backgroundCompaction() { defer m.wg.Done() - // 使用配置的间隔时间 - m.configMu.RLock() + // Options 是不可变的,配置在启动后不会改变,直接读取即可 interval := m.compactionInterval disabled := m.disableCompaction - m.configMu.RUnlock() if disabled { return // 禁用自动 Compaction,直接退出 @@ -1035,22 +1031,6 @@ func (m *CompactionManager) backgroundCompaction() { case <-m.stopCh: return case <-ticker.C: - // 检查配置是否被更新 - m.configMu.RLock() - newInterval := m.compactionInterval - disabled := m.disableCompaction - m.configMu.RUnlock() - - if disabled { - return // 运行中被禁用,退出 - } - - // 如果间隔时间改变,重新创建 ticker - if newInterval != interval { - interval = newInterval - ticker.Reset(interval) - } - m.maybeCompact() } } @@ -1435,15 +1415,30 @@ func (m *CompactionManager) GetLevelStats() []LevelStats { return stats } +// GetLevelSizeLimit 获取指定层级的大小限制(公开方法,供 WebUI 等外部使用) +// 注意:Options 是不可变的,配置在 ApplyConfig 后不会改变,因此不需要加锁 +func (m *CompactionManager) GetLevelSizeLimit(level int) int64 { + switch level { + case 0: + return m.level0SizeLimit + case 1: + return m.level1SizeLimit + case 2: + return m.level2SizeLimit + case 3: + return m.level3SizeLimit + default: + return m.level3SizeLimit + } +} + // backgroundGarbageCollection 后台垃圾回收循环 func (m *CompactionManager) backgroundGarbageCollection() { defer m.wg.Done() - // 使用配置的间隔时间 - m.configMu.RLock() + // Options 是不可变的,配置在启动后不会改变,直接读取即可 interval := m.gcInterval disabled := m.disableGC - m.configMu.RUnlock() if disabled { return // 禁用垃圾回收,直接退出 @@ -1457,22 +1452,6 @@ func (m *CompactionManager) backgroundGarbageCollection() { case <-m.stopCh: return case <-ticker.C: - // 检查配置是否被更新 - m.configMu.RLock() - newInterval := m.gcInterval - disabled := m.disableGC - m.configMu.RUnlock() - - if disabled { - return // 运行中被禁用,退出 - } - - // 如果间隔时间改变,重新创建 ticker - if newInterval != interval { - interval = newInterval - ticker.Reset(interval) - } - m.collectOrphanFiles() } } @@ -1515,10 +1494,8 @@ func (m *CompactionManager) collectOrphanFiles() { // 检查是否是活跃文件 if !activeFiles[fileNum] { // 检查文件修改时间,避免删除正在 flush 的文件 - // 使用配置的文件最小年龄(默认 1 分钟,可能正在 LogAndApply) - m.configMu.RLock() + // Options 是不可变的,直接读取配置即可 minAge := m.gcFileMinAge - m.configMu.RUnlock() fileInfo, err := os.Stat(sstPath) if err != nil { diff --git a/examples/webui/commands/webui.go b/examples/webui/commands/webui.go index d904b26..810ff97 100644 --- a/examples/webui/commands/webui.go +++ b/examples/webui/commands/webui.go @@ -63,16 +63,21 @@ func StartWebUI(dbPath string, addr string) { } else { // 插入一些示例数据 users := []map[string]any{ - {"name": "Alice", "email": "alice@example.com", "age": 30, "city": "Beijing"}, - {"name": "Bob", "email": "bob@example.com", "age": 25, "city": "Shanghai"}, - {"name": "Charlie", "email": "charlie@example.com", "age": 35, "city": "Guangzhou"}, - {"name": "David", "email": "david@example.com", "age": 28, "city": "Shenzhen"}, - {"name": "Eve", "email": "eve@example.com", "age": 32, "city": "Hangzhou"}, + {"name": "Alice", "email": "alice@example.com", "age": int64(30), "city": "Beijing"}, + {"name": "Bob", "email": "bob@example.com", "age": int64(25), "city": "Shanghai"}, + {"name": "Charlie", "email": "charlie@example.com", "age": int64(35), "city": "Guangzhou"}, + {"name": "David", "email": "david@example.com", "age": int64(28), "city": "Shenzhen"}, + {"name": "Eve", "email": "eve@example.com", "age": int64(32), "city": "Hangzhou"}, } + insertedCount := 0 for _, user := range users { - table.Insert(user) + if err := table.Insert(user); err != nil { + log.Printf("Failed to insert user: %v, error: %v", user, err) + } else { + insertedCount++ + } } - log.Printf("Created users table with %d records", len(users)) + log.Printf("Created users table with %d/%d records", insertedCount, len(users)) } } @@ -83,17 +88,22 @@ func StartWebUI(dbPath string, addr string) { } else { // 插入一些示例数据 products := []map[string]any{ - {"product_name": "Laptop", "price": 999.99, "quantity": 10, "category": "Electronics"}, - {"product_name": "Mouse", "price": 29.99, "quantity": 50, "category": "Electronics"}, - {"product_name": "Keyboard", "price": 79.99, "quantity": 30, "category": "Electronics"}, - {"product_name": "Monitor", "price": 299.99, "quantity": 15, "category": "Electronics"}, - {"product_name": "Desk", "price": 199.99, "quantity": 5, "category": "Furniture"}, - {"product_name": "Chair", "price": 149.99, "quantity": 8, "category": "Furniture"}, + {"product_name": "Laptop", "price": 999.99, "quantity": int64(10), "category": "Electronics"}, + {"product_name": "Mouse", "price": 29.99, "quantity": int64(50), "category": "Electronics"}, + {"product_name": "Keyboard", "price": 79.99, "quantity": int64(30), "category": "Electronics"}, + {"product_name": "Monitor", "price": 299.99, "quantity": int64(15), "category": "Electronics"}, + {"product_name": "Desk", "price": 199.99, "quantity": int64(5), "category": "Furniture"}, + {"product_name": "Chair", "price": 149.99, "quantity": int64(8), "category": "Furniture"}, } + insertedCount := 0 for _, product := range products { - table.Insert(product) + if err := table.Insert(product); err != nil { + log.Printf("Failed to insert product: %v, error: %v", product, err) + } else { + insertedCount++ + } } - log.Printf("Created products table with %d records", len(products)) + log.Printf("Created products table with %d/%d records", insertedCount, len(products)) } } diff --git a/webui/webui.go b/webui/webui.go index 1a70c5f..b8cd1ba 100644 --- a/webui/webui.go +++ b/webui/webui.go @@ -214,12 +214,12 @@ func (ui *WebUI) handleTableManifest(w http.ResponseWriter, r *http.Request, tab Files []FileInfo `json:"files"` } - // 获取 Compaction Manager 和 Picker + // 获取 Compaction Manager compactionMgr := table.GetCompactionManager() - picker := compactionMgr.GetPicker() - levels := make([]LevelInfo, 0) + levels := make([]LevelInfo, 0, 7) for level := range 7 { + // 只调用一次 GetLevel,避免重复复制文件列表 files := version.GetLevel(level) totalSize := int64(0) @@ -236,9 +236,15 @@ func (ui *WebUI) handleTableManifest(w http.ResponseWriter, r *http.Request, tab }) } + // 使用已计算的 totalSize 和 fileCount 计算 score,避免再次调用 GetLevel score := 0.0 - if len(files) > 0 { - score = picker.GetLevelScore(version, level) + if len(files) > 0 && level < 3 { // L3 是最后一层,不需要 compaction + // 直接计算 score,避免调用 picker.GetLevelScore(它会再次获取 files) + // 使用下一级的大小限制来计算得分(从 Options 配置读取) + nextLevelLimit := compactionMgr.GetLevelSizeLimit(level + 1) + if nextLevelLimit > 0 { + score = float64(totalSize) / float64(nextLevelLimit) + } } levels = append(levels, LevelInfo{