diff --git a/go.mod b/go.mod index 3b980f5..57c414f 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/client_model v0.6.2 // indirect @@ -41,6 +42,7 @@ require ( go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/otel/trace v1.37.0 // indirect golang.org/x/crypto v0.41.0 // indirect + golang.org/x/sync v0.16.0 // indirect golang.org/x/sys v0.35.0 // indirect golang.org/x/text v0.28.0 // indirect google.golang.org/protobuf v1.36.6 // indirect diff --git a/go.sum b/go.sum index fa6f80e..fdfc532 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,6 @@ github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWN github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4= github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= diff --git a/internal/database/postgres.go b/internal/database/postgres.go index 3d9d8cf..420fc1e 100644 --- a/internal/database/postgres.go +++ b/internal/database/postgres.go @@ -6,37 +6,58 @@ import ( "errors" "fmt" "strings" + "time" "github.com/google/uuid" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" apiv0 "github.com/modelcontextprotocol/registry/pkg/api/v0" ) // PostgreSQL is an implementation of the Database interface using PostgreSQL type PostgreSQL struct { - conn *pgx.Conn + pool *pgxpool.Pool } // NewPostgreSQL creates a new instance of the PostgreSQL database func NewPostgreSQL(ctx context.Context, connectionURI string) (*PostgreSQL, error) { - conn, err := pgx.Connect(ctx, connectionURI) + // Parse connection config for pool settings + config, err := pgxpool.ParseConfig(connectionURI) if err != nil { - return nil, fmt.Errorf("failed to connect to PostgreSQL: %w", err) + return nil, fmt.Errorf("failed to parse PostgreSQL config: %w", err) + } + + // Configure pool for stability-focused defaults + config.MaxConns = 30 // Handle good concurrent load + config.MinConns = 5 // Keep connections warm for fast response + config.MaxConnIdleTime = 30 * time.Minute // Keep connections available for bursts + config.MaxConnLifetime = 2 * time.Hour // Refresh connections regularly for stability + + // Create connection pool with configured settings + pool, err := pgxpool.NewWithConfig(ctx, config) + if err != nil { + return nil, fmt.Errorf("failed to create PostgreSQL pool: %w", err) } // Test the connection - if err = conn.Ping(ctx); err != nil { + if err = pool.Ping(ctx); err != nil { return nil, fmt.Errorf("failed to ping PostgreSQL: %w", err) } - // Run migrations - migrator := NewMigrator(conn) + // Run migrations using a single connection from the pool + conn, err := pool.Acquire(ctx) + if err != nil { + return nil, fmt.Errorf("failed to acquire connection for migrations: %w", err) + } + defer conn.Release() + + migrator := NewMigrator(conn.Conn()) if err := migrator.Migrate(ctx); err != nil { return nil, fmt.Errorf("failed to run database migrations: %w", err) } return &PostgreSQL{ - conn: conn, + pool: pool, }, nil } @@ -120,7 +141,7 @@ func (db *PostgreSQL) List( `, whereClause, argIndex) args = append(args, limit) - rows, err := db.conn.Query(ctx, query, args...) + rows, err := db.pool.Query(ctx, query, args...) if err != nil { return nil, "", fmt.Errorf("failed to query servers: %w", err) } @@ -173,7 +194,7 @@ func (db *PostgreSQL) GetByID(ctx context.Context, id string) (*apiv0.ServerJSON var valueJSON []byte - err := db.conn.QueryRow(ctx, query, id).Scan(&valueJSON) + err := db.pool.QueryRow(ctx, query, id).Scan(&valueJSON) if err != nil { if errors.Is(err, pgx.ErrNoRows) { @@ -216,7 +237,7 @@ func (db *PostgreSQL) CreateServer(ctx context.Context, server *apiv0.ServerJSON VALUES ($1, $2) ` - _, err = db.conn.Exec(ctx, query, id, valueJSON) + _, err = db.pool.Exec(ctx, query, id, valueJSON) if err != nil { return nil, fmt.Errorf("failed to insert server: %w", err) } @@ -248,7 +269,7 @@ func (db *PostgreSQL) UpdateServer(ctx context.Context, id string, server *apiv0 WHERE id = $2 ` - result, err := db.conn.Exec(ctx, query, valueJSON, id) + result, err := db.pool.Exec(ctx, query, valueJSON, id) if err != nil { return nil, fmt.Errorf("failed to update server: %w", err) } @@ -262,5 +283,6 @@ func (db *PostgreSQL) UpdateServer(ctx context.Context, id string, server *apiv0 // Close closes the database connection func (db *PostgreSQL) Close() error { - return db.conn.Close(context.Background()) + db.pool.Close() + return nil }