October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsPC HealthRecommendedCrashes, freezes, slowdowns? Check your PC nowSpot repairable issues before they interrupt work.Check PCOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk6 min

Building a High-Performance REST API in Go with Database Connection Pooling

A practical guide to sharing sql.DB, configuring pool limits carefully, canceling database work with request contexts, and benchmarking changes against your real workload.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use one shared *sql.DB for your Go service, pass each HTTP request’s context into database calls, and tune pool limits only after measuring the API against its real database and workload. sql.DB is a concurrency-safe pool handle—not a single connection—and Go’s documentation says most programs do not need to change its defaults.

How connection pooling works in Go

A *sql.DB represents a pool of underlying database connections. Your handlers and other goroutines can share it; database/sql obtains or creates connections as operations need them and reuses connections when possible. Creating a new *sql.DB for every request defeats that shared-pool design.

sql.Open may validate its arguments without establishing a live database connection. Decide separately how your service verifies connectivity at startup and how readiness checks report database availability. The precise behavior of connection establishment and driver-specific options depends on the driver.

Initialize one shared handle

db, err := sql.Open(driverName, dataSourceName)
if err != nil {
    return err
}

// Configure pool limits here only if they are justified for this service.

pingCtx, cancel := context.WithTimeout(ctx, startupBudget)
defer cancel()
if err := db.PingContext(pingCtx); err != nil {
    _ = db.Close()
    return err
}

// Keep db in your application dependencies and share it with handlers.

This fragment assumes the selected driver is registered and that driverName, dataSourceName, ctx, and startupBudget are supplied by your application. Close the handle during orderly application shutdown. Passing the handle into services or repositories makes its shared lifetime explicit.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall

How to configure database/sql connection pool size

Start with the defaults unless measurements or an operational constraint give you a reason to change them. The four commonly adjusted settings control different aspects of the pool:

Setting What it controls Trade-off to consider
SetMaxOpenConns(n) The maximum number of open connections. Once the limit is occupied, operations needing a connection wait. A limit can constrain database load, but it can also add queueing and, in some program designs, contribute to deadlock if code holds resources while waiting for another connection.
SetMaxIdleConns(n) The maximum number of connections retained idle for reuse. Retaining idle connections can avoid reopening them, while a large idle pool can retain connections the workload does not need.
SetConnMaxIdleTime(d) How long an idle connection may remain in the pool before it is closed. Use it with the database and any intermediary’s idle-connection policies in mind.
SetConnMaxLifetime(d) The maximum age of a connection before it is closed. Use it to fit connection-management requirements; it is not the same as an idle timeout.

Apply settings once during application setup, not independently in each handler. The appropriate values depend on the driver, database capacity, request mix, deployment topology, and any load balancer or proxy that manages connections. There is no universally correct connection count or pool configuration established for every Go API.

Why a maximum can create waiting

A maximum-open limit behaves like a capacity gate: when all permitted connections are in use, other database operations wait. Go’s documentation warns that limiting connections can make database use resemble acquiring a lock or semaphore, and can deadlock an application that waits for another connection while still holding resources needed to release the first one. Keep transactions and other resource-holding work bounded, and inspect code paths that acquire connections in sequence.

A rising pool wait count or wait duration can indicate contention, but it is not by itself proof that the pool limit is too low. The database may already be saturated, requests may be holding connections for too long, or the workload may have changed. Read pool metrics alongside request latency, errors, and database-side health.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

How to cancel a database query when an HTTP request is canceled

Use the inbound request context for database work. In Go’s HTTP server, that context is canceled when the client connection closes, an HTTP/2 request is canceled, or the handler returns. Calling a context-aware database method lets the driver and database operation respond to cancellation as supported by the driver.

Pass context through the handler and service layers

func (s *Server) getItem(w http.ResponseWriter, r *http.Request) {
    item, err := s.items.Get(r.Context(), itemIDFromRequest(r))
    if err != nil {
        // Map cancellation, timeout, not-found, and other errors
        // according to the API's response policy.
        http.Error(w, "request failed", http.StatusInternalServerError)
        return
    }

    writeItem(w, item)
}

func (repo *ItemRepository) Get(ctx context.Context, id string) (Item, error) {
    var item Item
    err := repo.db.QueryRowContext(ctx,
        "SELECT id, name FROM items WHERE id = ?", id,
    ).Scan(&item.ID, &item.Name)
    if err != nil {
        return Item{}, err
    }
    return item, nil
}

