# Performance

## Overview

Performance optimization is crucial for delivering fast, responsive applications. This guide covers techniques for optimizing database queries, implementing efficient caching strategies, and monitoring application performance.

## Database Optimization

### Query Optimization

```go
// ✅ Efficient - Use specific columns
func (f *GardenFetcher) FindGardenSummaries(ctx context.Context) ([]*GardenSummary, error) {
    query := `
        SELECT id, name, created_at, user_id,
               (SELECT COUNT(*) FROM plants WHERE garden_id = gardens.id) as plant_count
        FROM gardens
        WHERE active = TRUE
        ORDER BY created_at DESC
    `

    rows, err := f.db.QueryContext(ctx, query)
    if err != nil {
        return nil, err
    }
    defer rows.Close()

    var summaries []*GardenSummary
    for rows.Next() {
        summary := &GardenSummary{}
        err := rows.Scan(&summary.Id, &summary.Name, &summary.CreatedAt,
                        &summary.UserId, &summary.PlantCount)
        if err != nil {
            return nil, err
        }
        summaries = append(summaries, summary)
    }

    return summaries, nil
}

// ❌ Inefficient - Loading full entities and counting in Go
func (f *GardenFetcher) FindGardenSummariesSlow(ctx context.Context) ([]*GardenSummary, error) {
    gardens, err := f.FindAll(ctx, nil)
    if err != nil {
        return nil, err
    }

    var summaries []*GardenSummary
    for _, garden := range gardens {
        plants, err := f.plantFetcher.FindByGardenId(ctx, garden.Id)
        if err != nil {
            return nil, err
        }

        summary := &GardenSummary{
            Id:         garden.Id,
            Name:       garden.Name,
            CreatedAt:  garden.CreatedAt,
            PlantCount: len(plants),
        }
        summaries = append(summaries, summary)
    }

    return summaries, nil
}
```

### Batch Operations

```go
// Efficient batch insert
func (h *PlantHandler) CreateMany(ctx context.Context, plants []*mdl.Plant, mod *handler.HandlerMod) error {
    if len(plants) == 0 {
        return nil
    }

    // Build batch insert query
    query := "INSERT INTO plants (id, name, species, garden_id, created_at, updated_at) VALUES "
    args := make([]interface{}, 0, len(plants)*6)
    placeholders := make([]string, 0, len(plants))

    for _, plant := range plants {
        plant.Id = generateID()
        plant.CreatedAt = time.Now()
        plant.UpdatedAt = time.Now()

        placeholders = append(placeholders, "(?, ?, ?, ?, ?, ?)")
        args = append(args, plant.Id, plant.Name, plant.Species,
                     plant.GardenId, plant.CreatedAt, plant.UpdatedAt)
    }

    query += strings.Join(placeholders, ", ")

    _, err := h.db.ExecContext(ctx, query, args...)
    return err
}

// Efficient batch update
func (h *PlantHandler) UpdateManyHeights(ctx context.Context, updates map[string]float64) error {
    if len(updates) == 0 {
        return nil
    }

    query := "UPDATE plants SET height = ?, updated_at = ? WHERE id = ?"
    stmt, err := h.db.PrepareContext(ctx, query)
    if err != nil {
        return err
    }
    defer stmt.Close()

    now := time.Now()
    for id, height := range updates {
        _, err = stmt.ExecContext(ctx, height, now, id)
        if err != nil {
            return err
        }
    }

    return nil
}
```

### Connection Pool Optimization

```go
func setupDatabase(config *config.Database) *sql.DB {
    db, err := sql.Open(config.Driver, config.URL)
    if err != nil {
        log.Fatal(err)
    }

    // Connection pool settings
    db.SetMaxOpenConns(25)              // Maximum open connections
    db.SetMaxIdleConns(5)               // Maximum idle connections
    db.SetConnMaxLifetime(5 * time.Minute) // Connection lifetime
    db.SetConnMaxIdleTime(1 * time.Minute) // Idle timeout

    // Verify connection
    if err := db.Ping(); err != nil {
        log.Fatal(err)
    }

    return db
}
```

