Backend Development 8 min read

Using Redis in Rust: Singleton Pattern and Connection Pooling with r2d2

This article explains how to integrate Redis into a Rust backend by defining resource structs, wrapping the redis‑rs client, implementing a connection manager for r2d2, creating a global singleton pool with OnceCell, and demonstrating basic CRUD operations through example code.

JD Tech Talk
JD Tech Talk
JD Tech Talk
Using Redis in Rust: Singleton Pattern and Connection Pooling with r2d2

When developing backend applications in Rust, interacting with Redis typically involves handling both single‑instance and clustered deployments, and managing connections efficiently through singletons and pooling.

The tutorial introduces three primary crates: once_cell for singleton creation, redis‑rs as the Rust driver for Redis, and r2d2 for connection pooling.

1. redis‑rs Wrapper

A custom RedisInstance struct mirrors configuration files, and an enum RedisClient abstracts over single and cluster clients. The RedisConnection enum encapsulates the actual connections, providing methods like is_open and query to unify API usage.

#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub struct RedisInstance {
    #[serde(default = "RedisInstance::urls_default")]
    pub urls: Vec
,
    #[serde(default = "RedisInstance::password_default")]
    pub password: String,
    #[serde(default = "RedisInstance::instance_type_default")]
    pub instance_type: InstanceType,
}

#[derive(Clone)]
pub enum RedisClient {
    Single(redis::Client),
    Cluster(redis::cluster::ClusterClient),
}
impl RedisClient {
    pub fn get_redis_connection(&self) -> RedisResult
{
        return match self {
            RedisClient::Single(s) => {
                let conn = s.get_connection()?;
                Ok(RedisConnection::Single(Box::new(conn)))
            }
            RedisClient::Cluster(c) => {
                let conn = c.get_connection()?;
                Ok(RedisConnection::Cluster(Box::new(conn)))
            }
        };
    }
}

pub enum RedisConnection {
    Single(Box
),
    Cluster(Box
),
}
impl RedisConnection {
    pub fn is_open(&self) -> bool {
        return match self {
            RedisConnection::Single(sc) => sc.is_open(),
            RedisConnection::Cluster(cc) => cc.is_open(),
        };
    }
    pub fn query
(&mut self, cmd: &redis::Cmd) -> RedisResult
{
        return match self {
            RedisConnection::Single(sc) => match sc.as_mut().req_command(cmd) {
                Ok(val) => from_redis_value(&val),
                Err(e) => Err(e),
            },
            RedisConnection::Cluster(cc) => match cc.req_command(cmd) {
                Ok(val) => from_redis_value(&val),
                Err(e) => Err(e),
            },
        };
    }
}

2. r2d2 Connection Pool Implementation

A RedisConnectionManager struct implements the r2d2::ManageConnection trait, defining how to create, validate, and detect broken connections.

#[derive(Clone)]
pub struct RedisConnectionManager {
    pub redis_client: RedisClient,
}
impl r2d2::ManageConnection for RedisConnectionManager {
    type Connection = RedisConnection;
    type Error = RedisError;

    fn connect(&self) -> Result
{
        let conn = self.redis_client.get_redis_connection()?;
        Ok(conn)
    }

    fn is_valid(&self, conn: &mut RedisConnection) -> Result<(), Self::Error> {
        match conn {
            RedisConnection::Single(sc) => { redis::cmd("PING").query(sc)?; }
            RedisConnection::Cluster(cc) => { redis::cmd("PING").query(cc)?; }
        }
        Ok(())
    }

    fn has_broken(&self, conn: &mut RedisConnection) -> bool {
        !conn.is_open()
    }
}

3. Generating the Connection Pool

The gen_redis_conn_pool function reads configuration, creates a RedisConnectionManager , and builds an r2d2::Pool with configurable size, idle count, and timeout.

pub fn gen_redis_conn_pool() -> Result
> {
    let config = get_config()?;
    let redis_client = config.redis.instance.to_redis_client()?;
    let manager = RedisConnectionManager { redis_client };
    let pool = r2d2::Pool::builder()
        .max_size(config.redis.pool.max_size as u32)
        .min_idle(Some(config.redis.pool.mini_idle as u32))
        .connection_timeout(Duration::from_secs(config.redis.pool.connection_timeout as u64))
        .build(manager)?;
    Ok(pool)
}

4. Global Singleton Pool

Using once_cell::OnceCell , a static GLOBAL_REDIS_POOL holds the pool, and init_global_redis initializes it at program start, panicking on failure to ensure the resource is always available.

pub static GLOBAL_REDIS_POOL: OnceCell
> = OnceCell::new();

fn init_global_redis() {
    GLOBAL_REDIS_POOL.get_or_init(|| {
        let pool = match gen_redis_conn_pool() {
            Ok(it) => it,
            Err(err) => panic!("{}", err.to_string()),
        };
        pool
    });
}

5. Using the Redis Resource

An example put function retrieves a connection from the global pool and executes a SET command, handling the case where the pool has not been initialized.

pub fn put(kv: KV) -> Result<()> {
    let conn = GLOBAL_REDIS_POOL.get();
    return match conn {
        Some(c) => {
            c.get()?.query(redis::cmd("set").arg(kv.Key).arg(kv.Value))?;
            Ok(())
        }
        None => Err(anyhow!("redis pool not init")),
    };
}

The full source code resides in the fullstack-rs repository, demonstrating a complete flow from HTTP entry to Redis write operations.

Backend DevelopmentrustRedisconnection poolingoncecellr2d2
JD Tech Talk
Written by

JD Tech Talk

Official JD Tech public account delivering best practices and technology innovation.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.