The placeholder in this example is illustrative: SQL placeholder syntax varies by driver. Translate sql.ErrNoRows and context cancellation or deadline errors into the API’s intended behavior rather than returning every database error as an internal server error. Do not store a request context in a long-lived struct; pass it as an argument through each operation.

Set a smaller operation budget when needed

If an endpoint needs a tighter database-operation budget than the overall request deadline, derive a timeout context and always call its cancel function:

queryCtx, cancel := context.WithTimeout(r.Context(), queryBudget)
defer cancel()

rows, err := db.QueryContext(queryCtx, query, args...)

Choose queryBudget as part of the endpoint’s latency and dependency policy; the sources do not establish one timeout that suits all APIs. A timeout cancels the context, but exact interruption behavior can depend on the database driver and server.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Choose the database/sql method that matches the query

  • Use QueryContext when a statement returns a result set. Close the returned Rows, and check Rows.Err() after iteration.
  • Use QueryRowContext when you expect at most one row. Call Scan to retrieve it and handle sql.ErrNoRows where appropriate.
  • Use ExecContext for statements that do not return rows, such as many inserts or updates.
rows, err := db.QueryContext(ctx, query, args...)
if err != nil {
    return err
}
defer rows.Close()

for rows.Next() {
    var item Item
    if err := rows.Scan(&item.ID, &item.Name); err != nil {
        return err
    }
    // Use item.
}
if err := rows.Err(); err != nil {
    return err
}

For repeatedly executed SQL, a prepared statement may be appropriate, but do not assume it guarantees a speedup. Assess its behavior with the chosen driver and workload.

How to measure connection-pool waits in Go

DB.Stats() provides a snapshot of pool activity. Record it periodically or alongside application telemetry, then compare changes over time with request-level and database-level signals. Relevant fields include:

  • OpenConnections, InUse, and Idle: current pool counts.
  • WaitCount and WaitDuration: accumulated waits for a connection when the pool is constrained.
  • MaxOpenConnections: the configured maximum-open limit.
stats := db.Stats()
logger.Info("database pool",
    "open", stats.OpenConnections,
    "in_use", stats.InUse,
    "idle", stats.Idle,
    "wait_count", stats.WaitCount,
    "wait_duration", stats.WaitDuration,
    "max_open", stats.MaxOpenConnections,
)

Because wait counters and durations accumulate, compare their changes over a defined interval rather than interpreting a single lifetime total as a rate. A growing wait duration warrants investigation, not an automatic increase to the maximum: more connections can increase pressure on a database that is already at capacity.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

How to benchmark pool settings fairly

Official Go documentation describes the pool controls and diagnostic tools; it does not report a universal throughput gain, latency improvement, or best pool size for this API. To evaluate a proposed configuration, change pool settings while keeping the workload and environment controlled.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  1. Record the database engine and version, Go driver and version, schema, SQL statements, request mix, and transaction behavior.
  2. Document concurrency and the machine or container resources available to the API and database.
  3. Run each configuration against the same workload and collect throughput plus latency distributions, not only an average.
  4. At the same time, record DB.Stats(), database saturation indicators, and errors.
  5. Use Go CPU and heap profiles if results suggest that application-side CPU or memory costs are limiting performance.
  6. Repeat runs under comparable conditions and report the configuration, environment, and test date with any measured result.

Go’s profiling tools can help locate Go-side hot spots; they do not replace database monitoring or a representative workload. If you expose runtime profiling handlers such as pprof in a production service, restrict access rather than making profiling data publicly available.

How many database connections should an API use?

The number is constrained by both the API’s concurrent database demand and the database’s connection budget, including other services and administrative usage. It also depends on how long operations occupy connections and what the database can handle. The title alone does not specify a database, driver, deployment, or workload, so an exact value cannot be responsibly recommended.

Use pool metrics and the controlled comparison above to determine whether a limit is causing meaningful queueing and whether the database has capacity for more concurrent work. A pool is a concurrency control, not a guarantee of higher performance: raising its limit can move the bottleneck from the Go process to the database.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
PC Slower Than It Used to Be?Free scan - under a minute
Crashes, No Sound, or Screen Glitches?Free driver scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.