## Memory Optimization

### Efficient Data Structures

```go
// Use struct pointers for large objects
type GardenWithPlants struct {
    Garden *mdl.Garden  // Pointer to avoid copying
    Plants []*mdl.Plant // Slice of pointers
}

// Pool objects for reuse
var gardenPool = sync.Pool{
    New: func() interface{} {
        return &mdl.Garden{}
    },
}

func (f *GardenFetcher) FindOneByIdPooled(ctx context.Context, id string) (*mdl.Garden, bool, error) {
    garden := gardenPool.Get().(*mdl.Garden)
    defer gardenPool.Put(garden)

    // Reset all fields
    *garden = mdl.Garden{}

    err := f.db.QueryRowContext(ctx, "SELECT * FROM gardens WHERE id = ?", id).
        Scan(&garden.Id, &garden.Name, &garden.Description, &garden.CreatedAt, &garden.UpdatedAt)

    if err == sql.ErrNoRows {
        return nil, false, nil
    }
    if err != nil {
        return nil, false, err
    }

    // Create new instance to return
    result := &mdl.Garden{}
    *result = *garden

    return result, true, nil
}
```

### Memory Profiling

```go
import (
    _ "net/http/pprof"
    "net/http"
)

// Enable profiling endpoint
func setupProfiling(router *gin.Engine) {
    // Add pprof routes in development
    if gin.Mode() == gin.DebugMode {
        pprof := router.Group("/debug/pprof")
        {
            pprof.GET("/", gin.WrapF(http.HandlerFunc(pprof.Index)))
            pprof.GET("/heap", gin.WrapF(http.HandlerFunc(pprof.Handler("heap").ServeHTTP)))
            pprof.GET("/goroutine", gin.WrapF(http.HandlerFunc(pprof.Handler("goroutine").ServeHTTP)))
            pprof.GET("/allocs", gin.WrapF(http.HandlerFunc(pprof.Handler("allocs").ServeHTTP)))
        }
    }
}

// Memory monitoring
func monitorMemory() {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)

    log.Printf("Memory - Alloc: %d KB, TotalAlloc: %d KB, Sys: %d KB, NumGC: %d",
        bToKb(m.Alloc), bToKb(m.TotalAlloc), bToKb(m.Sys), m.NumGC)
}

func bToKb(b uint64) uint64 {
    return b / 1024
}
```

## Concurrency Optimization

### Worker Pools

```go
type WorkerPool struct {
    workerCount int
    jobQueue    chan Job
    quit        chan bool
}

type Job func() error

func NewWorkerPool(workerCount int, queueSize int) *WorkerPool {
    return &WorkerPool{
        workerCount: workerCount,
        jobQueue:    make(chan Job, queueSize),
        quit:        make(chan bool),
    }
}

func (wp *WorkerPool) Start() {
    for i := 0; i < wp.workerCount; i++ {
        go wp.worker()
    }
}

func (wp *WorkerPool) worker() {
    for {
        select {
        case job := <-wp.jobQueue:
            if err := job(); err != nil {
                log.Printf("Worker job failed: %v", err)
            }
        case <-wp.quit:
            return
        }
    }
}

func (wp *WorkerPool) Submit(job Job) {
    select {
    case wp.jobQueue <- job:
    default:
        log.Println("Job queue is full, dropping job")
    }
}

// Usage for background processing
func (h *EmailHandler) SendWelcomeEmail(userID string) {
    job := func() error {
        user, exists, err := h.userFetcher.FindOneById(context.Background(), userID, nil)
        if err != nil || !exists {
            return err
        }

        return h.emailService.SendWelcomeEmail(user.Email, user.Name)
    }

    h.workerPool.Submit(job)
}
```

### Goroutine Management

