package database import ( "fmt" "os" "time" ) // OptimizationManager handles database storage optimization using SQLite built-in features type OptimizationManager struct { db *Database config *Config lastVacuum time.Time } // NewOptimizationManager creates a new optimization manager func NewOptimizationManager(db *Database, config *Config) *OptimizationManager { return &OptimizationManager{ db: db, config: config, } } // PerformMaintenance runs database maintenance tasks including VACUUM func (om *OptimizationManager) PerformMaintenance() error { now := time.Now() // Check if VACUUM is needed if om.config.VacuumInterval > 0 && now.Sub(om.lastVacuum) >= om.config.VacuumInterval { if err := om.VacuumDatabase(); err != nil { return fmt.Errorf("vacuum failed: %w", err) } om.lastVacuum = now } return nil } // VacuumDatabase performs VACUUM to reclaim space and optimize database func (om *OptimizationManager) VacuumDatabase() error { conn := om.db.GetConnection() if conn == nil { return fmt.Errorf("database connection not available") } start := time.Now() // Get size before VACUUM sizeBefore, err := om.getDatabaseSize() if err != nil { return fmt.Errorf("failed to get database size: %w", err) } // Perform VACUUM if _, err := conn.Exec("VACUUM"); err != nil { return fmt.Errorf("VACUUM operation failed: %w", err) } // Get size after VACUUM sizeAfter, err := om.getDatabaseSize() if err != nil { return fmt.Errorf("failed to get database size after VACUUM: %w", err) } duration := time.Since(start) savedBytes := sizeBefore - sizeAfter savedPercent := float64(savedBytes) / float64(sizeBefore) * 100 fmt.Printf("VACUUM completed in %v: %.1f MB → %.1f MB (saved %.1f MB, %.1f%%)\n", duration, float64(sizeBefore)/(1024*1024), float64(sizeAfter)/(1024*1024), float64(savedBytes)/(1024*1024), savedPercent) return nil } // OptimizeDatabase applies various SQLite optimizations for better storage efficiency func (om *OptimizationManager) OptimizeDatabase() error { conn := om.db.GetConnection() if conn == nil { return fmt.Errorf("database connection not available") } fmt.Println("Optimizing database for storage efficiency...") // Apply storage-friendly pragmas optimizations := []struct{ name string query string description string }{ {"Auto VACUUM", "PRAGMA auto_vacuum = INCREMENTAL", "Enable incremental auto-vacuum"}, {"Incremental VACUUM", "PRAGMA incremental_vacuum", "Reclaim free pages incrementally"}, {"Optimize", "PRAGMA optimize", "Update SQLite query planner statistics"}, {"Analyze", "ANALYZE", "Update table statistics for better query plans"}, } for _, opt := range optimizations { if _, err := conn.Exec(opt.query); err != nil { fmt.Printf("Warning: %s failed: %v\n", opt.name, err) } else { fmt.Printf("✓ %s: %s\n", opt.name, opt.description) } } return nil } // OptimizePageSize sets an optimal page size for the database (requires rebuild) func (om *OptimizationManager) OptimizePageSize(pageSize int) error { conn := om.db.GetConnection() if conn == nil { return fmt.Errorf("database connection not available") } // Check current page size var currentPageSize int if err := conn.QueryRow("PRAGMA page_size").Scan(¤tPageSize); err != nil { return fmt.Errorf("failed to get current page size: %w", err) } if currentPageSize == pageSize { fmt.Printf("Page size already optimal: %d bytes\n", pageSize) return nil } fmt.Printf("Optimizing page size: %d → %d bytes (requires VACUUM)\n", currentPageSize, pageSize) // Set new page size query := fmt.Sprintf("PRAGMA page_size = %d", pageSize) if _, err := conn.Exec(query); err != nil { return fmt.Errorf("failed to set page size: %w", err) } // VACUUM to apply the new page size if err := om.VacuumDatabase(); err != nil { return fmt.Errorf("failed to apply page size change: %w", err) } return nil } // GetOptimizationStats returns current database optimization statistics func (om *OptimizationManager) GetOptimizationStats() (*OptimizationStats, error) { stats := &OptimizationStats{} // Get database size size, err := om.getDatabaseSize() if err != nil { return nil, err } stats.DatabaseSize = size // Get page statistics conn := om.db.GetConnection() if conn != nil { var pageSize, pageCount, freelistCount int conn.QueryRow("PRAGMA page_size").Scan(&pageSize) conn.QueryRow("PRAGMA page_count").Scan(&pageCount) conn.QueryRow("PRAGMA freelist_count").Scan(&freelistCount) stats.PageSize = pageSize stats.PageCount = pageCount stats.FreePages = freelistCount stats.UsedPages = pageCount - freelistCount if pageCount > 0 { stats.Efficiency = float64(stats.UsedPages) / float64(pageCount) * 100 } // Check auto vacuum setting var autoVacuum int conn.QueryRow("PRAGMA auto_vacuum").Scan(&autoVacuum) stats.AutoVacuumEnabled = autoVacuum > 0 } stats.LastVacuum = om.lastVacuum return stats, nil } // OptimizationStats holds database storage optimization statistics type OptimizationStats struct { DatabaseSize int64 `json:"database_size"` PageSize int `json:"page_size"` PageCount int `json:"page_count"` UsedPages int `json:"used_pages"` FreePages int `json:"free_pages"` Efficiency float64 `json:"efficiency_percent"` AutoVacuumEnabled bool `json:"auto_vacuum_enabled"` LastVacuum time.Time `json:"last_vacuum"` } // getDatabaseSize returns the current database file size in bytes func (om *OptimizationManager) getDatabaseSize() (int64, error) { if om.config.Path == "" { return 0, fmt.Errorf("database path not configured") } stat, err := os.Stat(om.config.Path) if err != nil { return 0, fmt.Errorf("failed to stat database file: %w", err) } return stat.Size(), nil }