# How DBX Handles Connection Pooling for MySQL, PostgreSQL, and Redis

> Learn how DBX centralizes connection pooling for MySQL, PostgreSQL, and Redis using a thread-safe HashMap in AppState. Discover lazy initialization and efficient connection management.

- Repository: [skyler/dbx](https://github.com/t8y2/dbx)
- Tags: internals
- Published: 2026-07-04

---

**DBX centralizes connection pooling in an `AppState` struct that maintains a thread-safe `HashMap` of driver-specific pools, lazily initializing MySQL, PostgreSQL, and Redis connections via the `get_or_create_pool` async method.**

The `t8y2/dbx` repository implements a unified connection management layer that abstracts database-specific pooling implementations behind a single registry. By storing active pools in a central `HashMap<String, PoolKind>` within `AppState`, DBX enables efficient connection reuse across heterogeneous backends while handling driver-specific nuances like TLS cancellation contexts and Redis cluster topology.

## Centralized Pool Registry in `AppState`

At the core of DBX's pooling strategy is the **`AppState`** struct defined in [`crates/dbx-core/src/connection.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/connection.rs). This state object maintains a thread-safe mapping of pool keys to driver-specific pool instances:

```rust
// Conceptual structure from connection.rs
pub struct AppState {
    connections: RwLock<HashMap<String, PoolKind>>,
    configs: RwLock<HashMap<String, ConnectionConfig>>,
    postgres_cancel_contexts: RwLock<HashMap<String, TlsCancelContext>>,
}

```

The **`PoolKind`** enum distinguishes between the three supported database types, wrapping `mysql_async::Pool` for MySQL, `deadpool_postgres::Pool` for PostgreSQL, and a custom `RedisConnection` type for Redis.

When a component requests a connection, it invokes **`get_or_create_pool`**, which implements a lazy-initialization pattern:

1. **Derives a unique pool key** using `base_pool_key_for` combined with `session_scoped_pool_key_for` to account for connection IDs, database names, and optional client-session isolation.
2. **Checks the existing pool map**—if a matching pool exists and is fresh, it updates the activity timestamp and returns the existing key.
3. **Creates a driver-specific pool** via the appropriate constructor if no match exists, then inserts it into the central map.

## MySQL Connection Pooling Implementation

For MySQL, DBX utilizes the **`mysql_async`** crate's `Pool` type, wrapped in the `PoolKind::Mysql(MySqlPool, MysqlMode)` variant. The implementation distinguishes between **metadata pools** and **bare pools** depending on whether schema introspection requires a connection without a default database selected.

Key implementation details in [`crates/dbx-core/src/connection.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/connection.rs):

- **Pool Limits**: Per-session connection limits are enforced via `mysql_pool_max_connections_for_session`, preventing a single client session from exhausting the global pool.
- **OceanBase/Oracle Compatibility**: When connecting to OceanBase or Oracle-mode MySQL, DBX executes `SET ob_query_timeout` immediately after establishing the connection via `oceanbase_mysql_setup_queries`.
- **TLS Support**: Optional TLS configuration is applied during pool creation in `connect_mysql_metadata_pool`.

The following Tauri command demonstrates acquiring a MySQL pool and executing a query:

```rust
#[tauri::command]
pub async fn mysql_list_tables(state: State<'_, Arc<AppState>>, conn_id: String, db: String) -> Result<Vec<String>, String> {
    // Acquire (or create) a MySQL pool for the requested database
    let pool_key = state.get_or_create_pool(&conn_id, Some(&db)).await?;
    
    // Obtain the pool from the central map
    let pool = state.connections.read().await.get(&pool_key)
        .ok_or("Pool disappeared")?;
    
    // The pool is of type `PoolKind::Mysql`
    if let PoolKind::Mysql(ref mysql_pool, _) = pool {
        let mut conn = mysql_pool.get_conn().await.map_err(|e| e.to_string())?;
        let rows: Vec<mysql_async::Row> = conn
            .query("SHOW TABLES")
            .await
            .map_err(|e| e.to_string())?;
        
        Ok(rows.iter()
            .map(|r| r.as_string("Tables_in_".to_owned() + &db).unwrap_or_default())
            .collect())
    } else {
        Err("Not a MySQL connection".into())
    }
}

```

## PostgreSQL Connection Pooling with Deadpool

DBX implements PostgreSQL pooling using the **`deadpool_postgres`** crate, storing pools as `PoolKind::Postgres(deadpool_postgres::Pool)`. This provides Tokio-based connection management with built-in bounds checking.

A notable DBX-specific extension is the **TLS cancel context** mechanism. When creating a PostgreSQL pool, DBX optionally stores a cancel context via `build_postgres_cancel_context`:

```rust
// From connection.rs (simplified)
DatabaseType::Postgres => {
    let pg_pool = db::postgres::connect(&url, connect_timeout).await?;
    // TLS cancel context for query cancellation
    if let Some(ctx) = db::postgres::build_postgres_cancel_context(&url) {
        state.postgres_cancel_contexts.write().await.insert(pool_key.clone(), ctx);
    }
    PoolKind::Postgres(pg_pool)
}

```

This context allows cancelled queries to reconstruct the TLS connection stream when terminating long-running operations.

Example usage within a Tauri command:

```rust
#[tauri::command]
pub async fn pg_query_scalar(state: State<'_, Arc<AppState>>, conn_id: String, sql: String) -> Result<serde_json::Value, String> {
    let pool_key = state.get_or_create_pool(&conn_id, None).await?;
    let pool = state.connections.read().await.get(&pool_key)
        .ok_or("Missing pool")?;
    
    if let PoolKind::Postgres(ref pg_pool) = pool {
        let client = pg_pool.get().await.map_err(|e| e.to_string())?;
        let row = client.query_one(&sql, &[]).await.map_err(|e| e.to_string())?;
        // Convert the first column to JSON
        Ok(serde_json::to_value(row.get::<usize, i64>(0)).unwrap())
    } else {
        Err("Not a Postgres connection".into())
    }
}

```

## Redis Connection Pooling for Cluster and Sentinel

Redis connections in DBX are represented by **`PoolKind::Redis(RedisConnection)`**, where `RedisConnection` is an enum supporting three deployment modes:

- **Cluster**: Uses `connect_redis_cluster` to build a client aware of all cluster nodes, enabling automatic slot routing.
- **Sentinel**: Uses `connect_redis_sentinel` to discover the current master via Sentinel before creating a direct client.
- **Standalone**: Uses `connect_standalone` for simple single-instance Redis, respecting TLS and authentication settings.

The connection object wraps either a `Cluster` variant or a `Direct` variant containing a `tokio::sync::Mutex` around the client:

```rust
// From connection.rs (lines 144-158)
DatabaseType::Redis => {
    let con = if db_config.uses_redis_cluster() {
        db::redis_driver::RedisConnection::Cluster(
            self.connect_redis_cluster(connection_id, &db_config).await?)
    } else if db_config.uses_redis_sentinel() {
        db::redis_driver::RedisConnection::Direct(tokio::sync::Mutex::new(
            self.connect_redis_sentinel(connection_id, &db_config).await?))
    } else {
        db::redis_driver::RedisConnection::Direct(tokio::sync::Mutex::new(
            db::redis_driver::connect_standalone(&db_config, &host, port, connect_timeout).await?))
    };
    PoolKind::Redis(con)
}

```

Accessing a Redis value requires matching on the `RedisConnection` variant:

```rust
#[tauri::command]
pub async fn redis_get(state: State<'_, Arc<AppState>>, conn_id: String, db: u32, key: String) -> Result<Option<String>, String> {
    let pool_key = state.get_or_create_pool(&conn_id, None).await?;
    let pool = state.connections.read().await.get(&pool_key)
        .ok_or("Missing pool")?;
    
    if let PoolKind::Redis(ref redis_conn) = pool {
        match redis_conn {
            db::redis_driver::RedisConnection::Direct(ref mutex) => {
                let client = mutex.lock().await;
                let value: Option<String> = client.get(key).await.map_err(|e| e.to_string())?;
                Ok(value)
            }
            db::redis_driver::RedisConnection::Cluster(ref cluster) => {
                let value: Option<String> = cluster.get(key).await.map_err(|e| e.to_string())?;
                Ok(value)
            }
        }
    } else {
        Err("Not a Redis connection".into())
    }
}

```

## Summary

- **Unified Registry**: DBX maintains all connection pools in a central `HashMap<String, PoolKind>` within `AppState`, enabling thread-safe access and reuse across the application.
- **Lazy Initialization**: The `get_or_create_pool` method checks for existing pools using derived keys (`base_pool_key_for` + `session_scoped_pool_key_for`) before creating new ones, minimizing redundant connections.
- **Driver-Specific Optimization**: MySQL pools support OceanBase-specific timeouts and session limits; PostgreSQL pools include TLS cancel contexts for safe query termination; Redis pools abstract Cluster, Sentinel, and Standalone modes behind a single interface.
- **Type Safety**: The `PoolKind` enum ensures compile-time differentiation between `mysql_async::Pool`, `deadpool_postgres::Pool`, and `RedisConnection` variants.

## Frequently Asked Questions

### How does DBX determine when to reuse an existing pool versus creating a new one?

DBX derives a unique pool key using `base_pool_key_for` combined with `session_scoped_pool_key_for`, incorporating the connection ID, database name, and optional client-session ID. Before creating a pool, `get_or_create_pool` checks if this key exists in the `connections` HashMap. If found, it updates the activity timestamp via `touch_pool_activity` and returns the existing pool, ensuring efficient reuse while preventing connection leaks.

### What is the difference between MySQL metadata pools and bare pools in DBX?

**Metadata pools** are created when DBX needs to perform schema introspection without assuming a specific default database, using `connect_bare_metadata_pool` or `connect_mysql_metadata_pool`. These are used for operations like listing databases or tables. **Bare pools** refer to connections established without a default database context, often used internally when the target database is specified per-query rather than in the connection string.

### How does DBX handle query cancellation for PostgreSQL connections?

DBX implements a **TLS cancel context** mechanism via `build_postgres_cancel_context`. When establishing a PostgreSQL pool, it stores the TLS parameters needed to reconstruct the connection stream in `postgres_cancel_contexts`. If a query needs to be cancelled, DBX can use this context to establish a separate cancellation connection that respects the original TLS configuration, allowing safe termination of long-running queries without corrupting the connection pool.

### Does DBX support Redis Cluster and Sentinel deployments?

Yes. The `RedisConnection` enum in [`crates/dbx-core/src/db/redis_driver.rs`](https://github.com/t8y2/dbx/blob/main/crates/dbx-core/src/db/redis_driver.rs) explicitly supports three deployment modes. **Cluster** mode uses `connect_redis_cluster` for automatic slot routing across nodes. **Sentinel** mode uses `connect_redis_sentinel` to discover the current master dynamically. **Standalone** mode uses `connect_standalone` for direct single-node connections. All three modes respect TLS and authentication settings from the connection configuration.