```go
// Bounded goroutine execution
func (f *GardenFetcher) LoadGardensWithPlantsParallel(ctx context.Context, gardenIDs []string) ([]*GardenWithPlants, error) {
    const maxConcurrency = 10
    semaphore := make(chan struct{}, maxConcurrency)

    results := make([]*GardenWithPlants, len(gardenIDs))
    errors := make([]error, len(gardenIDs))
    var wg sync.WaitGroup

    for i, gardenID := range gardenIDs {
        wg.Add(1)
        go func(index int, id string) {
            defer wg.Done()

            // Acquire semaphore
            semaphore <- struct{}{}
            defer func() { <-semaphore }()

            garden, exists, err := f.FindOneById(ctx, id, nil)
            if err != nil || !exists {
                errors[index] = err
                return
            }

            plants, err := f.plantFetcher.FindByGardenId(ctx, id)
            if err != nil {
                errors[index] = err
                return
            }

            results[index] = &GardenWithPlants{
                Garden: garden,
                Plants: plants,
            }
        }(i, gardenID)
    }

    wg.Wait()

    // Check for errors
    for _, err := range errors {
        if err != nil {
            return nil, err
        }
    }

    return results, nil
}
```

## HTTP Performance

### Response Compression

```go
import "github.com/gin-contrib/gzip"

func setupCompression(router *gin.Engine) {
    // Enable gzip compression
    router.Use(gzip.Gzip(gzip.DefaultCompression))
}

// Custom compression for large responses
func CompressJSONResponse(data interface{}) ([]byte, error) {
    jsonData, err := json.Marshal(data)
    if err != nil {
        return nil, err
    }

    var buf bytes.Buffer
    gzipWriter := gzip.NewWriter(&buf)

    _, err = gzipWriter.Write(jsonData)
    if err != nil {
        return nil, err
    }

    err = gzipWriter.Close()
    if err != nil {
        return nil, err
    }

    return buf.Bytes(), nil
}
```

### HTTP/2 Server Push

```go
func setupHTTP2Push(router *gin.Engine) {
    router.GET("/gardens/:id", func(c *gin.Context) {
        // Push critical resources
        if pusher := c.Writer.Pusher(); pusher != nil {
            pusher.Push("/assets/css/output.css", nil)
            pusher.Push("/assets/js/app.js", nil)
        }

        // Render response
        // ...
    })
}
```

## Monitoring & Metrics

### Performance Metrics

```go
type MetricsCollector struct {
    requestCount    *prometheus.CounterVec
    requestDuration *prometheus.HistogramVec
    activeRequests  prometheus.Gauge
}

func NewMetricsCollector() *MetricsCollector {
    return &MetricsCollector{
        requestCount: prometheus.NewCounterVec(
            prometheus.CounterOpts{
                Name: "http_requests_total",
                Help: "Total HTTP requests",
            },
            []string{"method", "endpoint", "status"},
        ),
        requestDuration: prometheus.NewHistogramVec(
            prometheus.HistogramOpts{
                Name: "http_request_duration_seconds",
                Help: "HTTP request duration in seconds",
            },
            []string{"method", "endpoint"},
        ),
        activeRequests: prometheus.NewGauge(
            prometheus.GaugeOpts{
                Name: "http_requests_active",
                Help: "Active HTTP requests",
            },
        ),
    }
}

func (mc *MetricsCollector) MetricsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        mc.activeRequests.Inc()

        defer func() {
            duration := time.Since(start).Seconds()
            mc.activeRequests.Dec()

            mc.requestCount.WithLabelValues(
                c.Request.Method,
                c.FullPath(),
                fmt.Sprintf("%d", c.Writer.Status()),
            ).Inc()

            mc.requestDuration.WithLabelValues(
                c.Request.Method,
                c.FullPath(),
            ).Observe(duration)
        }()

        c.Next()
    }
}
```

### Database Query Monitoring

```go
type QueryLogger struct {
    slowQueryThreshold time.Duration
    logger            *log.Logger
}

func NewQueryLogger(threshold time.Duration, logger *log.Logger) *QueryLogger {
    return &QueryLogger{
        slowQueryThreshold: threshold,
        logger:            logger,
    }
}

func (ql *QueryLogger) LogQuery(query string, args []interface{}, duration time.Duration) {
    if duration > ql.slowQueryThreshold {
        ql.logger.Printf("SLOW QUERY (%v): %s [args: %v]", duration, query, args)
    }
}

// Wrapper for database queries
func (f *GardenFetcher) QueryWithLogging(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
    start := time.Now()
    rows, err := f.db.QueryContext(ctx, query, args...)
    duration := time.Since(start)

    f.queryLogger.LogQuery(query, args, duration)

    return rows, err
}
```

