Skip to Content
Course content

176: Connection Pooling with SQLx

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this a dozen times in code reviews for junior Rust developers: the "Clean Slate" approach to database connections. The idea is that to avoid leaking resources or leaving hanging transactions, you should just open a connection at the start of an HTTP request and close it at the end. It feels intuitively "safe."

"One Connection Per Request is Cleaner"

Let's look at why that intuition is a performance trap. Imagine you're building a high-traffic user session store. If you open a new connection for every single request, your code looks something like this:

// The "Wrong" Way
async fn get_user_session(url: &str, session_id: &str) -> Result {
    let conn = PgConnection::connect(url).await?; // Handshake happens here
    let session = sqlx::query_as!(Session, "SELECT * FROM sessions WHERE id = $1", session_id)
        .fetch_one(&conn)
        .await?;
    Ok(session)
    // Connection drops here
}

The problem isn't the Rust code; it's the network. Every time PgConnection::connect is called, your app has to perform a TCP handshake, negotiate TLS, and handle the Postgres authentication protocol. In a local dev environment, this takes a few milliseconds. In production, across a VPC, it can be a significant chunk of your total request latency. Even worse, Postgres forks a new process for every connection. If you get 500 concurrent users, you're asking your database to spawn 500 processes simultaneously. Your DB will likely hit its max_connections limit and start rejecting requests before your app even breaks a sweat.

"Managing a Pool of Warm Connections"

This is where PgPool comes in. Instead of treating connections as disposable, we treat them as a shared resource. A connection pool maintains a set of "warm" connections that stay open. When you need to run a query, you "borrow" a connection from the pool, use it, and then return it. The TCP handshake happened once when the app started, not every time a user refreshes their page.

Here is how we actually implement this in a production-ready way using PgPoolOptions. I generally recommend being explicit about your pool limits rather than relying on defaults, because "default" varies depending on your environment.

use sqlx::postgres::PgPoolOptions;
use std::time::Duration;

async fn setup_database_pool(url: &str) -> Result {
    PgPoolOptions::new()
        .max_connections(10) // Limit concurrent connections to the DB
        .acquire_timeout(Duration::from_secs(3)) // Don't wait forever if the pool is full
        .idle_timeout(Duration::from_secs(600)) // Close connections that haven't been used in 10 mins
        .connect(url)
        .await
}

Now, look at how the usage changes. You don't pass a connection string around your app anymore; you pass the PgPool.

async fn get_user_session(pool: &sqlx::PgPool, session_id: &str) -> Result {
    // .fetch_one() accepts &PgPool. 
    // It automatically acquires a connection and returns it to the pool when done.
    let session = sqlx::query_as!(Session, "SELECT * FROM sessions WHERE id = $1", session_id)
        .fetch_one(pool) 
        .await?;
    
    Ok(session)
}

A quick tip: you'll notice that PgPool is internally wrapped in an Arc. This means you can .clone() the pool and move it into different async tasks or Axum handlers without any expensive overhead. You aren't cloning the connections themselves—just the handle to the manager that controls them.

One thing to keep in mind: if you start a transaction using pool.begin(), you are checking out a single connection for the duration of that transaction. If you have a pool size of 10 and you have 10 long-running transactions open, the 11th request will hang until the acquire_timeout hits. Always keep your transactions as short as possible to avoid starving the pool.




📋 Practical Task

Implementing a Throttled User Session Manager with SQLx

You are tasked with creating a database module for a session management service. You need to ensure that the application does not overwhelm the database during peak traffic by strictly limiting the number of concurrent connections.

Requirements:

  • Create a function create_session_pool(url: &str) that returns a PgPool.
  • Configure the pool to have a maximum of 5 connections.
  • Set an acquisition timeout of 2 seconds (meaning if a connection isn't available within 2 seconds, it should return an error).
  • Implement a function fetch_session_data(pool: &PgPool, session_id: &str) that queries a hypothetical sessions table (columns: id, data) and returns the data string.
  • Ensure that fetch_session_data uses the pool directly to execute the query, allowing the pool to handle the checkout/checkin process automatically.

Deliverable: Provide the Rust code implementing the pool configuration and the query function.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.