- Add comprehensive database optimization management - Enhance external data source loading with progress tracking - Add optimization statistics and efficiency calculations - Update Go module dependencies for database operations - Implement database size and performance monitoring 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
208 lines
No EOL
5.8 KiB
Go
208 lines
No EOL
5.8 KiB
Go
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
|
|
} |