## Load Testing

### Benchmark Tests

```go
func BenchmarkGardenFetch(b *testing.B) {
    fetcher := setupBenchmarkFetcher()
    ctx := context.Background()

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        _, _, err := fetcher.FindOneById(ctx, "test-garden-id", nil)
        if err != nil {
            b.Fatal(err)
        }
    }
}

func BenchmarkGardenFetchParallel(b *testing.B) {
    fetcher := setupBenchmarkFetcher()
    ctx := context.Background()

    b.ResetTimer()
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            _, _, err := fetcher.FindOneById(ctx, "test-garden-id", nil)
            if err != nil {
                b.Fatal(err)
            }
        }
    })
}
```

### Load Testing Script

```go
// Simple load test
func loadTest() {
    const (
        baseURL     = "http://localhost:8080"
        concurrency = 10
        requests    = 1000
    )

    client := &http.Client{
        Timeout: 30 * time.Second,
    }

    var wg sync.WaitGroup
    requestsPerWorker := requests / concurrency
    results := make(chan time.Duration, requests)

    for i := 0; i < concurrency; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()

            for j := 0; j < requestsPerWorker; j++ {
                start := time.Now()
                resp, err := client.Get(baseURL + "/api/gardens")
                duration := time.Since(start)

                if err != nil {
                    log.Printf("Request failed: %v", err)
                    continue
                }
                resp.Body.Close()

                results <- duration
            }
        }()
    }

    wg.Wait()
    close(results)

    // Calculate statistics
    var durations []time.Duration
    for duration := range results {
        durations = append(durations, duration)
    }

    sort.Slice(durations, func(i, j int) bool {
        return durations[i] < durations[j]
    })

    fmt.Printf("Total requests: %d\n", len(durations))
    fmt.Printf("Average: %v\n", calculateAverage(durations))
    fmt.Printf("95th percentile: %v\n", durations[int(float64(len(durations))*0.95)])
    fmt.Printf("99th percentile: %v\n", durations[int(float64(len(durations))*0.99)])
}
```

## Performance Best Practices

### General Guidelines

1. **Profile First**: Always measure before optimizing
2. **Database Efficiency**: Optimize queries and use appropriate indexes
3. **Caching Strategy**: Implement multi-level caching
4. **Connection Pooling**: Configure database connection pools properly
5. **Concurrent Processing**: Use goroutines and worker pools for I/O bound tasks
6. **Memory Management**: Use object pools and avoid memory leaks
7. **HTTP Optimization**: Enable compression and use HTTP/2
8. **Monitoring**: Implement comprehensive monitoring and alerting

### Common Anti-patterns

```go
// ❌ N+1 Query Problem
for _, garden := range gardens {
    plants, _ := plantFetcher.FindByGardenId(ctx, garden.Id)
    // Process plants...
}

// ✅ Batch Loading
allPlants, _ := plantFetcher.FindByGardenIds(ctx, gardenIds)
plantsByGarden := groupPlantsByGarden(allPlants)

// ❌ Loading Too Much Data
gardens, _ := gardenFetcher.FindAll(ctx, nil) // Loads everything

// ✅ Pagination and Filtering
mod := gardenFetcher.Mod()
mod.Page = 1
mod.PerPage = 20
gardens, pagination, _ := gardenFetcher.FindPage(ctx, mod)

// ❌ Synchronous Blocking Operations
for _, email := range emails {
    emailService.Send(email) // Blocks for each email
}

// ✅ Asynchronous Processing
for _, email := range emails {
    emailQueue.Enqueue(email)
}
```

Performance optimization is an ongoing process. Regular monitoring, profiling, and load testing help identify bottlenecks and ensure your application scales effectively.