bb8 Documentation

repository·main·Indexed 21 days ago

https://github.com/djc/bb8

A full-featured asynchronous connection pool for Rust built on top of tokio. bb8 is backend-agnostic and uses the ManageConnection trait to manage sets of connections to various backends, such as PostgreSQL and Redis, preventing resource exhaustion by reusing open connections. It provides a flexible Builder for configuring pool size, timeouts, and queue strategies (FIFO/LIFO), and includes built-in support for monitoring pool state and statistics.

Tokens
4.5K
Snippets
15
Records
21
Agent score
75%

What's inside bb8

  1. What is bb8 and how does it work?

    main

    bb8 is an asynchronous connection pool designed for use with tokio. It prevents the inefficiency and resource exhaustion caused by opening new database connections for every request by maintaining a set of open connections that are reused.

    bb8 is backend-agnostic. It relies on the ManageConnection trait, which implementors use to provide database-specific logic for creating new connections and verifying their health. To use bb8 with a specific database, you typically use an adapter crate (e.g., bb8-postgres or bb8-redis).

  2. Understand QueueStrategy: FIFO vs LIFO

    main

    The QueueStrategy determines how the pool selects an idle connection when a user requests one:

    • Fifo (First In, First Out): Behaves like a queue. It spreads load evenly across all existing connections, which helps prevent connections from idling out, but keeps more connections active.
    • Lifo (Last In, First Out): Behaves like a stack. It reuses the most recently used connection. This helps keep the total pool size small by allowing older, unused connections to reach their idle timeout and be evicted.
  3. How bb8 connection pools work

    main

    bb8 is an asynchronous connection pool designed for tokio. Instead of opening a new connection for every request—which is inefficient and risks resource exhaustion—bb8 maintains a set of open connections that are reused.

    To use bb8, you must provide an implementation of the ManageConnection trait. This trait contains the database-specific logic required to create new connections and verify their health. The pool manages the lifecycle of these connections, handing them out to callers and reclaiming them once they fall out of scope.

    #[tokio::main]
    async fn main() {
        let manager = bb8_foodb::FooConnectionManager::new("localhost:1234");
        let pool = bb8::Pool::builder().build(manager).await.unwrap();
    
        for _ in 0..20 {
            let pool = pool.clone();
            tokio::spawn(async move {
                let conn = pool.get().await.unwrap();
                // use the connection
                // it will be returned to the pool when it falls out of scope.
            });
        }
    }
  4. Initialize a connection pool with Builder

    main

    To create a new Pool, use Pool::builder() to configure settings and then call .build(manager) or .build_unchecked(manager).

    • build(manager): An async method that waits until the configured minimum number of connections is established before returning the pool.
    • build_unchecked(manager): Returns the pool immediately and spawns a background task to establish connections.
    let manager = MyManager::new();
    let pool = Pool::builder()
        .max_size(20)
        .min_idle(Some(5))
        .build(manager)
        .await?;
  5. Initialize and use a bb8 connection pool

    main

    To use bb8, you first create a connection manager (provided by your database adapter), then use bb8::Pool::builder() to configure and build the pool. You can set constraints like max_size.

    To acquire a connection, call pool.get().await. The connection is automatically returned to the pool when the returned guard falls out of scope.

    #[tokio::main]
    async fn main() {
        let manager = bb8_foodb::FooConnectionManager::new("localhost:1234");
        let pool = bb8::Pool::builder()
            .max_size(15)
            .build(manager)
            .await
            .unwrap();
    
        for _ in 0..20 {
            let pool = pool.clone();
            tokio::spawn(async move {
                let conn = pool.get().await.unwrap();
                // use the connection
                // it will be returned to the pool when it falls out of scope.
            });
        }
    }
  6. Configure Pool settings via Builder

    main

    The Builder allows fine-tuning the pool behavior. Key configuration methods include:

    • max_size(u32): Maximum connections allowed (must be > 0). Defaults to 10.
    • min_idle(impl Into<Option<u32>>): Minimum idle connections to maintain. Defaults to None.
    • test_on_check_out(bool): If true, verifies connection health via ManageConnection::is_valid before checkout. Defaults to true.
    • max_lifetime(impl Into<Option<Duration>>): Maximum age of a connection. Defaults to 30 minutes.
    • idle_timeout(impl Into<Option<Duration>>): Duration after which excess idle connections are closed. Defaults to 10 minutes.
    • connection_timeout(Duration): How long Pool::get waits before timing out. Defaults to 30 seconds.
    • retry_connection(bool): Enables automatic retries on connection creation. Defaults to true.
    • queue_strategy(QueueStrategy): Sets whether to use Fifo or Lifo for connection reuse.
    • connection_customizer(Box<dyn CustomizeConnection>): Provides a way to run initialization logic on new connections.
  7. Available bb8 adapters for different backends

    main

    bb8 supports various backends through specialized adapter crates. Below is a list of supported backends and their corresponding adapters:

    BackendAdapter Crate
    tokio-postgresbb8-postgres (in-tree)
    redisbb8-redis (in-tree)
    redis_cluster_asyncbb8-redis-cluster
    rsmqrsmq_async
    bolt-clientbb8-bolt
    dieseldiesel_async
    tiberiusbb8-tiberius
    nebula-clientbb8-nebula
    memcache-asyncbb8-memcached
    lapinbb8-lapin
    arangorsbb8-arangodb
    tonicbb8-tonic
  8. Use RedisConnectionManager with bb8

    main

    To use Redis with the bb8 connection pool, use the RedisConnectionManager. This manager implements bb8::ManageConnection for redis::aio::MultiplexedConnection.

    1. Initialize the manager using RedisConnectionManager::new(connection_info), where connection_info can be any type that implements redis::IntoConnectionInfo (e.g., a connection string like redis://localhost).
    2. Build a bb8::Pool using the manager.
    3. Retrieve connections from the pool using pool.get().await.
    use futures_util::future::join_all;
    use bb8_redis::{
        bb8,
        redis::{cmd, AsyncCommands},
        RedisConnectionManager
    };
    
    #[tokio::main]
    async fn main() {
        // 1. Create the manager
        let manager = RedisConnectionManager::new("redis://localhost").unwrap();
        
        // 2. Build the pool
        let pool = bb8::Pool::builder().build(manager).await.unwrap();
    
        let mut handles = vec![];
    
        for _i in 0..10 {
            let pool = pool.clone();
    
            handles.push(tokio::spawn(async move {
                // 3. Get a connection from the pool
                let mut conn = pool.get().await.unwrap();
    
                // Use the connection with redis-rs commands
                let reply: String = cmd("PING").query_async(&mut *conn).await.unwrap();
    
                assert_eq!("PONG", reply);
            }));
        }
    
        join_all(handles).await;
    }
  9. Get a connection from the pool using pool.get()

    main

    Once you have a Pool instance, you can request a connection by calling .get().await. This returns a PooledConnection, which is a wrapper around the underlying connection. When the PooledConnection is dropped (falls out of scope), the connection is automatically returned to the pool for reuse.

    let conn = pool.get().await.unwrap();
    // use the connection
    // it will be returned to the pool when it falls out of scope.
  10. Retrieve connections from the pool

    main

    Use Pool::get() to retrieve a connection managed by the pool. This returns a PooledConnection which implements Deref and DerefMut to allow direct access to the underlying connection. When the PooledConnection is dropped, the connection is automatically returned to the pool.

    If you need to move the connection to a different thread or leak the pool's lifetime, use Pool::get_owned(). This returns a PooledConnection<'static, M> by cloning the pool's inner state.

    // Standard usage (preferred)
    let conn = pool.get().await?;
    // Use conn like the underlying connection type
    conn.execute("SELECT 1").await?;
    
    // Owned usage (for moving across lifetimes)
    let owned_conn = pool.get_owned().await?;
  11. Initialize a connection pool with Pool::builder()

    main

    To create a new connection pool, use the Pool::builder() pattern. You must pass an object that implements the ManageConnection trait to the .build(manager) method. This method is asynchronous and returns a Result<Pool<M>, RunError>.

    let manager = bb8_foodb::FooConnectionManager::new("localhost:1234");
    let pool = bb8::Pool::builder().build(manager).await.unwrap();
  12. RedisConnectionManager::new

    main

    Creates a new RedisConnectionManager. The input info must implement redis::IntoConnectionInfo (such as a connection string).

    Returns Ok(RedisConnectionManager) or a RedisError if the connection info is invalid.

    impl RedisConnectionManager {
        pub fn new<T: IntoConnectionInfo>(info: T) -> Result<Self, RedisError>
    }