Skip to content

Critical Performance and Design Issues in PostgreSQL Storage Layer #303

Description

@jigar-joshi-nirmata

🚨 Summary

The current PostgreSQL storage implementation has critical performance bottlenecks and design issues

📊 Impact

  • Kyverno policy enforcement is delayed (waiting on report storage)
  • API server experiences high load due to slow queries

🐛 Critical Issues

Issue #1: Global Mutex Locks Serialize All Operations 🔴

Location: pkg/storage/db/polr.go:19-23, cpolr.go, ephr.go, cephr.go, report.go, clusterreport.go

Problem:

type polrdb struct {
    sync.Mutex  // ← ONE LOCK FOR ALL POLICY REPORTS
    MultiDB   *MultiDB
}

func (p *polrdb) Create(ctx context.Context, report *PolicyReport) error {
    p.Lock()    // ← Blocks ALL other operations
    defer p.Unlock()
    // ... database work ...
}

Issue #2: Code Duplication (969 Lines × 6 Times) 🔴

Location: All files in pkg/storage/db/

Problem:

polr.go           - 181 lines (PolicyReport CRUD)
cpolr.go          - 170 lines (ClusterPolicyReport CRUD) - 95% identical
ephr.go           - 172 lines (EphemeralReport CRUD) - 95% identical
cephr.go          - 172 lines (ClusterEphemeralReport CRUD) - 95% identical  
report.go         - 137 lines (Report CRUD) - 95% identical
clusterreport.go  - 137 lines (ClusterReport CRUD) - 95% identical

Impact:

  • Bug fixes must be applied in 6 places
  • Already has inconsistent error handling across files
  • Adding new features requires changing 6 files
  • High maintenance burden

Evidence of Problems:

  • Migration watchers have return instead of continue (bug in all 6 types)
  • Error handling differs between implementations
  • Some have nil checks, others don't

Issue #3: Postgres should support read replica 🟠

Location: pkg/storage/db/multiDB.go:26-47

Problem:
Every query is diverged to primaryDB, however read queries must be handled via readReplica


Issue #4: Migration Watchers Exit After First Event 🔴

Location: pkg/server/config.go:180-227 (and 3 other watch loops)

Problem:

go func() {
    for event := range cpolrWatchInterface.ResultChan() {
        switch event.Type {
        case watch.Added:
            cpolr := event.Object.(*ClusterPolicyReport)
            if _, ok := cpolr.Annotations[ServedByAnnotation]; ok {
                return  // ← BUG: Should be 'continue'!
            }
            store.Create(ctx, cpolr)
        }
    }
}()

Impact:

  • Watcher exits after processing FIRST report with annotation
  • Subsequent reports are NOT synced to database
  • Silent failure (no error logged)
  • Data inconsistency between etcd and PostgreSQL

Fix: Change return to continue


Issue #5: No Pagination in List Operations 🔴

Location: All List() methods in pkg/storage/db/

Problem:

func (p *polrdb) List(ctx context.Context, namespace string) ([]*PolicyReport, error) {
    // Fetches ALL reports in namespace
    rows, err := p.MultiDB.ReadQuery(ctx, "SELECT report FROM policyreports WHERE namespace = $1")
    
    for rows.Next() {
        // Deserializes ALL reports
        json.Unmarshal([]byte(jsonb), &report)
        res = append(res, &report)
    }
    return res  // Returns ALL reports
}

Impact:

  • With 10,000 reports @ 100KB each = 1GB transferred and deserialized per LIST
  • Guaranteed OOM (out of memory) in large clusters
  • No way to paginate through results
  • API server times out on large responses

Issue #7: Missing Database Indexes 🟠

Current indexes:

CREATE INDEX policyreportnamespace ON policyreports(namespace)
CREATE INDEX policyreportcluster ON policyreports(clusterId)

Missing:

  • Composite index on (clusterId, namespace, name) for Get queries
  • No index on resource_version for Watch
  • No JSONB GIN indexes for label queries

Impact:

  • Get queries may do full table scans
  • List queries are slow
  • Can't efficiently query by labels

🏗️ Architecture Issues

No Separation of Concerns

  • Storage layer knows about Kubernetes (should be generic)
  • Manual SQL string building (error-prone)
  • No query builder or repository pattern
  • Metrics mixed with business logic

No Abstraction

  • Can't swap storage backends (PostgreSQL, MySQL, etc.)
  • Storage code duplicated for each resource type
  • No generic interface

📚 References

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions